pmagpy 4.2.125__py3-none-any.whl → 4.3.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (976) hide show
  1. pmagpy/__init__.py +2 -1
  2. pmagpy/contribution_builder.py +3 -9
  3. pmagpy/ipmag.py +155 -220
  4. pmagpy/pmag.py +17 -65
  5. pmagpy/pmagplotlib.py +14 -257
  6. pmagpy/rockmag.py +4513 -0
  7. pmagpy/version.py +2 -2
  8. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/PmagPy-cli.ipynb +77 -106
  9. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/PmagPy_MagIC.ipynb +169 -170
  10. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/PmagPy_calculations.ipynb +2 -6
  11. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/PmagPy_introduction.ipynb +2 -3
  12. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/PmagPy_plots_analysis.ipynb +275 -329
  13. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/aarm_magic/aarm_measurements.txt +126 -126
  14. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/atrm_magic/measurements.txt +210 -210
  15. {pmagpy-4.2.125.dist-info → pmagpy-4.3.1.dist-info}/METADATA +1 -1
  16. pmagpy-4.3.1.dist-info/RECORD +1081 -0
  17. pmagpy-4.2.125.dist-info/RECORD +0 -1080
  18. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/ages.txt +0 -0
  19. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/er_ages.txt +0 -0
  20. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/er_citations.txt +0 -0
  21. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/er_images.txt +0 -0
  22. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/er_locations.txt +0 -0
  23. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/er_mailinglist.txt +0 -0
  24. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/er_samples.txt +0 -0
  25. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/er_sites.txt +0 -0
  26. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/er_specimens.txt +0 -0
  27. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/magic_measurements.txt +0 -0
  28. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/magic_methods.txt +0 -0
  29. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/pmag_criteria.txt +0 -0
  30. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/pmag_results.txt +0 -0
  31. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/pmag_sites.txt +0 -0
  32. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/pmag_specimens.txt +0 -0
  33. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/rmag_anisotropy.txt +0 -0
  34. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/rmag_hysteresis.txt +0 -0
  35. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/rmag_results.txt +0 -0
  36. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/2_5/McMurdo/zmab0100049tmp03.txt +0 -0
  37. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/.ipynb_checkpoints/Parsing_data_model-checkpoint.ipynb +0 -0
  38. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/ages.txt +0 -0
  39. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/contribution.txt +0 -0
  40. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/criteria.txt +0 -0
  41. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/extra_specimens.txt +0 -0
  42. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/images.txt +0 -0
  43. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/locations.txt +0 -0
  44. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/measurements.txt +0 -0
  45. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/new_locations.txt +0 -0
  46. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/samples.txt +0 -0
  47. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/sites.txt +0 -0
  48. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/McMurdo/specimens.txt +0 -0
  49. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/.ipynb_checkpoints/ages_from_samples_to_sites-checkpoint.ipynb +0 -0
  50. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_1/ages.txt +0 -0
  51. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_1/locations.txt +0 -0
  52. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_1/measurements.txt +0 -0
  53. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_1/samples.txt +0 -0
  54. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_1/sites.txt +0 -0
  55. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_1/specimens.txt +0 -0
  56. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_2/ages.txt +0 -0
  57. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_2/locations.txt +0 -0
  58. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_2/measurements.txt +0 -0
  59. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_2/samples.txt +0 -0
  60. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_2/sites.txt +0 -0
  61. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/Location_2/specimens.txt +0 -0
  62. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/ages.txt +0 -0
  63. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/ages_from_samples_to_sites.ipynb +0 -0
  64. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/contribution.txt +0 -0
  65. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/criteria.txt +0 -0
  66. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/locations.txt +0 -0
  67. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/magic_contribution.txt +0 -0
  68. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/measurements.txt +0 -0
  69. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/samples.txt +0 -0
  70. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/sites.txt +0 -0
  71. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/specimens.txt +0 -0
  72. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Megiddo/test_spec.txt +0 -0
  73. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/contribution.txt +0 -0
  74. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/contribution_11087_v2.5.txt +0 -0
  75. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/contribution_11087_v3.0.txt +0 -0
  76. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/er_citations.txt +0 -0
  77. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/er_locations.txt +0 -0
  78. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/er_sites.txt +0 -0
  79. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/locations.txt +0 -0
  80. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/pmag_results.txt +0 -0
  81. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/pmag_sites.txt +0 -0
  82. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/sites.txt +0 -0
  83. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/3_0/Osler/stored.json +0 -0
  84. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/Cont_rot.svg +0 -0
  85. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/aus_saf.frp +0 -0
  86. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/col_saf.frp +0 -0
  87. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/eant_saf.frp +0 -0
  88. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/eur_saf.frp +0 -0
  89. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/grn_saf.frp +0 -0
  90. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/ind_saf.frp +0 -0
  91. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/mad_saf.frp +0 -0
  92. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/nam_saf.frp +0 -0
  93. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/neaf_saf.frp +0 -0
  94. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/nwaf_saf.frp +0 -0
  95. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/par_saf.frp +0 -0
  96. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/FRPTMP/sac_saf.frp +0 -0
  97. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/af.asc +0 -0
  98. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/ages.tmp +0 -0
  99. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/ant.asc +0 -0
  100. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/aus.asc +0 -0
  101. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/aus_saf.frp +0 -0
  102. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/balt.asc +0 -0
  103. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/col_saf.frp +0 -0
  104. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/congo.asc +0 -0
  105. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/continents.py +0 -0
  106. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/eant_saf.frp +0 -0
  107. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/eur.asc +0 -0
  108. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/eur_saf.frp +0 -0
  109. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/finrot_saf.txt +0 -0
  110. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/globalapwps.txt +0 -0
  111. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/gond.asc +0 -0
  112. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/grn.asc +0 -0
  113. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/grn_saf.frp +0 -0
  114. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/ib_eur.frp +0 -0
  115. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/ind.asc +0 -0
  116. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/ind.bak +0 -0
  117. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/ind_saf.frp +0 -0
  118. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/kala.asc +0 -0
  119. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/lau.asc +0 -0
  120. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/mad_saf.frp +0 -0
  121. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/mkcont.py +0 -0
  122. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/mkfrp.py +0 -0
  123. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/nam.asc +0 -0
  124. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/nam_saf.frp +0 -0
  125. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/neaf_saf.frp +0 -0
  126. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/nwaf_saf.frp +0 -0
  127. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/par_saf.frp +0 -0
  128. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/plates.asc +0 -0
  129. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/sac_saf.frp +0 -0
  130. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/saf.frp +0 -0
  131. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/sam.asc +0 -0
  132. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Continents/waf.asc +0 -0
  133. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ErMagicBuilder/Z35.sam.magic +0 -0
  134. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ErMagicBuilder/Z35_er_samples.txt +0 -0
  135. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ErMagicBuilder/Z35_er_sites.txt +0 -0
  136. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ErMagicBuilder/Z35_er_specimens.txt +0 -0
  137. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ErMagicBuilder/er_ages.txt +0 -0
  138. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ErMagicBuilder/er_locations.txt +0 -0
  139. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ErMagicBuilder/er_samples.txt +0 -0
  140. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ErMagicBuilder/er_sites.txt +0 -0
  141. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ErMagicBuilder/er_specimens.txt +0 -0
  142. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ErMagicBuilder/magic_measurements.txt +0 -0
  143. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Figures/atrm_meas.png +0 -0
  144. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Figures/chartmaker.png +0 -0
  145. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Figures/meas15.png +0 -0
  146. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Figures/samples.png +0 -0
  147. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/PmagPy_online.ipynb +0 -0
  148. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/Tel-Hazor_Tel-Megiddo_25.Aug.2016.txt +0 -0
  149. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/ages.txt +0 -0
  150. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/ages_from_samples_to_sites.ipynb +0 -0
  151. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/contribution.txt +0 -0
  152. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/criteria.txt +0 -0
  153. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/demag_orient.txt +0 -0
  154. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/locations.txt +0 -0
  155. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/measurements.txt +0 -0
  156. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/new_samples.txt +0 -0
  157. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/new_sites.txt +0 -0
  158. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/new_specimens.txt +0 -0
  159. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/samples.bak +0 -0
  160. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/samples.txt +0 -0
  161. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/sites.bak +0 -0
  162. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/sites.txt +0 -0
  163. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/specimens.bak +0 -0
  164. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/specimens.txt +0 -0
  165. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/3_0/thellier_GUI.log +0 -0
  166. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/SIOfiles/na_sw.atrm +0 -0
  167. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/SIOfiles/na_sw.cool +0 -0
  168. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/SIOfiles/na_sw.thel +0 -0
  169. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/SIOfiles.zip +0 -0
  170. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/ThisProject/SrExample_AF.txt +0 -0
  171. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/ThisProject/SrExample_orient.txt +0 -0
  172. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/ThisProject/SrExample_thellier.txt +0 -0
  173. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/ThisProject/SrExample_thermal.txt +0 -0
  174. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/Pmag_GUI/zmab0083201tmp03.txt +0 -0
  175. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/SVEI_demo.ipynb +0 -0
  176. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/.ipynb_checkpoints/Editing-checkpoint.ipynb +0 -0
  177. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/.ipynb_checkpoints/U1456A-checkpoint.ipynb +0 -0
  178. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/.ipynb_checkpoints/discretes-checkpoint.ipynb +0 -0
  179. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/Core_depthplot.py +0 -0
  180. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/JR6_data/UTESTA.jr6 +0 -0
  181. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/JR6_data/UTESTA_fixed.jr6 +0 -0
  182. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/KLY4S_data/UTESTA.kly4s +0 -0
  183. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/SRM_data/srmdiscrete-XXX-UTEST-A.csv +0 -0
  184. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/SRM_data/srmsection-XXX-UTEST-A.csv +0 -0
  185. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/CoreSummary_XXX_UTESTA.csv +0 -0
  186. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/UTESTA.kly4s.magic +0 -0
  187. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/UTESTA_er_specimens.txt +0 -0
  188. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/UTESTA_fixed.jr6.magic +0 -0
  189. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/UTESTA_rmag_anisotropy.txt +0 -0
  190. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/er_samples.txt +0 -0
  191. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/er_sites.txt +0 -0
  192. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/er_specimens.txt +0 -0
  193. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/magic_measurements.txt +0 -0
  194. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/pmag_specimens.txt +0 -0
  195. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/rmag_anisotropy.txt +0 -0
  196. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/samples-XXX-UTEST-A_er_samples.txt +0 -0
  197. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/srmdiscrete-XXX-UTEST-A.csv.magic +0 -0
  198. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/srmsection-XXX-UTEST-A.csv.magic +0 -0
  199. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/srmsection-XXX-UTEST-A_er_samples.txt +0 -0
  200. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/srmsection-XXX-UTEST-A_er_sites.txt +0 -0
  201. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC/srmsection-XXX-UTEST-A_er_specimens.txt +0 -0
  202. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC3/CoreSummary_XXX_UTESTA.csv +0 -0
  203. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC3/measurements.txt +0 -0
  204. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC3/samples.txt +0 -0
  205. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC3/sites.txt +0 -0
  206. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/UTESTA_MagIC3/specimens.txt +0 -0
  207. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/UTESTA/samples-XXX-UTEST-A.csv +0 -0
  208. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/aarm_magic/arm_magic_example.dat +0 -0
  209. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/aarm_magic/locations.txt +0 -0
  210. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/aarm_magic/samples.txt +0 -0
  211. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/aarm_magic/sites.txt +0 -0
  212. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/aarm_magic/specimens.txt +0 -0
  213. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/angle/angle.dat +0 -0
  214. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/angle/tmp.out +0 -0
  215. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/ages.txt +0 -0
  216. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/er_ages.txt +0 -0
  217. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/er_locations.txt +0 -0
  218. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/er_samples.txt +0 -0
  219. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/er_sites.txt +0 -0
  220. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/er_specimens.txt +0 -0
  221. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/magic_contribution_12152.txt +0 -0
  222. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/magic_measurements.txt +0 -0
  223. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/magic_methods.txt +0 -0
  224. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/measurements.txt +0 -0
  225. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/pmag_specimens.txt +0 -0
  226. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/rmag_anisotropy.txt +0 -0
  227. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/samples.txt +0 -0
  228. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/sites.txt +0 -0
  229. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/ani_depthplot/specimens.txt +0 -0
  230. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/aniso_magic/dike_anisotropy.txt +0 -0
  231. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/aniso_magic/dike_specimens.txt +0 -0
  232. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/aniso_magic/sed_anisotropy.txt +0 -0
  233. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/aniso_magic/sed_specimens.txt +0 -0
  234. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/apwp/apwp_example.dat +0 -0
  235. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/atrm_magic/atrm_magic_example.dat +0 -0
  236. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/atrm_magic/atrm_measurements.txt +0 -0
  237. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/atrm_magic/atrm_measurements3.txt +0 -0
  238. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/atrm_magic/atrm_results.txt +0 -0
  239. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/atrm_magic/locations.txt +0 -0
  240. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/atrm_magic/orig_specimens.txt +0 -0
  241. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/atrm_magic/samples.txt +0 -0
  242. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/atrm_magic/sites.txt +0 -0
  243. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/atrm_magic/specimens.txt +0 -0
  244. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/azdip_magic/azdip_magic_example.dat +0 -0
  245. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/b_vdm/b_vdm_example.dat +0 -0
  246. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/basemap_magic/basemap_example.txt +0 -0
  247. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/biplot_magic/biplot_magic_example.dat +0 -0
  248. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/biplot_magic/contribution.txt +0 -0
  249. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/biplot_magic/locations.txt +0 -0
  250. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/biplot_magic/measurements.txt +0 -0
  251. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/biplot_magic/samples.txt +0 -0
  252. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/biplot_magic/sites.txt +0 -0
  253. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/biplot_magic/specimens.txt +0 -0
  254. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/bootams/bootams_example.dat +0 -0
  255. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/cart_dir/cart_dir_example.dat +0 -0
  256. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/chi_magic/chi_magic_example.dat +0 -0
  257. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/chi_magic/chi_magic_example.txt +0 -0
  258. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/chi_magic/contribution.txt +0 -0
  259. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/chi_magic/locations.txt +0 -0
  260. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/chi_magic/measurements.txt +0 -0
  261. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/chi_magic/samples.txt +0 -0
  262. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/chi_magic/sites.txt +0 -0
  263. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/chi_magic/specimens.txt +0 -0
  264. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/combine_magic/af_measurements.txt +0 -0
  265. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/combine_magic/measurements.txt +0 -0
  266. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/combine_magic/ns_a.mag +0 -0
  267. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/combine_magic/ns_t.mag +0 -0
  268. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/combine_magic/therm_measurements.txt +0 -0
  269. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/common_mean/common_mean_ex_file1.dat +0 -0
  270. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/common_mean/common_mean_ex_file2.dat +0 -0
  271. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_asc_magic/README +0 -0
  272. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_asc_magic/_2g_asc/DR3B.asc +0 -0
  273. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_asc_magic/_2g_asc/OK3_15af.asc +0 -0
  274. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_asc_magic/_2g_asc/README +0 -0
  275. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/KodamaFiles/165A.dat +0 -0
  276. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/KodamaFiles/165B.dat +0 -0
  277. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/KodamaFiles/165C.dat +0 -0
  278. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/KodamaFiles/60A.dat +0 -0
  279. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/KodamaFiles/60B.dat +0 -0
  280. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/KodamaFiles/60C.dat +0 -0
  281. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/KodamaFiles/70A.dat +0 -0
  282. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/KodamaFiles/70C.dat +0 -0
  283. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/KodamaFiles/70D.dat +0 -0
  284. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1ab.dat +0 -0
  285. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1bb.dat +0 -0
  286. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1c.dat +0 -0
  287. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1db.dat +0 -0
  288. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1e.dat +0 -0
  289. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1f.dat +0 -0
  290. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1ga.dat +0 -0
  291. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1ha.dat +0 -0
  292. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1ib.dat +0 -0
  293. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1jb.dat +0 -0
  294. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1kb.dat +0 -0
  295. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1la.dat +0 -0
  296. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton1ma.dat +0 -0
  297. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29ab.dat +0 -0
  298. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29bb.dat +0 -0
  299. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29cb.dat +0 -0
  300. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29da.dat +0 -0
  301. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29db.dat +0 -0
  302. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29e.dat +0 -0
  303. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29eb.dat +0 -0
  304. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29f.dat +0 -0
  305. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29ga.dat +0 -0
  306. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29gc.dat +0 -0
  307. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29h.dat +0 -0
  308. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29ib.dat +0 -0
  309. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton29j.dat +0 -0
  310. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton2a.dat +0 -0
  311. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton2b.dat +0 -0
  312. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton2c.dat +0 -0
  313. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton2d.dat +0 -0
  314. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton2e.dat +0 -0
  315. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton2f.dat +0 -0
  316. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton2g.dat +0 -0
  317. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton2h.dat +0 -0
  318. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton2i.dat +0 -0
  319. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton3aa.dat +0 -0
  320. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton3ba.dat +0 -0
  321. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton3ca.dat +0 -0
  322. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton3da.dat +0 -0
  323. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton3ea.dat +0 -0
  324. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton3fb.dat +0 -0
  325. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton3ga.dat +0 -0
  326. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton3ha.dat +0 -0
  327. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton4ab.dat +0 -0
  328. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton4bb.dat +0 -0
  329. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton4c.dat +0 -0
  330. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton4d.dat +0 -0
  331. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton4eb.dat +0 -0
  332. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton4fb.dat +0 -0
  333. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton4gb.dat +0 -0
  334. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton4ha.dat +0 -0
  335. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/MolinaFiles/ton4ia.dat +0 -0
  336. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/README +0 -0
  337. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/MN1.CSV +0 -0
  338. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/MN_chr_dir.xls +0 -0
  339. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn001-1a.dat +0 -0
  340. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn004-2b.dat +0 -0
  341. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn008-2b.dat +0 -0
  342. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn010-1a.dat +0 -0
  343. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn014-1b.dat +0 -0
  344. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn017-1b.dat +0 -0
  345. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn022-1b.dat +0 -0
  346. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn026-1b.dat +0 -0
  347. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn031-1a.dat +0 -0
  348. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn033-1b.dat +0 -0
  349. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn034-2a.dat +0 -0
  350. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn038-1b.dat +0 -0
  351. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn041-1a.dat +0 -0
  352. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn042-1b.dat +0 -0
  353. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn046-1a.dat +0 -0
  354. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn049-2a.dat +0 -0
  355. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn056-2a.dat +0 -0
  356. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn061-1a.dat +0 -0
  357. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn065-1b.dat +0 -0
  358. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn067-1a.dat +0 -0
  359. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn071-1a.dat +0 -0
  360. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn075-1b.dat +0 -0
  361. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn078-1a.dat +0 -0
  362. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn081-1b.dat +0 -0
  363. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn084-1b.dat +0 -0
  364. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn087-2a.dat +0 -0
  365. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn091-1a.dat +0 -0
  366. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn093-1b.dat +0 -0
  367. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn096-1b.dat +0 -0
  368. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn1.saf +0 -0
  369. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn1.txt +0 -0
  370. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn100-1a.dat +0 -0
  371. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn103-1b.dat +0 -0
  372. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn105-1a.dat +0 -0
  373. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn106-1a.dat +0 -0
  374. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn107-1b.dat +0 -0
  375. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn109-2a.dat +0 -0
  376. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/2g_bin_magic/mn1/mn110-1b.dat +0 -0
  377. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01a-1.agm +0 -0
  378. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01a-1.irm +0 -0
  379. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01a-2.agm +0 -0
  380. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01a-2.irm +0 -0
  381. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01b-1.agm +0 -0
  382. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01b-1.irm +0 -0
  383. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01c-1.agm +0 -0
  384. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01c-1.irm +0 -0
  385. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01d-1.agm +0 -0
  386. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01d-1.irm +0 -0
  387. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01e-1.agm +0 -0
  388. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01e-1.irm +0 -0
  389. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01f-1.agm +0 -0
  390. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01f-1.irm +0 -0
  391. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01f-2.agm +0 -0
  392. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS01f-2.irm +0 -0
  393. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS02a-1.agm +0 -0
  394. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS02a-1.irm +0 -0
  395. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS02a-2.agm +0 -0
  396. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS02a-3.irm +0 -0
  397. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS02b-1.agm +0 -0
  398. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS02b-1.irm +0 -0
  399. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS02b-2.agm +0 -0
  400. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS02b-2.irm +0 -0
  401. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_directory/IS02c-1.agm +0 -0
  402. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_magic_example.agm +0 -0
  403. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/agm_magic/agm_magic_example.irm +0 -0
  404. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/bgc_magic/15HHA1-2A +0 -0
  405. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/bgc_magic/15JC4-1A +0 -0
  406. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/bgc_magic/96MT.05.01 +0 -0
  407. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/bgc_magic/96MT.05.01.txt +0 -0
  408. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/bgc_magic/BC0-3A +0 -0
  409. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/bgc_magic/BC0-3A.txt +0 -0
  410. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/bgc_magic/CA14-TA02.05'a +0 -0
  411. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/Craig_Jones_webpage_of_PMag_file_formats_CIT_file_format_source.html +0 -0
  412. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/Craig_Jones_webpage_of_PMag_file_formats_CIT_file_format_source_files/PaleoMag.gif +0 -0
  413. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/Craig_Jones_webpage_of_PMag_file_formats_CIT_file_format_source_files/PaleoMag_002.gif +0 -0
  414. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/Craig_Jones_webpage_of_PMag_file_formats_CIT_file_format_source_files/a-95.gif +0 -0
  415. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/Craig_Jones_webpage_of_PMag_file_formats_CIT_file_format_source_files/kappa.gif +0 -0
  416. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/Craig_Jones_webpage_of_PMag_file_formats_CIT_file_format_source_files/phi.gif +0 -0
  417. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B.LSQ +0 -0
  418. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B.sam +0 -0
  419. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B71 +0 -0
  420. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B72 +0 -0
  421. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B73 +0 -0
  422. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B74 +0 -0
  423. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B75 +0 -0
  424. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B76 +0 -0
  425. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B77 +0 -0
  426. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B78 +0 -0
  427. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/MIT/7325B/7325B79 +0 -0
  428. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/PI47-.sam +0 -0
  429. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/PI47-1a +0 -0
  430. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/PI47-2a +0 -0
  431. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/PI47-3a +0 -0
  432. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/PI47-4a +0 -0
  433. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/PI47-5a +0 -0
  434. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/PI47-6a +0 -0
  435. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/PI47-7a +0 -0
  436. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/PI47-8a +0 -0
  437. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/PI47-9a +0 -0
  438. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/locations.txt +0 -0
  439. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/measurements.txt +0 -0
  440. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/samples.txt +0 -0
  441. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/sites.txt +0 -0
  442. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/PI47/specimens.txt +0 -0
  443. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/README +0 -0
  444. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9001-1 +0 -0
  445. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9001-1.rmg +0 -0
  446. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9002-1 +0 -0
  447. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9002-1.rmg +0 -0
  448. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9003-1 +0 -0
  449. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9003-1.rmg +0 -0
  450. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9004-1 +0 -0
  451. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9004-1.rmg +0 -0
  452. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9005-1 +0 -0
  453. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9005-1.rmg +0 -0
  454. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9006-1 +0 -0
  455. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9006-1.rmg +0 -0
  456. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9007-1 +0 -0
  457. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9007-1.rmg +0 -0
  458. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9008-1 +0 -0
  459. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9008-1.rmg +0 -0
  460. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9009-1 +0 -0
  461. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/BL9009-1.rmg +0 -0
  462. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/bl9-1.sam +0 -0
  463. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/bl9001.dir +0 -0
  464. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/USGS/bl9-1/command +0 -0
  465. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/cit_magic/sample_formats.pdf +0 -0
  466. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/fla_magic/README +0 -0
  467. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/fla_magic/mejia04.pdf +0 -0
  468. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/fla_magic/pa_thermal.fla +0 -0
  469. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/fla_magic/pt_af.fla +0 -0
  470. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/generic_magic/generic_magic_example.txt +0 -0
  471. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/huji_magic/Massada_AF_HUJI_new_format.txt +0 -0
  472. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/huji_magic/Massada_AF_all_old_format.txt +0 -0
  473. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/huji_magic/README +0 -0
  474. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/huji_magic/magdelkrum.txt +0 -0
  475. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/huji_magic/magdelkrum_datafile.txt +0 -0
  476. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/iodp_jr6_magic/er_samples.txt +0 -0
  477. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/iodp_jr6_magic/test.jr6 +0 -0
  478. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/iodp_srm_magic/GCR_U1359_B_coresummary.csv +0 -0
  479. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/iodp_srm_magic/IODP_Janus_312_U1256.csv +0 -0
  480. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/iodp_srm_magic/IODP_LIMS_SRMdiscrete_344_1414A.csv +0 -0
  481. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/iodp_srm_magic/IODP_LIMS_SRMsection_344_1414A.csv +0 -0
  482. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/iodp_srm_magic/SRM_318_U1359_B_A.csv +0 -0
  483. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/iodp_srm_magic/samples_318_U1359_B.csv +0 -0
  484. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/AF.jr6 +0 -0
  485. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/AF.txt +0 -0
  486. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/AF_samples.txt +0 -0
  487. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/AF_sites.txt +0 -0
  488. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/AF_specimens.txt +0 -0
  489. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/AP12.jr6 +0 -0
  490. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/AP12.tmp +0 -0
  491. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/AP12.txt +0 -0
  492. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/README +0 -0
  493. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/SML01.JR6 +0 -0
  494. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/SML02.JR6 +0 -0
  495. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/SML03.JR6 +0 -0
  496. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/SML04.JR6 +0 -0
  497. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/SML05.JR6 +0 -0
  498. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/SML06.JR6 +0 -0
  499. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/SML07.JR6 +0 -0
  500. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/TRM.jr6 +0 -0
  501. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/TRM.txt +0 -0
  502. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/TRM_samples.txt +0 -0
  503. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/TRM_sites.txt +0 -0
  504. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/TRM_specimens.txt +0 -0
  505. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/locations.txt +0 -0
  506. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/measurements.txt +0 -0
  507. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/samples.txt +0 -0
  508. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/sites.txt +0 -0
  509. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/jr6_magic/specimens.txt +0 -0
  510. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/k15_magic/k15_example.dat +0 -0
  511. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/kly4s_magic/KLY4S_magic_example.dat +0 -0
  512. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/ldeo_magic/ldeo_magic_example.dat +0 -0
  513. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_C+/CHEV.livdb +0 -0
  514. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_C+/CHEV.livdb_different_delimiters +0 -0
  515. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_C+/measurements.txt +0 -0
  516. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_C+/samples.txt +0 -0
  517. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_C+/sites.txt +0 -0
  518. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_C+/specimens.txt +0 -0
  519. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_IZZI+andC++/NVPA.livdb +0 -0
  520. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_IZZI+andC++/NVPA.livdb_fifferent_delimiter +0 -0
  521. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_IZZI+andC++/measurements.txt +0 -0
  522. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_IZZI+andC++/samples.txt +0 -0
  523. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_IZZI+andC++/sites.txt +0 -0
  524. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_IZZI+andC++/specimens.txt +0 -0
  525. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_OT+/016-01.livdb_old_delimiters +0 -0
  526. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_OT+/017-03.livdb_old_delimiters +0 -0
  527. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_OT+/16-1.livdb +0 -0
  528. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_OT+/measurements.txt +0 -0
  529. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_OT+/samples.txt +0 -0
  530. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_OT+/sites.txt +0 -0
  531. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_OT+/specimens.txt +0 -0
  532. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_P/measurements.txt +0 -0
  533. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_P/perp.csv +0 -0
  534. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_P/samples.txt +0 -0
  535. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_P/sites.txt +0 -0
  536. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/MW_P/specimens.txt +0 -0
  537. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/TH_IZZI+/ATPI_Thellier.livdb +0 -0
  538. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/TH_IZZI+/measurements.txt +0 -0
  539. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/TH_IZZI+/samples.txt +0 -0
  540. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/TH_IZZI+/sites.txt +0 -0
  541. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/livdb_magic/TH_IZZI+/specimens.txt +0 -0
  542. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/mini_magic/Peru_rev1.txt +0 -0
  543. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/mini_magic/Peru_rev1_description.rtf +0 -0
  544. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/mst_magic/curie_example.dat +0 -0
  545. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/IPGP/0110C.PMD +0 -0
  546. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/IPGP/0210C.pmd +0 -0
  547. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/README +0 -0
  548. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0101a.pmd +0 -0
  549. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0102a.pmd +0 -0
  550. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0103a.pmd +0 -0
  551. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0104a.pmd +0 -0
  552. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0105a.pmd +0 -0
  553. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0106a.pmd +0 -0
  554. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0107a.pmd +0 -0
  555. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0108a.pmd +0 -0
  556. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0201a.pmd +0 -0
  557. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0202a.pmd +0 -0
  558. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0203a.pmd +0 -0
  559. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0204a.pmd +0 -0
  560. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0205a.pmd +0 -0
  561. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0206a.pmd +0 -0
  562. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0207a.pmd +0 -0
  563. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/PMD/ss0208c.pmd +0 -0
  564. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/pmd_magic/UCSC/ssDirAll.pmm +0 -0
  565. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/s_magic/s_magic_example.dat +0 -0
  566. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/SantaRosa2006.scz +0 -0
  567. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy01.pmm +0 -0
  568. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy02.pmm +0 -0
  569. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy03.pmm +0 -0
  570. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy04.pmm +0 -0
  571. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy05.pmm +0 -0
  572. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy06.pmm +0 -0
  573. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy07.pmm +0 -0
  574. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy08.pmm +0 -0
  575. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy09.pmm +0 -0
  576. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy10.pmm +0 -0
  577. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy11.pmm +0 -0
  578. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy12.pmm +0 -0
  579. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy13.pmm +0 -0
  580. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy13A.pmm +0 -0
  581. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy13B.pmm +0 -0
  582. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy14.pmm +0 -0
  583. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/cy15.pmm +0 -0
  584. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/scz_magic/CanyonCreek/santaRosa.pmm +0 -0
  585. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sio_magic/locations.txt +0 -0
  586. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sio_magic/samples.txt +0 -0
  587. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sio_magic/sio_af_example.dat +0 -0
  588. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sio_magic/sio_thermal_example.dat +0 -0
  589. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sio_magic/sites.txt +0 -0
  590. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sio_magic/specimens.txt +0 -0
  591. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sufar_asc_magic/measurements.txt +0 -0
  592. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sufar_asc_magic/samples.txt +0 -0
  593. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sufar_asc_magic/sites.txt +0 -0
  594. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sufar_asc_magic/specimens.txt +0 -0
  595. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/sufar_asc_magic/sufar4-asc_magic_example.txt +0 -0
  596. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/tdt_magic/Krasa_MGH1.tdt +0 -0
  597. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/tdt_magic/Krasa_MGH1_noAC.tdt +0 -0
  598. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/utrecht_magic/Utrecht_Example.af +0 -0
  599. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/utrecht_magic/locations.txt +0 -0
  600. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/utrecht_magic/measurements.txt +0 -0
  601. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/utrecht_magic/samples.txt +0 -0
  602. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/utrecht_magic/sites.txt +0 -0
  603. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_2_magic/utrecht_magic/specimens.txt +0 -0
  604. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_ages/magic_downloaded_rows.txt +0 -0
  605. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_samples/Iceland_orient.txt_Northern_Iceland.txt +0 -0
  606. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_samples/README +0 -0
  607. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_samples/convert_samples_example.dat +0 -0
  608. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/convert_samples/orient_Northern_Iceland.txt +0 -0
  609. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/Z35.sam.magic +0 -0
  610. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/Z35_er_samples.txt +0 -0
  611. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/Z35_er_sites.txt +0 -0
  612. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/Z35_er_specimens.txt +0 -0
  613. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/copy_er_ages.txt +0 -0
  614. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/er_ages.txt +0 -0
  615. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/er_locations.txt +0 -0
  616. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/er_samples.txt +0 -0
  617. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/er_sites.txt +0 -0
  618. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/er_specimens.txt +0 -0
  619. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/magic_measurements.txt +0 -0
  620. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/pmag_samples.txt +0 -0
  621. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/pmag_sites.txt +0 -0
  622. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/pmag_specimens.txt +0 -0
  623. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/copy_ErMagicBuilder/weird_er_ages.txt +0 -0
  624. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/ages.txt +0 -0
  625. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/core_depthplot_fixed.txt +0 -0
  626. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/er_ages.txt +0 -0
  627. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/er_citations.txt +0 -0
  628. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/er_locations.txt +0 -0
  629. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/er_samples.txt +0 -0
  630. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/er_sites.txt +0 -0
  631. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/er_specimens.txt +0 -0
  632. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/locations.txt +0 -0
  633. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/magic_measurements.txt +0 -0
  634. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/measurements.txt +0 -0
  635. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/pmag_results.txt +0 -0
  636. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/pmag_specimens.txt +0 -0
  637. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/samples.txt +0 -0
  638. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/sites.txt +0 -0
  639. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/core_depthplot/specimens.txt +0 -0
  640. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/curie/curie_example.dat +0 -0
  641. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dayplot_magic/dayplot_magic_example.dat +0 -0
  642. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dayplot_magic/specimens.txt +0 -0
  643. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_eq/di_eq_example.dat +0 -0
  644. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_eq/tmp +0 -0
  645. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_eq/tmp1 +0 -0
  646. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_geo/di_geo.out +0 -0
  647. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_geo/di_geo_example.dat +0 -0
  648. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_rot/di_rot.out +0 -0
  649. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_rot/di_rot_example.txt +0 -0
  650. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_rot/fishrot.out +0 -0
  651. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_tilt/di_tilt.out +0 -0
  652. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_tilt/di_tilt_example.dat +0 -0
  653. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/di_vgp/di_vgp_example.dat +0 -0
  654. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dipole_pinc/dipole_pinc_example.dat +0 -0
  655. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dipole_plat/dipole_plat_example.dat +0 -0
  656. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dir_cart/dir_cart_example.dat +0 -0
  657. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dmag_magic/contribution.txt +0 -0
  658. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dmag_magic/dmag_magic_example.dat +0 -0
  659. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dmag_magic/locations.txt +0 -0
  660. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dmag_magic/magic_contribution_16338.txt +0 -0
  661. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dmag_magic/measurements.txt +0 -0
  662. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dmag_magic/samples.txt +0 -0
  663. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dmag_magic/sites.txt +0 -0
  664. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/dmag_magic/specimens.txt +0 -0
  665. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eigs_s/eigs_s_example.dat +0 -0
  666. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eq_di/eq_di_example.dat +0 -0
  667. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eq_di/tmp +0 -0
  668. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eqarea/fishrot.out +0 -0
  669. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eqarea_ell/eqarea_ell_example.txt +0 -0
  670. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eqarea_ell/tk03.out +0 -0
  671. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eqarea_magic/measurements.txt +0 -0
  672. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eqarea_magic/pmag_results.txt +0 -0
  673. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eqarea_magic/samples.txt +0 -0
  674. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eqarea_magic/site_results.txt +0 -0
  675. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eqarea_magic/sites.txt +0 -0
  676. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/eqarea_magic/specimens.txt +0 -0
  677. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/etopo20/etopo20data.gz +0 -0
  678. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/etopo20/etopo20lats.gz +0 -0
  679. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/etopo20/etopo20lons.gz +0 -0
  680. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/find_EI/find_EI_example.dat +0 -0
  681. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/find_EI/tmp +0 -0
  682. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/fishqq/fishqq_example.txt +0 -0
  683. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/fishrot/fishrot.out +0 -0
  684. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/foldtest/foldtest_example.dat +0 -0
  685. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/foldtest_magic/er_citations.txt +0 -0
  686. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/foldtest_magic/er_locations.txt +0 -0
  687. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/foldtest_magic/er_sites.txt +0 -0
  688. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/foldtest_magic/magic_contribution_11087.txt +0 -0
  689. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/foldtest_magic/magic_methods.txt +0 -0
  690. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/foldtest_magic/pmag_results.txt +0 -0
  691. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/foldtest_magic/pmag_sites.txt +0 -0
  692. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/foldtest_magic/sites.txt +0 -0
  693. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/forc_diagram/.ipynb_checkpoints/forc_diagram-checkpoint.ipynb +0 -0
  694. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/forc_diagram/conventional_example.forc +0 -0
  695. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/forc_diagram/irforc_exmaple.irforc +0 -0
  696. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/gaussian/gauss.out +0 -0
  697. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/generic_magic/ATRM/README +0 -0
  698. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/generic_magic/ATRM/generic_ATRM.txt +0 -0
  699. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/generic_magic/ATRM/generic_ATRM.txt.magic +0 -0
  700. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/generic_magic/CR/README +0 -0
  701. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/generic_magic/CR/generic_CR.magic +0 -0
  702. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/generic_magic/CR/generic_CR.txt +0 -0
  703. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/generic_magic/Demag/README.txt +0 -0
  704. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/generic_magic/Demag/generic_demag.txt +0 -0
  705. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/generic_magic/PI/README +0 -0
  706. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/generic_magic/PI/generic_izzi.txt +0 -0
  707. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/geomagia/geomagia_sel.txt +0 -0
  708. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/gobing/gobing_example.txt +0 -0
  709. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/gofish/fishrot.out +0 -0
  710. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/gokent/gokent_example.txt +0 -0
  711. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/gokent/tk03.out +0 -0
  712. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/goprinc/goprinc_example.txt +0 -0
  713. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/goprinc/tk03.out +0 -0
  714. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/grab_magic_key/lats +0 -0
  715. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/hysteresis_magic/hysteresis_magic_example.dat +0 -0
  716. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/hysteresis_magic/measurements.txt +0 -0
  717. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/igrf/igrf.out +0 -0
  718. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/igrf/igrf_example.dat +0 -0
  719. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/incfish/incfish_example_di.dat +0 -0
  720. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/incfish/incfish_example_inc.dat +0 -0
  721. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/.ipynb_checkpoints/PmagPy_iodp_HOLE_template-checkpoint.ipynb +0 -0
  722. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/.ipynb_checkpoints/U999A-checkpoint.ipynb +0 -0
  723. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/Figures/U999A_1.pdf +0 -0
  724. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/Figures/U999A_anisotropy_xmastree.pdf +0 -0
  725. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/ProcessingPmagData.docx +0 -0
  726. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/Core Summary_18_5_2019.csv +0 -0
  727. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/JR6_data/spinner_17_5_2019.csv +0 -0
  728. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/KLY4S_data/ex-kappa_17_5_2019.csv +0 -0
  729. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/SRM_archive_data/srmsection_17_5_2019.csv +0 -0
  730. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/SRM_discrete_data/srmdiscrete_17_5_2019.csv +0 -0
  731. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/Section Summary_17_5_2019.csv +0 -0
  732. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U1999A_xray_disturbance.xlsx +0 -0
  733. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/Core Summary_17_5_2019.csv +0 -0
  734. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/dscr_measurements.txt +0 -0
  735. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/jr6_measurements.txt +0 -0
  736. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/kly4s_measurements.txt +0 -0
  737. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/kly4s_specimens.txt +0 -0
  738. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/lims_samples.txt +0 -0
  739. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/lims_sites.txt +0 -0
  740. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/lims_specimens.txt +0 -0
  741. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/locations.txt +0 -0
  742. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/samples.txt +0 -0
  743. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/sites.txt +0 -0
  744. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/specimens.txt +0 -0
  745. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/srm_arch_measurements.txt +0 -0
  746. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/srm_arch_samples.txt +0 -0
  747. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/srm_arch_sites.txt +0 -0
  748. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/srm_arch_specimens.txt +0 -0
  749. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_MagIC/srm_dscr_measurements.txt +0 -0
  750. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/U999A_disturbances.xlsx +0 -0
  751. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A/samples_17_5_2019.csv +0 -0
  752. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/iodp_magic/U999A.ipynb +0 -0
  753. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/irm_unmix/irm_unmix_example.dat +0 -0
  754. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/irm_unmix/irm_unmix_example_fit.csv +0 -0
  755. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/irmaq_magic/U1359A_IRM_coil2.txt +0 -0
  756. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/irmaq_magic/U1359A_IRM_coil3.txt +0 -0
  757. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/irmaq_magic/measurements.txt +0 -0
  758. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/k15_magic/k15_example.dat +0 -0
  759. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/k15_s/k15_example.dat +0 -0
  760. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/kly4s_magic/KLY4S_magic_example.dat +0 -0
  761. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lnp_magic/ages.txt +0 -0
  762. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lnp_magic/criteria.txt +0 -0
  763. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lnp_magic/locations.txt +0 -0
  764. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lnp_magic/measurements.txt +0 -0
  765. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lnp_magic/samples.txt +0 -0
  766. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lnp_magic/sites.txt +0 -0
  767. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lnp_magic/specimens.txt +0 -0
  768. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lnp_magic/zmab0001193tmp02.txt +0 -0
  769. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lowrie/lowrie_example.dat +0 -0
  770. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lowrie/lowrie_magic_example.dat +0 -0
  771. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lowrie_magic/lowrie_example.dat +0 -0
  772. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lowrie_magic/lowrie_magic_example.dat +0 -0
  773. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/lowrie_magic/measurements.txt +0 -0
  774. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/SrExample_AF.txt.magic +0 -0
  775. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/SrExample_AF_er_samples.txt +0 -0
  776. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/SrExample_thellier.txt.magic +0 -0
  777. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/SrExample_thellier_er_samples.txt +0 -0
  778. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/SrExample_thermal.txt.magic +0 -0
  779. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/SrExample_thermal_er_samples.txt +0 -0
  780. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/er_samples.txt +0 -0
  781. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/magic_measurements.txt +0 -0
  782. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/measurements.txt +0 -0
  783. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/samples.txt +0 -0
  784. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_gui/3_0/specimens.txt +0 -0
  785. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_select/AF_BFL_specimens.txt +0 -0
  786. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_select/AF_specimens.txt +0 -0
  787. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_select/pmag_specimens.txt +0 -0
  788. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/magic_select/specimens.txt +0 -0
  789. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/measurements_normalize/irm_measurements.txt +0 -0
  790. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/measurements_normalize/specimens_weight.txt +0 -0
  791. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/misc_files/er_specimens.txt +0 -0
  792. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/misc_files/pmag_specimens.txt +0 -0
  793. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/er_ages.txt +0 -0
  794. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/er_citations.txt +0 -0
  795. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/er_locations.txt +0 -0
  796. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/er_samples.txt +0 -0
  797. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/er_sites.txt +0 -0
  798. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/er_specimens.txt +0 -0
  799. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/magic_measurements.txt +0 -0
  800. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/magic_methods.txt +0 -0
  801. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/pmag_criteria.txt +0 -0
  802. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/pmag_results.txt +0 -0
  803. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/pmag_samples.txt +0 -0
  804. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/pmag_sites.txt +0 -0
  805. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/pmag_specimens.txt +0 -0
  806. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/specimens.txt +0 -0
  807. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/mk_redo/zmab0083201tmp03.txt +0 -0
  808. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/notebooks/Importing and using the 3.0 data model.ipynb +0 -0
  809. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/notebooks/Intro to MagIC Contributions.ipynb +0 -0
  810. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/notebooks/Intro to MagicDataFrames.ipynb +0 -0
  811. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/notebooks/Uploading contributions (with validations).ipynb +0 -0
  812. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/notebooks/Validate Quoted Strings.ipynb +0 -0
  813. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/notebooks/data_model_conversion.ipynb +0 -0
  814. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/notebooks/thellier_gui3_0_tester.ipynb +0 -0
  815. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/nrm_specimens_magic/magic_contribution_15143.txt +0 -0
  816. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/nrm_specimens_magic/nrm_specimens.txt +0 -0
  817. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/orientation_magic/orient_example.txt +0 -0
  818. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/parse_measurements/magic_measurements.txt +0 -0
  819. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pca/pca_example.txt +0 -0
  820. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pca/zeq_example.dat +0 -0
  821. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/plotXY/plotXY.png +0 -0
  822. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/plotXY/plotXY.svg +0 -0
  823. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/plotXY/plotxy_example.txt +0 -0
  824. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/plotXY/tmp +0 -0
  825. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/plot_cdf/gaussian.out +0 -0
  826. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/plot_map_pts/Map_PTS.png +0 -0
  827. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/plot_map_pts/uniform.out +0 -0
  828. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/plotdi_a/plotdi_a_example.dat +0 -0
  829. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pmag_results_extract/Directions.tex +0 -0
  830. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pmag_results_extract/Directions.txt +0 -0
  831. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pmag_results_extract/Intensities.tex +0 -0
  832. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pmag_results_extract/Intensities.txt +0 -0
  833. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pmag_results_extract/SiteNfo.tex +0 -0
  834. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pmag_results_extract/SiteNfo.txt +0 -0
  835. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pmag_results_extract/pmag_results.txt +0 -0
  836. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pmag_results_extract/pmag_specimens.txt +0 -0
  837. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/polemap_magic/locations.txt +0 -0
  838. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pt_rot/Map_PTS.pdf +0 -0
  839. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pt_rot/lon_lat +0 -0
  840. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pt_rot/nam_180-200.txt +0 -0
  841. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pt_rot/nam_panA.frp +0 -0
  842. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pt_rot/panA.out +0 -0
  843. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pt_rot/pt_rot.input +0 -0
  844. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pt_rot/pt_rot.out +0 -0
  845. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pt_rot/pt_rot_example.dat +0 -0
  846. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/pt_rot/pt_rot_panA.out +0 -0
  847. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/qqplot/gauss.out +0 -0
  848. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/quick_hyst/hysteresis_magic_example3.dat +0 -0
  849. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/quick_hyst2/hysteresis_magic_example.dat +0 -0
  850. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/remanence_anisotropy_magic/README +0 -0
  851. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/revtest/revtest_example.txt +0 -0
  852. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/revtest_magic/criteria.txt +0 -0
  853. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/revtest_magic/revtest_magic_example.txt +0 -0
  854. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/revtest_magic/sites.txt +0 -0
  855. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/s_eigs/s_eigs_example.dat +0 -0
  856. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/s_geo/s_geo_example.dat +0 -0
  857. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/s_hext/s_geo_example.dat +0 -0
  858. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/s_magic/s_magic_example.dat +0 -0
  859. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/s_magic/specimens.txt +0 -0
  860. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/s_tilt/s_tilt_example.dat +0 -0
  861. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/scalc/scalc_example.txt +0 -0
  862. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/scalc_magic/pmag_results.txt +0 -0
  863. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/scalc_magic/sites.txt +0 -0
  864. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/scalc_magic/vgp_lat +0 -0
  865. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/site_edit_magic/thellier_redo +0 -0
  866. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/site_edit_magic/zeq_redo +0 -0
  867. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/site_edit_magic/zmab0083201tmp03.txt +0 -0
  868. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/squish/squish_example.dat +0 -0
  869. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/stats/gaussian.out +0 -0
  870. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/strip_magic/pmag_results.txt +0 -0
  871. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/strip_magic/sites.txt +0 -0
  872. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/strip_magic/sites_with_vgps.txt +0 -0
  873. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/sundec/sundec_example.dat +0 -0
  874. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/__init__.py +0 -0
  875. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/empty_dir/blank.txt +0 -0
  876. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/methods/er_ages.txt +0 -0
  877. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/methods/er_locations.txt +0 -0
  878. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/methods/er_samples.txt +0 -0
  879. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/methods/er_sites.txt +0 -0
  880. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/methods/er_specimens.txt +0 -0
  881. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/methods/location_09.Oct.2015.txt +0 -0
  882. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/methods/location_14.Oct.2015.txt +0 -0
  883. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/methods/location_16.Aug.2015.txt +0 -0
  884. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/methods/location_16.Aug.2015_1.txt +0 -0
  885. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/methods/pmag_specimens.txt +0 -0
  886. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/__init__.py +0 -0
  887. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/er_ages.txt +0 -0
  888. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/er_locations.txt +0 -0
  889. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/er_samples.txt +0 -0
  890. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/er_samples_orient.txt +0 -0
  891. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/er_sites.txt +0 -0
  892. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/er_sites_orient.txt +0 -0
  893. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/er_specimens.txt +0 -0
  894. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/magic_measurements.txt +0 -0
  895. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/thellier_GUI.log +0 -0
  896. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/thellier_interpreter/thellier_interpreter.log +0 -0
  897. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/thellier_interpreter/thellier_interpreter_STDEV-OPT_redo +0 -0
  898. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/thellier_interpreter/thellier_interpreter_STDEV-OPT_samples.txt +0 -0
  899. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/thellier_interpreter/thellier_interpreter_STDEV-OPT_specimens.txt +0 -0
  900. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/thellier_interpreter/thellier_interpreter_all.txt +0 -0
  901. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project/thellier_interpreter/thellier_interpreter_specimens_bounds.txt +0 -0
  902. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project_with_errors/__init__.py +0 -0
  903. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project_with_errors/er_ages.txt +0 -0
  904. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project_with_errors/er_locations.txt +0 -0
  905. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project_with_errors/er_samples.txt +0 -0
  906. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project_with_errors/er_samples_orient.txt +0 -0
  907. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project_with_errors/er_sites.txt +0 -0
  908. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project_with_errors/er_sites_orient.txt +0 -0
  909. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project_with_errors/er_specimens.txt +0 -0
  910. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project_with_errors/magic_measurements.txt +0 -0
  911. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/my_project_with_errors/something.py +0 -0
  912. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/odp_magic/odp_magic_er_samples.txt +0 -0
  913. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/validation/Jack-Hills_19.Apr.2020_4.txt +0 -0
  914. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/validation/Jack-Hills_19.Apr.2020_5.txt +0 -0
  915. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/validation/er_locations.txt +0 -0
  916. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/validation/er_sites.txt +0 -0
  917. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/validation/location1_30.Dec.2015.txt +0 -0
  918. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/testing/validation/location1_30.Dec.2015_1.txt +0 -0
  919. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Megiddo_unpublished_example/er_ages.txt +0 -0
  920. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Megiddo_unpublished_example/er_locations.txt +0 -0
  921. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/SU1_example/er_ages.txt +0 -0
  922. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/SU1_example/er_locations.txt +0 -0
  923. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/SU1_example/optimizer_test_groups.txt +0 -0
  924. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/er_ages.txt +0 -0
  925. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/er_citations.txt +0 -0
  926. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/er_expeditions.txt +0 -0
  927. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/er_locations.txt +0 -0
  928. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/er_test_groups.txt +0 -0
  929. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/magic_methods.txt +0 -0
  930. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/pmag_results.txt +0 -0
  931. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/pmag_samples.txt +0 -0
  932. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/pmag_sites.txt +0 -0
  933. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/pmag_specimens.txt +0 -0
  934. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/thellier_interpreter/thellier_interpreter_STDEV-OPT_redo +0 -0
  935. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/thellier_interpreter/thellier_interpreter_STDEV-OPT_samples.txt +0 -0
  936. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/thellier_interpreter/thellier_interpreter_STDEV-OPT_specimens.txt +0 -0
  937. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/thellier_interpreter/thellier_interpreter_all.txt +0 -0
  938. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/thellier_interpreter/thellier_interpreter_specimens_bounds.txt +0 -0
  939. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/thellier_redo +0 -0
  940. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/thellier_specimens.txt +0 -0
  941. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/Tauxe_2006_example/zmab0094380tmp01.txt +0 -0
  942. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_GUI/thellier_GUI_full_manual_1_0.pdf +0 -0
  943. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_magic/measurements.txt +0 -0
  944. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/thellier_magic/zmab0100159tmp01.txt +0 -0
  945. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/tk03/tk03.out +0 -0
  946. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/tsunakawa_shaw/raw_data/mc120c-SA4.d +0 -0
  947. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/uniform/uniform.out +0 -0
  948. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/unsquish/unsquish_example.dat +0 -0
  949. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/er_ages.txt +0 -0
  950. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/er_citations.txt +0 -0
  951. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/er_locations.txt +0 -0
  952. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/magic_methods.txt +0 -0
  953. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/pmag_results.txt +0 -0
  954. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/pmag_samples.txt +0 -0
  955. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/pmag_sites.txt +0 -0
  956. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/pmag_specimens.txt +0 -0
  957. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/thellier_specimens.txt +0 -0
  958. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/upload.txt +0 -0
  959. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/upload_dos.txt +0 -0
  960. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/upload_magic/zeq_specimens.txt +0 -0
  961. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/vdm_b/vdm_b_example.dat +0 -0
  962. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/vector_mean/vector_mean_example.dat +0 -0
  963. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/vgp_di/vgp_di_example.dat +0 -0
  964. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/vgpmap_magic/pmag_results.txt +0 -0
  965. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/vgpmap_magic/sites.txt +0 -0
  966. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/watsons_f/watsons_f_example_file1.dat +0 -0
  967. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/watsons_f/watsons_f_example_file2.dat +0 -0
  968. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/xpeem_magic/Bryson2019_PVA01-r1.txt +0 -0
  969. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/xpeem_magic/Maurel2020_TeA01Comma-r1onL.txt +0 -0
  970. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/xpeem_magic/Maurel2020_TeA01TwoSpace-r1onL.txt +0 -0
  971. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/zeq/zeq_example.dat +0 -0
  972. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/zeq_magic/measurements.txt +0 -0
  973. {pmagpy-4.2.125.data → pmagpy-4.3.1.data}/data/data_files/zeq_magic/zmab0083201tmp03.txt +0 -0
  974. {pmagpy-4.2.125.dist-info → pmagpy-4.3.1.dist-info}/WHEEL +0 -0
  975. {pmagpy-4.2.125.dist-info → pmagpy-4.3.1.dist-info}/entry_points.txt +0 -0
  976. {pmagpy-4.2.125.dist-info → pmagpy-4.3.1.dist-info}/top_level.txt +0 -0
pmagpy/rockmag.py ADDED
@@ -0,0 +1,4513 @@
1
+ import pandas as pd
2
+ import numpy as np
3
+ import copy
4
+
5
+ from scipy.optimize import minimize, brent, least_squares, minimize_scalar
6
+ from scipy.signal import savgol_filter
7
+ from scipy.interpolate import UnivariateSpline
8
+
9
+ import matplotlib.pyplot as plt
10
+ import matplotlib.colors as colors
11
+ import matplotlib.patches as patches
12
+
13
+ try:
14
+ import ipywidgets as widgets
15
+ from ipywidgets import HBox, VBox, Output, Dropdown, RadioButtons, Checkbox, IntSlider, FloatSlider, IntRangeSlider
16
+ from IPython.display import HTML, display
17
+
18
+ except ImportError:
19
+ widgets = None
20
+ display = None
21
+
22
+ try:
23
+ from bokeh.plotting import figure, show
24
+ from bokeh.layouts import gridplot
25
+ from bokeh.models import HoverTool, Label
26
+ from bokeh.embed import components
27
+ from bokeh.palettes import Category10
28
+ from bokeh.models import ColumnDataSource
29
+ from bokeh.models.widgets import DataTable, TableColumn
30
+ from bokeh.layouts import column
31
+ _HAS_BOKEH = True
32
+ except ImportError:
33
+ _HAS_BOKEH = False
34
+ try:
35
+ from lmfit import Parameters, Model # for fitting
36
+ from lmfit.models import SkewedGaussianModel
37
+ except ImportError:
38
+ Parameters = None
39
+ Model = None
40
+ SkewedGaussianModel = None
41
+
42
+ try:
43
+ import statsmodels.api as sm
44
+ lowess = sm.nonparametric.lowess
45
+ except ImportError:
46
+ sm = None
47
+ lowess = None
48
+
49
+ mpl_to_bokeh_markers = {
50
+ ".": "dot",
51
+ ",": "dot",
52
+ "o": "circle",
53
+ "v": "inverted_triangle",
54
+ "^": "triangle",
55
+ "<": "triangle",
56
+ ">": "triangle",
57
+ "1": "triangle",
58
+ "2": "inverted_triangle",
59
+ "3": "triangle",
60
+ "4": "inverted_triangle",
61
+ "s": "square",
62
+ "p": "square",
63
+ "*": "asterisk",
64
+ "h": "hex",
65
+ "H": "hex",
66
+ "+": "plus",
67
+ "x": "x",
68
+ "X": "x",
69
+ "D": "diamond",
70
+ "d": "diamond",
71
+ "|": "dash",
72
+ "_": "dash",
73
+ }
74
+
75
+ # general I/O functions
76
+ # ------------------------------------------------------------------------------------------------------------------
77
+
78
+ def dict_in_native_python(d):
79
+ """Convert NumPy scalar values in a dict to native Python scalars.
80
+
81
+ Parameters
82
+ ----------
83
+ d : dict
84
+ Dictionary whose values may include NumPy scalar types
85
+ (e.g. np.float64, np.int64, np.bool_).
86
+
87
+ Returns
88
+ -------
89
+ dict
90
+ A new dictionary with the same keys as `d` but with any
91
+ NumPy scalar values replaced by their native Python
92
+ equivalents (float, int, bool). Non‐NumPy values are
93
+ left unchanged.
94
+ """
95
+ return {k: v.item() if isinstance(v, np.generic) else v for k, v in d.items()}
96
+
97
+
98
+ def interactive_specimen_selection(measurements):
99
+ """
100
+ Creates and displays a dropdown widget for selecting a specimen from a given
101
+ DataFrame of measurements.
102
+
103
+ Parameters
104
+ ----------
105
+ measurements : pd.DataFrame
106
+ The DataFrame containing measurement data with a column 'specimen'. It is
107
+ expected to have at least this column where 'specimen' identifies the
108
+ specimen name.
109
+
110
+ Returns
111
+ -------
112
+ ipywidgets.Dropdown
113
+ A dropdown widget allowing for the selection of a specimen. The initial
114
+ selection in the dropdown is set to the first specimen option.
115
+ """
116
+ # Extract unique specimen names from the measurements DataFrame
117
+ specimen_options = measurements['specimen'].unique().tolist()
118
+
119
+ # Set the initial selection to the first specimen option, if available
120
+ selected_specimen_name = specimen_options[0] if specimen_options else None
121
+
122
+ # Create a dropdown for specimen selection
123
+ specimen_dropdown = widgets.Dropdown(
124
+ options=specimen_options,
125
+ description='Specimen:',
126
+ value=selected_specimen_name
127
+ )
128
+
129
+ # Display the dropdown widget
130
+ display(specimen_dropdown)
131
+
132
+ return specimen_dropdown
133
+
134
+
135
+ def interactive_specimen_experiment_selection(measurements):
136
+ """
137
+ Creates interactive dropdown widgets for selecting a specimen and its associated
138
+ experiment from a measurements DataFrame.
139
+
140
+ Parameters
141
+ ----------
142
+ measurements : pd.DataFrame
143
+ DataFrame containing measurement data with at least two columns: 'specimen' and
144
+ 'experiment'. The 'specimen' column holds the specimen names while the 'experiment'
145
+ column holds the experiment identifiers associated with each specimen.
146
+
147
+ Returns
148
+ -------
149
+ tuple of ipywidgets.Dropdown
150
+ A tuple containing two dropdown widgets. The first widget allows for selecting a
151
+ specimen, and the second widget allows for selecting an experiment associated with
152
+ the chosen specimen. The experiment dropdown is dynamically updated based on the
153
+ specimen selection.
154
+ """
155
+ specimen_dropdown = widgets.Dropdown(
156
+ options = measurements['specimen'].unique(),
157
+ description = 'specimen:',
158
+ disabled = False,
159
+ )
160
+
161
+ experiment_dropdown = widgets.Dropdown(
162
+ options = measurements['experiment'].unique(),
163
+ description = 'Experiment:',
164
+ disabled = False,
165
+ )
166
+ # make sure to set the default value of the experiment dropdown to the first experiment in the specimen dropdown
167
+ experiment_dropdown.options = measurements[measurements['specimen']==specimen_dropdown.value]['experiment'].unique()
168
+
169
+ # make sure to update the experiment dropdown based on the specimen selected
170
+ def update_experiment(*args):
171
+ experiment_dropdown.options = measurements[measurements['specimen']==specimen_dropdown.value]['experiment'].unique()
172
+
173
+ specimen_dropdown.observe(update_experiment, 'value')
174
+
175
+ # display the dropdowns
176
+ display(specimen_dropdown, experiment_dropdown)
177
+
178
+ return specimen_dropdown, experiment_dropdown
179
+
180
+
181
+ def make_experiment_df(measurements):
182
+ """
183
+ Creates a DataFrame of unique experiments from the measurements DataFrame.
184
+
185
+ Args:
186
+ measurements (pd.DataFrame): The DataFrame containing measurement data with columns
187
+ 'specimen', 'method_codes', and 'experiment'.
188
+
189
+ Returns:
190
+ pd.DataFrame: A DataFrame containing unique combinations of 'specimen', 'method_codes',
191
+ and 'experiment'.
192
+ """
193
+ experiments = measurements.groupby(['specimen', 'method_codes', 'experiment']).size().reset_index().iloc[:, :3]
194
+ return experiments
195
+
196
+
197
+ def clean_out_na(dataframe):
198
+ """
199
+ Cleans a DataFrame by removing columns and rows that contain only NaN values.
200
+
201
+ Args:
202
+ dataframe (pd.DataFrame): The DataFrame to be cleaned.
203
+ Returns:
204
+ pd.DataFrame: A cleaned DataFrame with all-NaN columns and rows removed.
205
+ """
206
+ cleaned_df = dataframe.dropna(axis=1, how='all')
207
+ return cleaned_df
208
+
209
+
210
+ def ms_t_plot(
211
+ data,
212
+ temperature_column="meas_temp",
213
+ magnetization_column="magn_mass",
214
+ temp_unit="K",
215
+ interactive=False,
216
+ return_figure=False,
217
+ show_plot=True,
218
+ ):
219
+ """
220
+ Plot magnetization vs. temperature, either static or interactive,
221
+ with option to display in K or °C.
222
+
223
+ Parameters
224
+ ----------
225
+ data : pandas.DataFrame or array-like
226
+ Table or array containing temperature and magnetization.
227
+ temperature_column : str
228
+ Name of the temperature column in `data` (assumed in K).
229
+ magnetization_column : str
230
+ Name of the magnetization column in `data`.
231
+ temp_unit : {'K','C'}, default 'K'
232
+ Units for the x-axis: 'K' for Kelvin or 'C' for Celsius.
233
+ interactive : bool, default False
234
+ If True, use Bokeh for an interactive plot.
235
+ return_figure : bool, default False
236
+ If True, return the figure object(s).
237
+ show_plot : bool, default True
238
+ If True, display the plot.
239
+
240
+ Returns
241
+ -------
242
+ (fig, ax) or bokeh layout or None
243
+ Matplotlib Figure and Axes or Bokeh layout if `return_figure` is True;
244
+ otherwise None.
245
+ """
246
+ # extract data arrays
247
+ T = np.asarray(data[temperature_column], dtype=float)
248
+ M = np.asarray(data[magnetization_column], dtype=float)
249
+
250
+ # convert to Celsius if requested
251
+ if temp_unit == "C":
252
+ T = T - 273.15
253
+ x_label = "Temperature (°C)"
254
+ else:
255
+ x_label = "Temperature (K)"
256
+
257
+ if interactive:
258
+ tools = [HoverTool(tooltips=[("T", "@x"), ("M", "@y")]),
259
+ "pan,box_zoom,wheel_zoom,reset,save"]
260
+ p = figure(
261
+ title="M vs T",
262
+ x_axis_label=x_label,
263
+ y_axis_label="Magnetization",
264
+ tools=tools,
265
+ sizing_mode="stretch_width"
266
+ )
267
+ p.xaxis.axis_label_text_font_style = "normal"
268
+ p.yaxis.axis_label_text_font_style = "normal"
269
+ p.line(T, M, legend_label="M(T)", line_width=2)
270
+ p.scatter(T, M, size=6, alpha=0.6, legend_label="M(T)")
271
+ p.legend.location = "top_left"
272
+ p.legend.click_policy = "hide"
273
+
274
+ layout = gridplot([[p]], sizing_mode="stretch_width")
275
+ if show_plot:
276
+ show(layout)
277
+ if return_figure:
278
+ return layout
279
+ return None
280
+
281
+ fig, ax = plt.subplots()
282
+ ax.plot(T, M, "o-", label="M(T)")
283
+ ax.set_xlabel(x_label)
284
+ ax.set_ylabel("Magnetization")
285
+ ax.set_title("M vs T")
286
+ ax.legend()
287
+ ax.grid(True)
288
+ if show_plot:
289
+ plt.show()
290
+ if return_figure:
291
+ return fig, ax
292
+ return None
293
+
294
+
295
+ # MPMS functions
296
+ # ------------------------------------------------------------------------------------------------------------------
297
+
298
+ def extract_mpms_data_dc(df, specimen_name):
299
+ """
300
+ Extracts and separates MPMS data for a specified specimen from a DataFrame.
301
+
302
+ This function filters data for the given specimen and separates it based on
303
+ MagIC measurement method codes. It specifically extracts data corresponding to
304
+ 'LP-FC' (Field Cooled), 'LP-ZFC' (Zero Field Cooled), 'LP-CW-SIRM:LP-MC' (Room
305
+ Temperature SIRM measured upon cooling), and 'LP-CW-SIRM:LP-MW' (Room Temperature
306
+ SIRM measured upon Warming). For each method code, if the data is not available,
307
+ an empty DataFrame with the same columns as the specimen data is returned.
308
+
309
+ Parameters:
310
+ df (pd.DataFrame): DataFrame containing MPMS measurement data.
311
+ specimen_name (str): Name of the specimen to filter data for.
312
+
313
+ Returns:
314
+ tuple: A tuple containing four DataFrames:
315
+ - fc_data: Data filtered for 'LP-FC' method if available, otherwise an empty
316
+ DataFrame.
317
+ - zfc_data: Data filtered for 'LP-ZFC' method if available, otherwise an empty
318
+ DataFrame.
319
+ - rtsirm_cool_data: Data filtered for 'LP-CW-SIRM:LP-MC' method if available,
320
+ otherwise an empty DataFrame.
321
+ - rtsirm_warm_data: Data filtered for 'LP-CW-SIRM:LP-MW' method if available,
322
+ otherwise an empty DataFrame.
323
+ """
324
+ specimen_df = df[df["specimen"] == specimen_name]
325
+ empty_df = pd.DataFrame(columns=specimen_df.columns)
326
+
327
+ # If the 'method_codes' column is missing, return empty DataFrames.
328
+ if "method_codes" not in specimen_df.columns:
329
+ return empty_df, empty_df, empty_df, empty_df
330
+
331
+ # Filter for each method code if available, otherwise use empty DataFrame.
332
+ fc_data = (
333
+ specimen_df[specimen_df["method_codes"].str.contains("LP-FC", na=False)]
334
+ if specimen_df["method_codes"].str.contains("LP-FC", na=False).any()
335
+ else empty_df
336
+ )
337
+ zfc_data = (
338
+ specimen_df[specimen_df["method_codes"].str.contains("LP-ZFC", na=False)]
339
+ if specimen_df["method_codes"].str.contains("LP-ZFC", na=False).any()
340
+ else empty_df
341
+ )
342
+ rtsirm_cool_data = (
343
+ specimen_df[
344
+ specimen_df["method_codes"].str.contains("LP-CW-SIRM:LP-MC", na=False)
345
+ ]
346
+ if specimen_df["method_codes"].str.contains("LP-CW-SIRM:LP-MC", na=False).any()
347
+ else empty_df
348
+ )
349
+ rtsirm_warm_data = (
350
+ specimen_df[
351
+ specimen_df["method_codes"].str.contains("LP-CW-SIRM:LP-MW", na=False)
352
+ ]
353
+ if specimen_df["method_codes"].str.contains("LP-CW-SIRM:LP-MW", na=False).any()
354
+ else empty_df
355
+ )
356
+
357
+ return fc_data, zfc_data, rtsirm_cool_data, rtsirm_warm_data
358
+
359
+
360
+ def plot_mpms_dc(
361
+ fc_data=None,
362
+ zfc_data=None,
363
+ rtsirm_cool_data=None,
364
+ rtsirm_warm_data=None,
365
+ fc_color="#1f77b4",
366
+ zfc_color="#ff7f0e",
367
+ rtsirm_cool_color="#17becf",
368
+ rtsirm_warm_color="#d62728",
369
+ fc_marker="d",
370
+ zfc_marker="p",
371
+ rtsirm_cool_marker="s",
372
+ rtsirm_warm_marker="o",
373
+ symbol_size=4,
374
+ interactive=False,
375
+ plot_derivative=False,
376
+ return_figure=False,
377
+ show_plot=True,
378
+ drop_first=False,
379
+ drop_last=False,
380
+ ):
381
+ """
382
+ Plots MPMS DC data and optional derivatives, omitting empty panels.
383
+
384
+ Parameters:
385
+ fc_data (DataFrame or None): Field-cooled data.
386
+ zfc_data (DataFrame or None): Zero-field-cooled data.
387
+ rtsirm_cool_data (DataFrame or None): RTSIRM cooling data.
388
+ rtsirm_warm_data (DataFrame or None): RTSIRM warming data.
389
+ fc_color, zfc_color, rtsirm_cool_color, rtsirm_warm_color (str):
390
+ HEX color codes.
391
+ fc_marker, zfc_marker, rtsirm_cool_marker, rtsirm_warm_marker (str):
392
+ Matplotlib-style markers.
393
+ symbol_size (int): Marker size.
394
+ interactive (bool): If True, use Bokeh.
395
+ plot_derivative (bool): If True, plot dM/dT curves.
396
+ return_figure (bool): If True, return the figure/grid.
397
+ show_plot (bool): If True, display the plot.
398
+ drop_first (bool): If True, drop first row of each series.
399
+ drop_last (bool): If True, drop last row of each series.
400
+
401
+ Returns:
402
+ Bokeh grid or Matplotlib fig/axes tuple, or None.
403
+ """
404
+ def trim(df):
405
+ if df is None or df.empty:
406
+ return None
407
+ df = df.reset_index(drop=True)
408
+ if drop_first:
409
+ df = df.iloc[1:].reset_index(drop=True)
410
+ if drop_last:
411
+ df = df.iloc[:-1].reset_index(drop=True)
412
+ return df
413
+
414
+ fc = trim(fc_data)
415
+ zfc = trim(zfc_data)
416
+ rc = trim(rtsirm_cool_data)
417
+ rw = trim(rtsirm_warm_data)
418
+
419
+ if plot_derivative:
420
+ def deriv(df):
421
+ return None if df is None else thermomag_derivative(
422
+ df["meas_temp"], df["magn_mass"]
423
+ )
424
+ fcd = deriv(fc)
425
+ zfcd = deriv(zfc)
426
+ rcd = deriv(rc)
427
+ rwd = deriv(rw)
428
+
429
+ fc_zfc_present = (fc is not None) or (zfc is not None)
430
+ rtsirm_present = (rc is not None) or (rw is not None)
431
+
432
+ if interactive:
433
+ tools = [HoverTool(tooltips=[("T","@x"),("M","@y")]), "pan,box_zoom,reset,save"]
434
+ figs = []
435
+
436
+ if fc_zfc_present:
437
+ p0 = figure(title="LTSIRM Data", x_axis_label="Temperature (K)",
438
+ y_axis_label="Magnetization (Am2/kg)", tools=tools,
439
+ sizing_mode="stretch_width",height=400)
440
+ if fc is not None:
441
+ p0.line(fc["meas_temp"], fc["magn_mass"], color=fc_color, legend_label="FC")
442
+ p0.scatter(fc["meas_temp"], fc["magn_mass"], marker=mpl_to_bokeh_markers.get(fc_marker), size=symbol_size, color=fc_color)
443
+ if zfc is not None:
444
+ p0.line(zfc["meas_temp"], zfc["magn_mass"], color=zfc_color, legend_label="ZFC")
445
+ p0.scatter(zfc["meas_temp"], zfc["magn_mass"], marker=mpl_to_bokeh_markers.get(zfc_marker), size=symbol_size, color=zfc_color)
446
+ p0.legend.click_policy="hide"
447
+ p0.xaxis.axis_label_text_font_style = "normal"
448
+ p0.yaxis.axis_label_text_font_style = "normal"
449
+ figs.append(p0)
450
+
451
+ if rtsirm_present:
452
+ p1 = figure(title="RTSIRM Data", x_axis_label="Temperature (K)",
453
+ y_axis_label="Magnetization (Am2/kg)", tools=tools,
454
+ sizing_mode="stretch_width",height=400)
455
+ if rc is not None:
456
+ p1.line(rc["meas_temp"], rc["magn_mass"], color=rtsirm_cool_color, legend_label="cool")
457
+ p1.scatter(rc["meas_temp"], rc["magn_mass"], marker=mpl_to_bokeh_markers.get(rtsirm_cool_marker), size=symbol_size, color=rtsirm_cool_color)
458
+ if rw is not None:
459
+ p1.line(rw["meas_temp"], rw["magn_mass"], color=rtsirm_warm_color, legend_label="warm")
460
+ p1.scatter(rw["meas_temp"], rw["magn_mass"], marker=mpl_to_bokeh_markers.get(rtsirm_warm_marker), size=symbol_size, color=rtsirm_warm_color)
461
+ p1.legend.click_policy="hide"
462
+ p1.xaxis.axis_label_text_font_style = "normal"
463
+ p1.yaxis.axis_label_text_font_style = "normal"
464
+ figs.append(p1)
465
+
466
+ # separate derivative panels
467
+ if plot_derivative and fc_zfc_present:
468
+ p2 = figure(title="LTSIRM Derivative", x_axis_label="Temperature (K)",
469
+ y_axis_label="dM/dT", tools=tools,
470
+ sizing_mode="stretch_width",height=400)
471
+ if fcd is not None: p2.line(fcd["T"], fcd["dM_dT"], color=fc_color, legend_label="FC dM/dT")
472
+ if zfcd is not None: p2.line(zfcd["T"], zfcd["dM_dT"], color=zfc_color, legend_label="ZFC dM/dT")
473
+ p2.legend.click_policy="hide"
474
+ p2.xaxis.axis_label_text_font_style = "normal"
475
+ p2.yaxis.axis_label_text_font_style = "normal"
476
+ figs.append(p2)
477
+
478
+ if plot_derivative and rtsirm_present:
479
+ p3 = figure(title="RTSIRM Derivative", x_axis_label="Temperature (K)",
480
+ y_axis_label="dM/dT", tools=tools,
481
+ sizing_mode="stretch_width",height=400)
482
+ if rcd is not None: p3.line(rcd["T"], rcd["dM_dT"], color=rtsirm_cool_color, legend_label="cool dM/dT")
483
+ if rwd is not None: p3.line(rwd["T"], rwd["dM_dT"], color=rtsirm_warm_color, legend_label="warm dM/dT")
484
+ p3.legend.click_policy="hide"
485
+ p3.xaxis.axis_label_text_font_style = "normal"
486
+ p3.yaxis.axis_label_text_font_style = "normal"
487
+ figs.append(p3)
488
+
489
+ layout = gridplot([figs[:2], figs[2:]], sizing_mode="stretch_width")
490
+ if show_plot: show(layout)
491
+ return layout if return_figure else None
492
+
493
+ # Matplotlib branch
494
+ rows = 1 + (1 if plot_derivative else 0)
495
+ cols = 2
496
+ fig, axes = plt.subplots(rows, cols, figsize=(5*cols, 4*rows))
497
+ axes = axes.reshape(rows, cols)
498
+
499
+ if not fc_zfc_present:
500
+ axes[0,0].set_visible(False)
501
+ if plot_derivative: axes[1,0].set_visible(False)
502
+ if not rtsirm_present:
503
+ axes[0,1].set_visible(False)
504
+ if plot_derivative: axes[1,1].set_visible(False)
505
+
506
+ if fc_zfc_present:
507
+ ax = axes[0,0]
508
+ if fc is not None: ax.plot(fc["meas_temp"], fc["magn_mass"], color=fc_color, marker=fc_marker, label="FC")
509
+ if zfc is not None: ax.plot(zfc["meas_temp"], zfc["magn_mass"], color=zfc_color, marker=zfc_marker, label="ZFC")
510
+ ax.set_title("LTSIRM Data"); ax.set_xlabel("Temperature (K)"); ax.set_ylabel("Magnetization"); ax.legend(); ax.grid(True)
511
+
512
+ if rtsirm_present:
513
+ ax = axes[0,1]
514
+ if rc is not None: ax.plot(rc["meas_temp"], rc["magn_mass"], color=rtsirm_cool_color, marker=rtsirm_cool_marker, label="cool")
515
+ if rw is not None: ax.plot(rw["meas_temp"], rw["magn_mass"], color=rtsirm_warm_color, marker=rtsirm_warm_marker, label="warm")
516
+ ax.set_title("RTSIRM Data"); ax.set_xlabel("Temperature (K)"); ax.set_ylabel("Magnetization"); ax.legend(); ax.grid(True)
517
+
518
+ if plot_derivative and fc_zfc_present:
519
+ ax = axes[1,0]
520
+ if fcd is not None: ax.plot(fcd["T"], fcd["dM_dT"], color=fc_color, marker=fc_marker, label="FC dM/dT")
521
+ if zfcd is not None: ax.plot(zfcd["T"], zfcd["dM_dT"], color=zfc_color, marker=zfc_marker, label="ZFC dM/dT")
522
+ ax.set_title("LTSIRM Derivative"); ax.set_xlabel("Temperature (K)"); ax.set_ylabel("dM/dT"); ax.legend(); ax.grid(True)
523
+
524
+ if plot_derivative and rtsirm_present:
525
+ ax = axes[1,1]
526
+ if rcd is not None: ax.plot(rcd["T"], rcd["dM_dT"], color=rtsirm_cool_color, marker=rtsirm_cool_marker, label="cool dM/dT")
527
+ if rwd is not None: ax.plot(rwd["T"], rwd["dM_dT"], color=rtsirm_warm_color, marker=rtsirm_warm_marker, label="warm dM/dT")
528
+ ax.set_title("RTSIRM Derivative"); ax.set_xlabel("Temperature (K)"); ax.set_ylabel("dM/dT"); ax.legend(); ax.grid(True)
529
+
530
+ fig.tight_layout()
531
+ if show_plot: plt.show()
532
+ return fig if return_figure else None
533
+
534
+
535
+ def make_mpms_plots_dc(measurements):
536
+ """Create a UI to select specimen and plot MPMS data in Matplotlib or Bokeh.
537
+
538
+ Parameters:
539
+ measurements (pandas.DataFrame): DataFrame with 'specimen' and
540
+ 'method_codes'.
541
+ """
542
+ experiments = (
543
+ measurements.groupby(["specimen", "method_codes"])
544
+ .size()
545
+ .reset_index()
546
+ .iloc[:, :2]
547
+ )
548
+ filtered = experiments[
549
+ experiments["method_codes"].isin(
550
+ ["LP-FC", "LP-ZFC", "LP-CW-SIRM:LP-MC", "LP-CW-SIRM:LP-MW"]
551
+ )
552
+ ]
553
+ specimen_options = filtered["specimen"].unique().tolist()
554
+
555
+ specimen_dd = widgets.Dropdown(
556
+ options=specimen_options, description="Specimen:"
557
+ )
558
+ library_rb = widgets.RadioButtons(
559
+ options=["Bokeh", "Matplotlib"], description="Plot with:"
560
+ )
561
+ out = widgets.Output()
562
+
563
+ def _update(change=None):
564
+ spec = specimen_dd.value
565
+ fc_data, zfc_data, rts_c, rts_w = extract_mpms_data_dc(
566
+ measurements, spec
567
+ )
568
+
569
+ with out:
570
+ out.clear_output(wait=True)
571
+ if library_rb.value == "Matplotlib":
572
+ plot_mpms_dc(
573
+ fc_data,
574
+ zfc_data,
575
+ rts_c,
576
+ rts_w,
577
+ interactive=False,
578
+ plot_derivative=True,
579
+ show_plot=True,
580
+ )
581
+ else:
582
+ grid = plot_mpms_dc(
583
+ fc_data,
584
+ zfc_data,
585
+ rts_c,
586
+ rts_w,
587
+ interactive=True,
588
+ plot_derivative=True,
589
+ return_figure=True,
590
+ show_plot=False,
591
+ )
592
+ script, div = components(grid)
593
+ display(HTML(div + script))
594
+
595
+ specimen_dd.observe(_update, names="value")
596
+ library_rb.observe(_update, names="value")
597
+
598
+ ui = widgets.VBox([widgets.HBox([specimen_dd, library_rb]), out])
599
+ display(ui)
600
+ _update()
601
+
602
+ def calc_verwey_estimate(temps, mags,
603
+ t_range_background_min=50,
604
+ t_range_background_max=250,
605
+ excluded_t_min=75,
606
+ excluded_t_max=150,
607
+ poly_deg=3):
608
+ """
609
+ Estimate the Verwey transition temperature and remanence loss of magnetite from MPMS data.
610
+ Plots the magnetization data, background fit, and resulting magnetite curve, and
611
+ optionally the zero-crossing.
612
+
613
+ Parameters
614
+ ----------
615
+ temps : pd.Series
616
+ Series representing the temperatures at which magnetization measurements were taken.
617
+ mags : pd.Series
618
+ Series representing the magnetization measurements.
619
+ t_range_background_min : int or float, optional
620
+ Minimum temperature for the background fitting range. Default is 50.
621
+ t_range_background_max : int or float, optional
622
+ Maximum temperature for the background fitting range. Default is 250.
623
+ excluded_t_min : int or float, optional
624
+ Minimum temperature to exclude from the background fitting range. Default is 75.
625
+ excluded_t_max : int or float, optional
626
+ Maximum temperature to exclude from the background fitting range. Default is 150.
627
+ poly_deg : int, optional
628
+ Degree of the polynomial for background fitting. Default is 3.
629
+ """
630
+
631
+ temps.reset_index(drop=True, inplace=True)
632
+ mags.reset_index(drop=True, inplace=True)
633
+
634
+ dM_dT_df = thermomag_derivative(temps, mags)
635
+ temps_dM_dT = dM_dT_df['T']
636
+
637
+ temps_dM_dT_filtered_indices = [i for i in np.arange(len(temps_dM_dT)) if ((float(temps_dM_dT[i]) > float(t_range_background_min)) and (float(temps_dM_dT[i]) < float(excluded_t_min)) ) or ((float(temps_dM_dT[i]) > float(excluded_t_max)) and (float(temps_dM_dT[i]) < float(t_range_background_max)))]
638
+ temps_dM_dT_filtered = dM_dT_df['T'][temps_dM_dT_filtered_indices]
639
+ dM_dT_filtered = dM_dT_df['dM_dT'][temps_dM_dT_filtered_indices]
640
+
641
+ poly_background_fit = np.polyfit(temps_dM_dT_filtered, dM_dT_filtered, poly_deg)
642
+ dM_dT_filtered_polyfit = np.poly1d(poly_background_fit)(temps_dM_dT_filtered)
643
+
644
+ residuals = dM_dT_filtered - dM_dT_filtered_polyfit
645
+ ss_tot = np.sum((dM_dT_filtered - np.mean(dM_dT_filtered)) ** 2)
646
+ ss_res = np.sum(residuals ** 2)
647
+ r_squared = 1 - (ss_res / ss_tot)
648
+
649
+ temps_dM_dT_background_indices = [i for i in np.arange(len(temps_dM_dT)) if ((float(temps_dM_dT[i]) > float(t_range_background_min)) and (float(temps_dM_dT[i]) < float(t_range_background_max)))]
650
+ temps_dM_dT_background = dM_dT_df['T'][temps_dM_dT_background_indices]
651
+ temps_dM_dT_background.reset_index(drop=True, inplace=True)
652
+ dM_dT_background = dM_dT_df['dM_dT'][temps_dM_dT_background_indices]
653
+ dM_dT_polyfit = np.poly1d(poly_background_fit)(temps_dM_dT_background)
654
+
655
+ mgt_dM_dT = dM_dT_polyfit - dM_dT_background
656
+ mgt_dM_dT.reset_index(drop = True, inplace=True)
657
+
658
+ temps_background_indices = [i for i in np.arange(len(temps)) if ((float(temps[i]) > float(t_range_background_min)) and (float(temps[i]) < float(t_range_background_max)))]
659
+ temps_background = temps[temps_background_indices]
660
+
661
+ poly_func = np.poly1d(poly_background_fit)
662
+ background_curve = np.cumsum(poly_func(temps_background) * np.gradient(temps_background))
663
+
664
+ last_background_temp = temps_background.iloc[-1]
665
+ last_background_mag = background_curve[-1]
666
+ target_temp_index = np.argmin(np.abs(temps - last_background_temp))
667
+ mags_value = mags[target_temp_index]
668
+ background_curve_adjusted = background_curve + (mags_value - last_background_mag)
669
+
670
+ mags_background = mags[temps_background_indices]
671
+ mgt_curve = mags_background - background_curve_adjusted
672
+
673
+ verwey_estimate = calc_zero_crossing(temps_dM_dT_background, mgt_dM_dT)[-1]
674
+
675
+ remanence_loss = np.trapz(mgt_dM_dT, temps_dM_dT_background)
676
+
677
+ return dM_dT_df, verwey_estimate, remanence_loss, r_squared, temps_background, temps_dM_dT_background, mgt_dM_dT, dM_dT_polyfit, background_curve_adjusted, mgt_curve
678
+
679
+ def verwey_estimate(temps, mags,
680
+ t_range_background_min=50,
681
+ t_range_background_max=250,
682
+ excluded_t_min=75,
683
+ excluded_t_max=150,
684
+ poly_deg=3,
685
+ plot_zero_crossing=False,
686
+ plot_title=None,
687
+ measurement_marker='o', measurement_color='FireBrick',
688
+ background_fit_marker='s', background_fit_color='Teal',
689
+ magnetite_marker='d', magnetite_color='RoyalBlue',
690
+ verwey_marker='*', verwey_color='Pink',
691
+ verwey_size=10,
692
+ markersize=3.5):
693
+ """
694
+ Estimate the Verwey transition temperature and remanence loss of magnetite from MPMS data.
695
+ Plots the magnetization data, background fit, and resulting magnetite curve, and
696
+ optionally the zero-crossing.
697
+
698
+ Parameters
699
+ ----------
700
+ temps : pd.Series
701
+ Series representing the temperatures at which magnetization measurements were taken.
702
+ mags : pd.Series
703
+ Series representing the magnetization measurements.
704
+ t_range_background_min : int or float, optional
705
+ Minimum temperature for the background fitting range. Default is 50.
706
+ t_range_background_max : int or float, optional
707
+ Maximum temperature for the background fitting range. Default is 250.
708
+ excluded_t_min : int or float, optional
709
+ Minimum temperature to exclude from the background fitting range. Default is 75.
710
+ excluded_t_max : int or float, optional
711
+ Maximum temperature to exclude from the background fitting range. Default is 150.
712
+ poly_deg : int, optional
713
+ Degree of the polynomial for background fitting. Default is 3.
714
+ plot_zero_crossing : bool, optional
715
+ If True, plots the zero-crossing of the second derivative. Default is False.
716
+ plot_title : str, optional
717
+ Title for the plot. Default is None.
718
+ measurement_marker : str, optional
719
+ Marker symbol for measurement data. Default is 'o'.
720
+ measurement_color : str, optional
721
+ Color for measurement data. Default is 'black'.
722
+ background_fit_marker : str, optional
723
+ Marker symbol for background fit data. Default is 's'.
724
+ background_fit_color : str, optional
725
+ Color for background fit data. Default is 'C1'.
726
+ magnetite_marker : str, optional
727
+ Marker symbol for magnetite data. Default is 'd'.
728
+ magnetite_color : str, optional
729
+ Color for magnetite data. Default is 'C0'.
730
+ verwey_marker : str, optional
731
+ Marker symbol used to denote the Verwey transition estimate on the plot. Default is '*'.
732
+ verwey_color : str, optional
733
+ Color of the marker representing the Verwey transition estimate. Default is 'Pink'.
734
+ verwey_size : int, optional
735
+ Size of the marker used for the Verwey transition estimate. Default is 10.
736
+ markersize : float, optional
737
+ Size of the markers. Default is 3.5.
738
+
739
+ Returns
740
+ -------
741
+ verwey_estimate : float
742
+ Estimated Verwey transition temperature.
743
+ remanence_loss : float
744
+ Estimated remanence loss.
745
+
746
+ Examples
747
+ --------
748
+ >>> temps = pd.Series([10, 20, 30, 40, 50, 60, 70, 80, 90, 100])
749
+ >>> mags = pd.Series([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
750
+ >>> verwey_estimate(temps, mags)
751
+ (75.0, 0.5)
752
+ """
753
+ dM_dT_df, verwey_estimate, remanence_loss, r_squared, temps_background, temps_dM_dT_background, mgt_dM_dT, dM_dT_polyfit, background_curve_adjusted, mgt_curve = calc_verwey_estimate(temps, mags,
754
+ t_range_background_min=t_range_background_min,
755
+ t_range_background_max=t_range_background_max,
756
+ excluded_t_min=excluded_t_min,
757
+ excluded_t_max=excluded_t_max,
758
+ poly_deg=poly_deg)
759
+
760
+ fig = plt.figure(figsize=(12,5))
761
+ ax0 = fig.add_subplot(1,2,1)
762
+ ax0.plot(temps, mags, marker=measurement_marker, markersize=markersize, color=measurement_color,
763
+ label='measurement')
764
+ ax0.plot(temps_background, background_curve_adjusted, marker=background_fit_marker, markersize=markersize, color=background_fit_color,
765
+ label='background fit')
766
+ ax0.plot(temps_background, mgt_curve, marker=magnetite_marker, markersize=markersize, color=magnetite_color,
767
+ label='magnetite (meas. minus background)')
768
+ verwey_y_value = np.interp(verwey_estimate, temps_background, mgt_curve)
769
+ ax0.plot(verwey_estimate, verwey_y_value, verwey_marker, color=verwey_color, markersize=verwey_size,
770
+ markeredgecolor='black', markeredgewidth=1,
771
+ label='Verwey estimate' + ' (' + str(round(verwey_estimate,1)) + ' K)')
772
+ ax0.set_ylabel('M (Am$^2$/kg)')
773
+ ax0.set_xlabel('T (K)')
774
+ ax0.legend(loc='upper right')
775
+ ax0.grid(True)
776
+ ax0.ticklabel_format(axis='y', style='scientific', scilimits=(0,0))
777
+ if plot_title is not None:
778
+ ax0.set_title(plot_title)
779
+
780
+ ax1 = fig.add_subplot(1,2,2)
781
+ ax1.plot(dM_dT_df['T'], dM_dT_df['dM_dT'], marker=measurement_marker, markersize=markersize, color=measurement_color,
782
+ label='measurement')
783
+ ax1.plot(temps_dM_dT_background, dM_dT_polyfit, marker=background_fit_marker, markersize=markersize, color=background_fit_color,
784
+ label='background fit'+ ' (r$^2$ = ' + str(round(r_squared,3)) + ')' )
785
+ ax1.plot(temps_dM_dT_background, mgt_dM_dT, marker=magnetite_marker, markersize=markersize, color=magnetite_color,
786
+ label='magnetite (background fit minus measurement)')
787
+ verwey_y_value = np.interp(verwey_estimate, temps_dM_dT_background, mgt_dM_dT)
788
+ ax1.plot(verwey_estimate, verwey_y_value, verwey_marker, color=verwey_color, markersize=verwey_size,
789
+ markeredgecolor='black', markeredgewidth=1,
790
+ label='Verwey estimate' + ' (' + str(round(verwey_estimate,1)) + ' K)')
791
+ rectangle = patches.Rectangle((excluded_t_min, ax1.get_ylim()[0]), excluded_t_max - excluded_t_min,
792
+ ax1.get_ylim()[1] - ax1.get_ylim()[0],
793
+ linewidth=0, edgecolor=None, facecolor='gray',
794
+ alpha=0.3)
795
+ ax1.add_patch(rectangle)
796
+ rect_legend_patch = patches.Patch(color='gray', alpha=0.3, label='excluded from background fit')
797
+ handles, labels = ax1.get_legend_handles_labels()
798
+ handles.append(rect_legend_patch) # Add the rectangle legend patch
799
+ ax1.legend(handles=handles, loc='lower right')
800
+ ax1.set_ylabel('dM/dT (Am$^2$/kg/K)')
801
+ ax1.set_xlabel('T (K)')
802
+ ax1.grid(True)
803
+ ax1.ticklabel_format(axis='y', style='scientific', scilimits=(0,0))
804
+ if plot_title is not None:
805
+ ax1.set_title(plot_title)
806
+ #plt.show()
807
+
808
+ return verwey_estimate, remanence_loss
809
+
810
+
811
+ def interactive_verwey_estimate(measurements, specimen_dropdown, method_dropdown):
812
+
813
+ selected_specimen_name = specimen_dropdown.value
814
+ selected_method = method_dropdown.value
815
+
816
+ fc_data, zfc_data, rtsirm_cool_data, rtsirm_warm_data = extract_mpms_data_dc(measurements, selected_specimen_name)
817
+ if selected_method == 'LP-FC':
818
+ temps = fc_data['meas_temp']
819
+ mags = fc_data['magn_mass']
820
+ elif selected_method == 'LP-ZFC':
821
+ temps = zfc_data['meas_temp']
822
+ mags = zfc_data['magn_mass']
823
+ temps.reset_index(drop=True, inplace=True)
824
+ mags.reset_index(drop=True, inplace=True)
825
+ dM_dT_df = thermomag_derivative(temps, mags)
826
+ temps_dM_dT = dM_dT_df['T']
827
+
828
+ # Determine a fixed width for the descriptions to align the sliders
829
+ description_width = '250px' # Adjust this based on the longest description
830
+ slider_total_width = '600px' # Total width of the slider widget including the description
831
+
832
+ description_style = {'description_width': description_width}
833
+ slider_layout = widgets.Layout(width=slider_total_width) # Set the total width of the slider widget
834
+
835
+ # Update sliders to use IntRangeSlider
836
+ background_temp_range_slider = IntRangeSlider(
837
+ value=[60, 250], min=0, max=300, step=1,
838
+ description='Background Temperature Range (K):',
839
+ layout=slider_layout, style=description_style
840
+ )
841
+
842
+ excluded_temp_range_slider = IntRangeSlider(
843
+ value=[75, 150], min=0, max=300, step=1,
844
+ description='Excluded Temperature Range (K):',
845
+ layout=slider_layout, style=description_style
846
+ )
847
+
848
+ poly_deg_slider = IntSlider(
849
+ value=3, min=1, max=5, step=1,
850
+ description='Background Fit Polynomial Degree:',
851
+ layout=slider_layout, style=description_style
852
+ )
853
+
854
+ # Function to reset sliders to initial values
855
+ def reset_sliders(b):
856
+ background_temp_range_slider.value = (60, 250)
857
+ excluded_temp_range_slider.value = (75, 150)
858
+ poly_deg_slider.value = 3
859
+
860
+ # Create reset button
861
+ reset_button = widgets.Button(description="Reset to Default Values", layout=widgets.Layout(width='200px'))
862
+ reset_button.on_click(reset_sliders)
863
+
864
+ # title_label = widgets.Label(value='Adjust Parameters for ' + selected_specimen_name + ' ' + selected_method + ' fit')
865
+
866
+ # Add the reset button to the UI
867
+ ui = widgets.VBox([
868
+ # title_label,
869
+ background_temp_range_slider,
870
+ excluded_temp_range_slider,
871
+ poly_deg_slider,
872
+ reset_button
873
+ ])
874
+
875
+ display(ui)
876
+
877
+ fig, ax = plt.subplots(ncols=2, nrows=1, figsize=(12, 6))
878
+ fig.canvas.header_visible = False
879
+
880
+ def update_plot(*args):
881
+ ax0, ax1 = ax
882
+ ax0.clear()
883
+ ax1.clear()
884
+ # get values from sliders
885
+ t_range_background_min = background_temp_range_slider.value[0]
886
+ t_range_background_max = background_temp_range_slider.value[1]
887
+ excluded_t_min = excluded_temp_range_slider.value[0]
888
+ excluded_t_max = excluded_temp_range_slider.value[1]
889
+ poly_deg = poly_deg_slider.value
890
+
891
+ # recalculate verwey estimate
892
+ dM_dT_df, verwey_estimate, remanence_loss, r_squared, temps_background, temps_dM_dT_background, mgt_dM_dT, dM_dT_polyfit, background_curve_adjusted, mgt_curve = calc_verwey_estimate(temps, mags,
893
+ t_range_background_min=t_range_background_min,
894
+ t_range_background_max=t_range_background_max,
895
+ excluded_t_min=excluded_t_min,
896
+ excluded_t_max=excluded_t_max,
897
+ poly_deg=poly_deg)
898
+
899
+ ax0.plot(temps, mags, marker='o', markersize=3.5, color='FireBrick',
900
+ label='measurement')
901
+ ax0.plot(temps_background, background_curve_adjusted, marker='s', markersize=3.5, color='Teal',
902
+ label='background fit')
903
+ ax0.plot(temps_background, mgt_curve, marker='d', markersize=3.5, color='RoyalBlue',
904
+ label='magnetite (meas. minus background)')
905
+ verwey_y_value = np.interp(verwey_estimate, temps_background, mgt_curve)
906
+ ax0.plot(verwey_estimate, verwey_y_value, '*', color='Pink', markersize=10,
907
+ markeredgecolor='black', markeredgewidth=1,
908
+ label='Verwey estimate' + ' (' + str(round(verwey_estimate,1)) + ' K)')
909
+ ax0.set_ylabel('M (Am$^2$/kg)')
910
+ ax0.set_xlabel('T (K)')
911
+ ax0.legend(loc='upper right')
912
+ ax0.grid(True)
913
+ ax0.ticklabel_format(axis='y', style='scientific', scilimits=(0,0))
914
+
915
+ ax1.plot(dM_dT_df['T'], dM_dT_df['dM_dT'], marker='o', markersize=3.5, color='FireBrick',
916
+ label='measurement')
917
+ ax1.plot(temps_dM_dT_background, dM_dT_polyfit, marker='s', markersize=3.5, color='Teal',
918
+ label='background fit'+ ' (r$^2$ = ' + str(round(r_squared,3)) + ')' )
919
+ ax1.plot(temps_dM_dT_background, mgt_dM_dT, marker='d', markersize=3.5, color='RoyalBlue',
920
+ label='magnetite (background fit minus measurement)')
921
+ verwey_y_value = np.interp(verwey_estimate, temps_dM_dT_background, mgt_dM_dT)
922
+ ax1.plot(verwey_estimate, verwey_y_value, '*', color='Pink', markersize=10,
923
+ markeredgecolor='black', markeredgewidth=1,
924
+ label='Verwey estimate' + ' (' + str(round(verwey_estimate,1)) + ' K)')
925
+ rectangle = patches.Rectangle((excluded_t_min, ax1.get_ylim()[0]), excluded_t_max - excluded_t_min,
926
+ ax1.get_ylim()[1] - ax1.get_ylim()[0],
927
+ linewidth=0, edgecolor=None, facecolor='gray',
928
+ alpha=0.3)
929
+ ax1.add_patch(rectangle)
930
+ rect_legend_patch = patches.Patch(color='gray', alpha=0.3, label='excluded from background fit')
931
+ handles, labels = ax1.get_legend_handles_labels()
932
+ handles.append(rect_legend_patch) # Add the rectangle legend patch
933
+ ax1.legend(handles=handles, loc='lower right')
934
+ ax1.set_ylabel('dM/dT (Am$^2$/kg/K)')
935
+ ax1.set_xlabel('T (K)')
936
+ ax1.grid(True)
937
+ ax1.ticklabel_format(axis='y', style='scientific', scilimits=(0,0))
938
+
939
+
940
+ # Attach observers
941
+ background_temp_range_slider.observe(update_plot, names='value')
942
+ excluded_temp_range_slider.observe(update_plot, names='value')
943
+ poly_deg_slider.observe(update_plot, names='value')
944
+ reset_button.on_click(update_plot)
945
+
946
+ update_plot()
947
+
948
+
949
+ def interactive_verwey_specimen_method_selection(measurements):
950
+ """
951
+ Creates and displays dropdown widgets for selecting a specimen and the corresponding
952
+ available method codes (specifically 'LP-FC' and 'LP-ZFC') from a given DataFrame of measurements.
953
+ This function filters the measurements to include only those with desired method codes,
954
+ dynamically updates the method dropdown based on the selected specimen, and organizes
955
+ the dropdowns vertically in the UI.
956
+
957
+ Parameters:
958
+ measurements (pd.DataFrame): The DataFrame containing measurement data with columns
959
+ 'specimen' and 'method_codes'. It is expected to have
960
+ at least these two columns where 'specimen' identifies
961
+ the specimen name and 'method_codes' contains the method
962
+ codes associated with each measurement.
963
+
964
+ Returns:
965
+ tuple: A tuple containing the specimen dropdown widget (`ipywidgets.Dropdown`)
966
+ and the method dropdown widget (`ipywidgets.Dropdown`). The specimen dropdown
967
+ allows for the selection of a specimen, and the method dropdown updates to
968
+ display only the methods available for the selected specimen. The initial
969
+ selection in the specimen dropdown is set to the first specimen option.
970
+
971
+ Note:
972
+ The method dropdown is initially populated based on the methods available for the
973
+ first selected specimen. The available methods are specifically filtered for 'LP-FC'
974
+ and 'LP-ZFC' codes.
975
+ """
976
+ # Filter to get specimens with desired method codes
977
+ experiments = measurements.groupby(['specimen', 'method_codes']).size().reset_index().iloc[:, :2]
978
+ filtered_experiments = experiments[experiments['method_codes'].isin(['LP-FC', 'LP-ZFC'])]
979
+ specimen_options = filtered_experiments['specimen'].unique().tolist()
980
+
981
+ selected_specimen_name = specimen_options[0] # Example initial selection
982
+
983
+ # Dropdown for specimen selection
984
+ specimen_dropdown = widgets.Dropdown(
985
+ options=specimen_options,
986
+ description='Specimen:',
987
+ value=selected_specimen_name
988
+ )
989
+
990
+ # Method dropdown initialized with placeholder options
991
+ method_dropdown = widgets.Dropdown(
992
+ description='Method:',
993
+ )
994
+
995
+ # Function to update method options based on selected specimen
996
+ def update_method_options(change):
997
+ selected_specimen = change['new']
998
+ # Filter experiments to get methods available for the selected specimen
999
+ available_methods = filtered_experiments[filtered_experiments['specimen'] == selected_specimen]['method_codes'].tolist()
1000
+ # Update method dropdown options and reset its value
1001
+ method_dropdown.options = available_methods
1002
+ if available_methods:
1003
+ method_dropdown.value = available_methods[0]
1004
+ else:
1005
+ method_dropdown.value = None
1006
+
1007
+ # Register the update function with specimen dropdown
1008
+ specimen_dropdown.observe(update_method_options, names='value')
1009
+
1010
+ # Initially populate method dropdown based on the first selected specimen
1011
+ update_method_options({'new': selected_specimen_name})
1012
+
1013
+ # Creating a UI layout using VBox to organize the dropdowns vertically
1014
+ ui_layout = widgets.VBox([specimen_dropdown, method_dropdown])
1015
+
1016
+ # Display the UI layout
1017
+ display(ui_layout)
1018
+
1019
+ return specimen_dropdown, method_dropdown
1020
+
1021
+
1022
+ def verwey_estimate_multiple_specimens(specimens_with_params, measurements):
1023
+ """
1024
+ Analyze Verwey transitions for a list of specimens with unique parameters.
1025
+
1026
+ This function uses either field-cooled (FC) or zero-field cooled (ZFC) data depending on the
1027
+ method_codes provided in each specimen's parameters. If "LP-FC" is found in the colon-delimited
1028
+ method_codes, FC data is used; if "LP-ZFC" is found, ZFC data is used.
1029
+
1030
+ Parameters
1031
+ ----------
1032
+ specimens_with_params : list of dict
1033
+ List of specimen dictionaries. Each dictionary should contain:
1034
+ - 'specimen_name' : str
1035
+ The name of the specimen.
1036
+ - 'params' : dict
1037
+ Dictionary containing:
1038
+ - 't_range_background_min' : int or float
1039
+ - 't_range_background_max' : int or float
1040
+ - 'excluded_t_min' : int or float
1041
+ - 'excluded_t_max' : int or float
1042
+ - 'poly_deg' : int
1043
+ - 'method_codes' : str
1044
+ Colon-delimited string that must include either "LP-FC" or "LP-ZFC".
1045
+
1046
+ measurements : object
1047
+ Measurements dataframe in MagIC format.
1048
+
1049
+ Returns
1050
+ -------
1051
+ pd.DataFrame
1052
+ DataFrame containing the Verwey transition estimates and the input parameters for each specimen.
1053
+ Columns include:
1054
+ - 'specimen'
1055
+ - 'critical_temp'
1056
+ - 'critical_temp_type'
1057
+ - 'remanence_loss'
1058
+ plus the additional parameters from the input.
1059
+
1060
+ Raises
1061
+ ------
1062
+ ValueError
1063
+ If neither "LP-FC" nor "LP-ZFC" is found in the method_codes for a specimen.
1064
+ Exception
1065
+ Propagates exceptions raised during data extraction or analysis.
1066
+ """
1067
+ verwey_estimates_and_params = []
1068
+
1069
+ # Process each specimen with its unique parameters
1070
+ for specimen in specimens_with_params:
1071
+ specimen_name = specimen['specimen_name']
1072
+ params = specimen['params']
1073
+
1074
+ # Extract method codes and determine whether to use FC or ZFC data
1075
+ method_codes = params.get('method_codes', '')
1076
+ codes = method_codes.split(':')
1077
+
1078
+ # Extract the measurement data for the specimen
1079
+ fc_data, zfc_data, rtsirm_cool_data, rtsirm_warm_data = extract_mpms_data_dc(measurements, specimen_name)
1080
+
1081
+ if "LP-FC" in codes:
1082
+ data = fc_data
1083
+ elif "LP-ZFC" in codes:
1084
+ data = zfc_data
1085
+ else:
1086
+ raise ValueError(f"Specimen {specimen_name} does not have a valid method code ('LP-FC' or 'LP-ZFC').")
1087
+
1088
+ temps = data['meas_temp']
1089
+ mags = data['magn_mass']
1090
+
1091
+ # Estimate Verwey transition using selected data
1092
+ vt_estimate, rem_loss = verwey_estimate(
1093
+ temps,
1094
+ mags,
1095
+ t_range_background_min=params['t_range_background_min'],
1096
+ t_range_background_max=params['t_range_background_max'],
1097
+ excluded_t_min=params['excluded_t_min'],
1098
+ excluded_t_max=params['excluded_t_max'],
1099
+ poly_deg=params['poly_deg'],
1100
+ plot_title=specimen_name
1101
+ )
1102
+
1103
+ record = {
1104
+ 'specimen': specimen_name,
1105
+ 'critical_temp': vt_estimate,
1106
+ 'critical_temp_type': 'Verwey',
1107
+ 'remanence_loss': rem_loss
1108
+ }
1109
+ record.update(params)
1110
+ verwey_estimates_and_params.append(record)
1111
+
1112
+ return pd.DataFrame(verwey_estimates_and_params)
1113
+
1114
+
1115
+ def thermomag_derivative(temps, mags, drop_first=False, drop_last=False):
1116
+ """
1117
+ Calculates the derivative of magnetization with respect to temperature and optionally
1118
+ drops the data corresponding to the highest and/or lowest temperature.
1119
+
1120
+ Parameters:
1121
+ temps (pd.Series): A pandas Series representing the temperatures at which
1122
+ magnetization measurements were taken.
1123
+ mags (pd.Series): A pandas Series representing the magnetization measurements.
1124
+ drop_last (bool): Optional; whether to drop the last row from the resulting
1125
+ DataFrame. Defaults to False. Useful when there is an
1126
+ artifact associated with the end of the experiment.
1127
+ drop_first (bool): Optional; whether to drop the first row from the resulting
1128
+ DataFrame. Defaults to False. Useful when there is an
1129
+ artifact associated with the start of the experiment.
1130
+
1131
+ Returns:
1132
+ pd.DataFrame: A pandas DataFrame with two columns:
1133
+ 'T' - Midpoint temperatures for each temperature interval.
1134
+ 'dM_dT' - The derivative of magnetization with respect to temperature.
1135
+ If drop_last is True, the last temperature point is excluded.
1136
+ If drop_first is True, the first temperature point is excluded.
1137
+ """
1138
+ temps = temps.reset_index(drop=True)
1139
+ mags = mags.reset_index(drop=True)
1140
+
1141
+ dT = temps.diff()
1142
+ dM = mags.diff()
1143
+ dM_dT = dM / dT
1144
+ dM_dT_real = dM_dT[1:]
1145
+ dM_dT_real.reset_index(drop=True, inplace=True)
1146
+
1147
+ temps_dM_dT = [temps[n] + dT[n + 1] / 2 for n in range(len(temps) - 1)]
1148
+ temps_dM_dT = pd.Series(temps_dM_dT)
1149
+
1150
+ dM_dT_df = pd.DataFrame({'T': temps_dM_dT, 'dM_dT': dM_dT_real})
1151
+
1152
+ # Drop the last row if specified
1153
+ if drop_last:
1154
+ dM_dT_df = dM_dT_df[:-1].reset_index(drop=True)
1155
+
1156
+ # Drop the first row if specified
1157
+ if drop_first:
1158
+ dM_dT_df = dM_dT_df[1:].reset_index(drop=True)
1159
+
1160
+ return dM_dT_df
1161
+
1162
+
1163
+ def calc_zero_crossing(dM_dT_temps, dM_dT):
1164
+ """
1165
+ Calculate the temperature at which the second derivative of magnetization with respect to
1166
+ temperature crosses zero. This value provides an estimate of the peak of the derivative
1167
+ curve that is more precise than the maximum value.
1168
+
1169
+ The function computes the second derivative of magnetization (dM/dT) with respect to
1170
+ temperature, identifies the nearest points around the maximum value of the derivative,
1171
+ and then calculates the temperature at which this second derivative crosses zero using
1172
+ linear interpolation.
1173
+
1174
+ Parameters:
1175
+ dM_dT_temps (pd.Series): A pandas Series representing temperatures corresponding to
1176
+ the first derivation of magnetization with respect to temperature.
1177
+ dM_dT (pd.Series): A pandas Series representing the first derivative of
1178
+ magnetization with respect to temperature.
1179
+ Returns:
1180
+ float: The estimated temperature at which the second derivative of magnetization
1181
+ with respect to temperature crosses zero.
1182
+
1183
+ Note:
1184
+ The function assumes that the input series `dM_dT_temps` and `dM_dT` are related to
1185
+ each other and are of equal length.
1186
+ """
1187
+ max_dM_dT_temp = dM_dT_temps[dM_dT.idxmax()]
1188
+
1189
+ d2M_dT2 = thermomag_derivative(dM_dT_temps, dM_dT)
1190
+ d2M_dT2_T_array = d2M_dT2['T'].to_numpy()
1191
+ max_index = np.searchsorted(d2M_dT2_T_array, max_dM_dT_temp)
1192
+
1193
+ d2M_dT2_T_before = d2M_dT2['T'][max_index-1]
1194
+ d2M_dT2_before = d2M_dT2['dM_dT'][max_index-1]
1195
+ d2M_dT2_T_after = d2M_dT2['T'][max_index]
1196
+ d2M_dT2_after = d2M_dT2['dM_dT'][max_index]
1197
+
1198
+ zero_cross_temp = d2M_dT2_T_before + ((d2M_dT2_T_after - d2M_dT2_T_before) / (d2M_dT2_after - d2M_dT2_before)) * (0 - d2M_dT2_before)
1199
+
1200
+ return d2M_dT2, d2M_dT2_T_before, d2M_dT2_before, d2M_dT2_T_after, d2M_dT2_after, zero_cross_temp
1201
+
1202
+ def zero_crossing(dM_dT_temps, dM_dT, make_plot=False, xlim=None,
1203
+ verwey_marker='*', verwey_color='Pink',
1204
+ verwey_size=10,):
1205
+ """
1206
+ Calculate the temperature at which the second derivative of magnetization with respect to
1207
+ temperature crosses zero. This value provides an estimate of the peak of the derivative
1208
+ curve that is more precise than the maximum value.
1209
+
1210
+ The function computes the second derivative of magnetization (dM/dT) with respect to
1211
+ temperature, identifies the nearest points around the maximum value of the derivative,
1212
+ and then calculates the temperature at which this second derivative crosses zero using
1213
+ linear interpolation.
1214
+
1215
+ Parameters:
1216
+ dM_dT_temps (pd.Series): A pandas Series representing temperatures corresponding to
1217
+ the first derivation of magnetization with respect to temperature.
1218
+ dM_dT (pd.Series): A pandas Series representing the first derivative of
1219
+ magnetization with respect to temperature.
1220
+ make_plot (bool, optional): If True, a plot will be generated. Defaults to False.
1221
+ xlim (tuple, optional): A tuple specifying the x-axis limits for the plot. Defaults to None.
1222
+ verwey_marker : str, optional
1223
+ Marker symbol used to denote the Verwey transition estimate on the plot. Default is '*'.
1224
+ verwey_color : str, optional
1225
+ Color of the marker representing the Verwey transition estimate. Default is 'Pink'.
1226
+ verwey_size : int, optional
1227
+ Size of the marker used for the Verwey transition estimate. Default is 10.
1228
+
1229
+ Returns:
1230
+ float: The estimated temperature at which the second derivative of magnetization
1231
+ with respect to temperature crosses zero.
1232
+
1233
+ Note:
1234
+ The function assumes that the input series `dM_dT_temps` and `dM_dT` are related to
1235
+ each other and are of equal length.
1236
+ """
1237
+
1238
+ d2M_dT2, d2M_dT2_T_before, d2M_dT2_before, d2M_dT2_T_after, d2M_dT2_after, zero_cross_temp = calc_zero_crossing(dM_dT_temps, dM_dT)
1239
+
1240
+ if make_plot:
1241
+ fig = plt.figure(figsize=(12,4))
1242
+ ax0 = fig.add_subplot(1,1,1)
1243
+ ax0.plot(d2M_dT2['T'], d2M_dT2['dM_dT'], '.-', color='purple', label='magnetite (background fit minus measurement)')
1244
+ ax0.plot(d2M_dT2_T_before, d2M_dT2_before, marker='o', markerfacecolor='none', markeredgecolor='red')
1245
+ ax0.plot(d2M_dT2_T_after, d2M_dT2_after, marker='o', markerfacecolor='none', markeredgecolor='red')
1246
+ ax0.plot(zero_cross_temp, 0, verwey_marker, color=verwey_color, markersize=verwey_size, markeredgecolor='black')
1247
+ label = f'{zero_cross_temp:.1f} K'
1248
+ ax0.text(zero_cross_temp+2, 0, label, color='black',
1249
+ verticalalignment='center', horizontalalignment='left',
1250
+ bbox=dict(facecolor='white', alpha=0.7, edgecolor='none'))
1251
+ ax0.set_ylabel('d$^2$M/dT$^2$')
1252
+ ax0.set_xlabel('T (K)')
1253
+ ax0.grid(True)
1254
+ ax0.ticklabel_format(axis='y', style='scientific', scilimits=(0,0))
1255
+ if xlim is not None:
1256
+ ax0.set_xlim(xlim)
1257
+ plt.show()
1258
+
1259
+ return zero_cross_temp
1260
+
1261
+
1262
+ def goethite_removal(rtsirm_warm_data,
1263
+ rtsirm_cool_data,
1264
+ t_min=150, t_max=290, poly_deg=2,
1265
+ rtsirm_cool_color='#17becf', rtsirm_warm_color='#d62728',
1266
+ symbol_size=4, return_data=False):
1267
+ """
1268
+ Analyzes and visualizes the removal of goethite signal from Room Temperature Saturation
1269
+ Isothermal Remanent Magnetization (RTSIRM) warming and cooling data. The function fits
1270
+ a polynomial to the RTSRIM warming curve between specified temperature bounds to model
1271
+ the goethite contribution, then subtracts this fit from the original data. The corrected
1272
+ and uncorrected magnetizations are plotted, along with their derivatives, to assess the
1273
+ effect of goethite removal.
1274
+
1275
+ Parameters:
1276
+ rtsirm_warm_data (pd.DataFrame): DataFrame containing 'meas_temp' and 'magn_mass' columns
1277
+ for RTSIRM warming data.
1278
+ rtsirm_cool_data (pd.DataFrame): DataFrame containing 'meas_temp' and 'magn_mass' columns
1279
+ for RTSIRM cooling data.
1280
+ t_min (int, optional): Minimum temperature for polynomial fitting. Default is 150.
1281
+ t_max (int, optional): Maximum temperature for polynomial fitting. Default is 290.
1282
+ poly_deg (int, optional): Degree of the polynomial to fit. Default is 2.
1283
+ rtsirm_cool_color (str, optional): Color code for plotting cooling data. Default is '#17becf'.
1284
+ rtsirm_warm_color (str, optional): Color code for plotting warming data. Default is '#d62728'.
1285
+ symbol_size (int, optional): Size of the markers in the plots. Default is 4.
1286
+ return_data (bool, optional): If True, returns the corrected magnetization data for both
1287
+ warming and cooling. Default is False.
1288
+
1289
+ Returns:
1290
+ Tuple[pd.Series, pd.Series]: Only if return_data is True. Returns two pandas Series
1291
+ containing the corrected magnetization data for the warming
1292
+ and cooling sequences, respectively.
1293
+ """
1294
+
1295
+ rtsirm_warm_temps = rtsirm_warm_data['meas_temp']
1296
+ rtsirm_warm_mags = rtsirm_warm_data['magn_mass']
1297
+ rtsirm_cool_temps = rtsirm_cool_data['meas_temp']
1298
+ rtsirm_cool_mags = rtsirm_cool_data['magn_mass']
1299
+
1300
+ rtsirm_warm_temps.reset_index(drop=True, inplace=True)
1301
+ rtsirm_warm_mags.reset_index(drop=True, inplace=True)
1302
+ rtsirm_cool_temps.reset_index(drop=True, inplace=True)
1303
+ rtsirm_cool_mags.reset_index(drop=True, inplace=True)
1304
+
1305
+ rtsirm_warm_temps_filtered_indices = [i for i in np.arange(len(rtsirm_warm_temps)) if ((float(rtsirm_warm_temps[i]) > float(t_min)) and (float(rtsirm_warm_temps[i]) < float(t_max)) )]
1306
+ rtsirm_warm_temps_filtered = rtsirm_warm_temps[rtsirm_warm_temps_filtered_indices]
1307
+ rtsirm_warm_mags_filtered = rtsirm_warm_mags[rtsirm_warm_temps_filtered_indices]
1308
+
1309
+ geothite_fit = np.polyfit(rtsirm_warm_temps_filtered, rtsirm_warm_mags_filtered, poly_deg)
1310
+ rtsirm_warm_mags_polyfit = np.poly1d(geothite_fit)(rtsirm_warm_temps)
1311
+ rtsirm_cool_mags_polyfit = np.poly1d(geothite_fit)(rtsirm_cool_temps)
1312
+
1313
+ rtsirm_warm_mags_corrected = rtsirm_warm_mags - rtsirm_warm_mags_polyfit
1314
+ rtsirm_cool_mags_corrected = rtsirm_cool_mags - rtsirm_cool_mags_polyfit
1315
+
1316
+ fig, axs = plt.subplots(nrows=2, ncols=2, figsize=(12, 8))
1317
+
1318
+ axs[0, 0].plot(rtsirm_warm_temps, rtsirm_warm_mags, color=rtsirm_warm_color,
1319
+ marker='o', linestyle='-', markersize=symbol_size, label='RTSIRM Warming')
1320
+ axs[0, 0].plot(rtsirm_cool_temps, rtsirm_cool_mags, color=rtsirm_cool_color,
1321
+ marker='o', linestyle='-', markersize=symbol_size, label='RTSIRM Cooling')
1322
+ axs[0, 0].plot(rtsirm_warm_temps, rtsirm_warm_mags_polyfit, color=rtsirm_warm_color,
1323
+ linestyle='--', label='goethite fit')
1324
+ axs[0, 1].plot(rtsirm_warm_temps, rtsirm_warm_mags_corrected, color=rtsirm_warm_color,
1325
+ marker='s', linestyle='-', markersize=symbol_size, label='RTSIRM Warming (goethite removed)')
1326
+ axs[0, 1].plot(rtsirm_cool_temps, rtsirm_cool_mags_corrected, color=rtsirm_cool_color,
1327
+ marker='s', linestyle='-', markersize=symbol_size, label='RTSIRM Cooling (goethite removed)')
1328
+
1329
+ ax0 = axs[0, 0]
1330
+ rectangle = patches.Rectangle((t_min, ax0.get_ylim()[0]), t_max - t_min,
1331
+ ax0.get_ylim()[1] - ax0.get_ylim()[0],
1332
+ linewidth=0, edgecolor=None, facecolor='gray',
1333
+ alpha=0.3)
1334
+ ax0.add_patch(rectangle)
1335
+ rect_legend_patch = patches.Patch(color='gray', alpha=0.3, label='excluded from background fit')
1336
+ handles, labels = ax0.get_legend_handles_labels()
1337
+ handles.append(rect_legend_patch) # Add the rectangle legend patch
1338
+
1339
+ for ax in axs[0, :]:
1340
+ ax.set_xlabel("Temperature (K)")
1341
+ ax.set_ylabel("Magnetization (Am$^2$/kg)")
1342
+ ax.legend()
1343
+ ax.grid(True)
1344
+ ax.set_xlim(0, 300)
1345
+
1346
+ rtsirm_cool_derivative = thermomag_derivative(rtsirm_cool_data['meas_temp'],
1347
+ rtsirm_cool_data['magn_mass'], drop_first=True)
1348
+ rtsirm_warm_derivative = thermomag_derivative(rtsirm_warm_data['meas_temp'],
1349
+ rtsirm_warm_data['magn_mass'], drop_last=True)
1350
+
1351
+ rtsirm_cool_derivative_corrected = thermomag_derivative(rtsirm_cool_data['meas_temp'],
1352
+ rtsirm_cool_mags_corrected, drop_first=True)
1353
+ rtsirm_warm_derivative_corrected = thermomag_derivative(rtsirm_warm_data['meas_temp'],
1354
+ rtsirm_warm_mags_corrected, drop_last=True)
1355
+
1356
+ axs[1, 0].plot(rtsirm_cool_derivative['T'], rtsirm_cool_derivative['dM_dT'],
1357
+ marker='o', linestyle='-', color=rtsirm_cool_color, markersize=symbol_size, label='RTSIRM Cooling Derivative')
1358
+ axs[1, 0].plot(rtsirm_warm_derivative['T'], rtsirm_warm_derivative['dM_dT'],
1359
+ marker='o', linestyle='-', color=rtsirm_warm_color, markersize=symbol_size, label='RTSIRM Warming Derivative')
1360
+ axs[1, 1].plot(rtsirm_cool_derivative_corrected['T'], rtsirm_cool_derivative_corrected['dM_dT'],
1361
+ marker='s', linestyle='-', color=rtsirm_cool_color, markersize=symbol_size, label='RTSIRM Cooling Derivative\n(goethite removed)')
1362
+ axs[1, 1].plot(rtsirm_warm_derivative_corrected['T'], rtsirm_warm_derivative_corrected['dM_dT'],
1363
+ marker='s', linestyle='-', color=rtsirm_warm_color, markersize=symbol_size, label='RTSIRM Warming Derivative\n(goethite removed)')
1364
+ for ax in axs[1, :]:
1365
+ ax.set_xlabel("Temperature (K)")
1366
+ ax.set_ylabel("dM/dT")
1367
+ ax.legend()
1368
+ ax.grid(True)
1369
+ ax.set_xlim(0, 300)
1370
+
1371
+ fig.tight_layout()
1372
+ plt.show()
1373
+
1374
+ if return_data:
1375
+ rtsirm_warm_adjusted = pd.DataFrame({'meas_temp': rtsirm_warm_temps, 'corrected_magn_mass': rtsirm_warm_mags_corrected})
1376
+ rtsirm_cool_adjusted = pd.DataFrame({'meas_temp': rtsirm_cool_temps, 'corrected_magn_mass': rtsirm_cool_mags_corrected})
1377
+ return rtsirm_warm_adjusted, rtsirm_cool_adjusted
1378
+
1379
+
1380
+ def interactive_goethite_removal(measurements, specimen_dropdown):
1381
+
1382
+ selected_specimen_name = specimen_dropdown.value
1383
+
1384
+ fc_data, zfc_data, rtsirm_cool_data, rtsirm_warm_data = extract_mpms_data_dc(measurements, selected_specimen_name)
1385
+
1386
+ # Determine a fixed width for the descriptions to align the sliders
1387
+ description_width = '250px' # Adjust this based on the longest description
1388
+ slider_total_width = '600px' # Total width of the slider widget including the description
1389
+
1390
+ description_style = {'description_width': description_width}
1391
+ slider_layout = widgets.Layout(width=slider_total_width) # Set the total width of the slider widget
1392
+
1393
+ # Update sliders to use IntRangeSlider
1394
+ temp_range_slider = widgets.IntRangeSlider(
1395
+ value=[150, 290], min=0, max=300, step=1,
1396
+ description='Geothite Fit Temperature Range (K):',
1397
+ layout=slider_layout, style=description_style
1398
+ )
1399
+
1400
+ poly_deg_slider = widgets.IntSlider(
1401
+ value=2, min=1, max=3, step=1,
1402
+ description='Goethite Fit Polynomial Degree:',
1403
+ layout=slider_layout, style=description_style
1404
+ )
1405
+
1406
+ # Function to reset sliders to initial values
1407
+ def reset_sliders(b):
1408
+ temp_range_slider.value = (150, 290)
1409
+ poly_deg_slider.value = 2
1410
+
1411
+ # Create reset button
1412
+ reset_button = widgets.Button(description="Reset to Default Values", layout=widgets.Layout(width='200px'))
1413
+ reset_button.on_click(reset_sliders)
1414
+
1415
+ title_label = widgets.Label(value='Adjust Parameters for ' + selected_specimen_name + ' ' + 'goethite' + ' fit')
1416
+
1417
+ # Add the reset button to the UI
1418
+ ui = widgets.VBox([
1419
+ title_label,
1420
+ temp_range_slider,
1421
+ poly_deg_slider,
1422
+ reset_button
1423
+ ])
1424
+
1425
+ out = widgets.interactive_output(
1426
+ lambda temp_range, poly_deg: goethite_removal(
1427
+ rtsirm_warm_data, rtsirm_cool_data,
1428
+ temp_range[0], temp_range[1],
1429
+ poly_deg
1430
+ ), {
1431
+ 'temp_range': temp_range_slider,
1432
+ 'poly_deg': poly_deg_slider,
1433
+ }
1434
+ )
1435
+
1436
+ out.layout.height = '500px'
1437
+
1438
+ display(ui, out)
1439
+
1440
+
1441
+ def plot_mpms_ac(
1442
+ experiment,
1443
+ frequency=None,
1444
+ phase='in',
1445
+ figsize=(6, 6),
1446
+ interactive=False,
1447
+ return_figure=False,
1448
+ show_plot=True):
1449
+ """
1450
+ Plot AC susceptibility data from MPMS-X, optionally as interactive Bokeh.
1451
+
1452
+ Parameters
1453
+ ----------
1454
+ experiment : pandas.DataFrame
1455
+ The experiment table from the MagIC contribution.
1456
+ frequency : float or None
1457
+ Frequency of AC measurement in Hz; None plots all frequencies.
1458
+ phase : {'in','out','both'}
1459
+ Which phase to plot.
1460
+ figsize : tuple of float
1461
+ Figure size for Matplotlib (width, height).
1462
+ interactive : bool
1463
+ If True, render with Bokeh for interactive exploration.
1464
+ return_figure : bool
1465
+ If True, return the figure object(s).
1466
+ show_plot : bool
1467
+ If True, display the plot.
1468
+
1469
+ Returns
1470
+ -------
1471
+ fig, ax or (fig, axes) or Bokeh layout or None
1472
+ """
1473
+ if phase not in ['in', 'out', 'both']:
1474
+ raise ValueError('phase must be "in", "out", or "both"')
1475
+ freqs = ([frequency] if frequency is not None
1476
+ else experiment['meas_freq'].unique().tolist())
1477
+ if frequency is not None and frequency not in freqs:
1478
+ raise ValueError(f'frequency must be one of {freqs}')
1479
+
1480
+ if interactive:
1481
+ tools = [
1482
+ HoverTool(tooltips=[('T', '@x'), ('χ', '@y')]),
1483
+ 'pan,box_zoom,wheel_zoom,reset,save']
1484
+ n = len(freqs)
1485
+ palette = Category10[n] if n <= 10 else Category10[10]
1486
+ figs = []
1487
+
1488
+ if phase in ['in', 'out']:
1489
+ p = figure(
1490
+ title=f'AC χ ({phase} phase)',
1491
+ x_axis_label='Temperature (K)',
1492
+ y_axis_label='χ (m³/kg)',
1493
+ tools=tools,
1494
+ width=int(figsize[0] * 100),
1495
+ height=int(figsize[1] * 100))
1496
+ p.xaxis.axis_label_text_font_style = "normal"
1497
+ p.yaxis.axis_label_text_font_style = "normal"
1498
+ for i, f in enumerate(freqs):
1499
+ d = experiment[experiment['meas_freq'] == f]
1500
+ col = 'susc_chi_mass' if phase == 'in' else 'susc_chi_qdr_mass'
1501
+ color = palette[i]
1502
+ p.line(
1503
+ d['meas_temp'], d[col],
1504
+ legend_label=f'{f} Hz',
1505
+ line_width=2,
1506
+ color=color)
1507
+ p.scatter(
1508
+ d['meas_temp'], d[col],
1509
+ size=6,
1510
+ alpha=0.6,
1511
+ fill_color=color,
1512
+ line_color=color,
1513
+ legend_label=f'{f} Hz')
1514
+ p.legend.location = 'top_left'
1515
+ p.legend.click_policy = "hide"
1516
+ figs = [p]
1517
+ else:
1518
+ p1 = figure(
1519
+ title='AC χ in phase',
1520
+ x_axis_label='Temperature (K)',
1521
+ y_axis_label='χ (m³/kg)',
1522
+ tools=tools,
1523
+ width=int(figsize[0] * 50),
1524
+ height=int(figsize[1] * 100))
1525
+ p2 = figure(
1526
+ title='AC χ out phase',
1527
+ x_axis_label='Temperature (K)',
1528
+ y_axis_label='χ (m³/kg)',
1529
+ tools=tools,
1530
+ width=int(figsize[0] * 50),
1531
+ height=int(figsize[1] * 100))
1532
+ for p in (p1, p2):
1533
+ p.xaxis.axis_label_text_font_style = "normal"
1534
+ p.yaxis.axis_label_text_font_style = "normal"
1535
+ for i, f in enumerate(freqs):
1536
+ d = experiment[experiment['meas_freq'] == f]
1537
+ color = palette[i]
1538
+ p1.line(
1539
+ d['meas_temp'], d['susc_chi_mass'],
1540
+ legend_label=f'{f} Hz',
1541
+ line_width=2,
1542
+ color=color)
1543
+ p1.scatter(
1544
+ d['meas_temp'], d['susc_chi_mass'],
1545
+ size=6,
1546
+ alpha=0.6,
1547
+ fill_color=color,
1548
+ line_color=color,
1549
+ legend_label=f'{f} Hz')
1550
+ p2.line(
1551
+ d['meas_temp'], d['susc_chi_qdr_mass'],
1552
+ legend_label=f'{f} Hz',
1553
+ line_width=2,
1554
+ color=color)
1555
+ p2.scatter(
1556
+ d['meas_temp'], d['susc_chi_qdr_mass'],
1557
+ size=6,
1558
+ alpha=0.6,
1559
+ fill_color=color,
1560
+ line_color=color,
1561
+ legend_label=f'{f} Hz')
1562
+ p1.legend.location = p2.legend.location = 'top_left'
1563
+ p1.legend.click_policy = p2.legend.click_policy = "hide"
1564
+ figs = [p1, p2]
1565
+
1566
+ layout = gridplot([figs], sizing_mode='stretch_width')
1567
+ if show_plot:
1568
+ show(layout)
1569
+ if return_figure:
1570
+ return layout
1571
+ return None
1572
+
1573
+ # static Matplotlib
1574
+ if phase in ['in', 'out']:
1575
+ fig, ax = plt.subplots(figsize=figsize)
1576
+ col = 'susc_chi_mass' if phase == 'in' else 'susc_chi_qdr_mass'
1577
+ for f in freqs:
1578
+ d = experiment[experiment['meas_freq'] == f]
1579
+ ax.plot(d['meas_temp'], d[col], 'o-', label=f'{f} Hz')
1580
+ ax.set_xlabel('Temperature (K)')
1581
+ ax.set_ylabel('χ (m³/kg)')
1582
+ ax.set_title(f'AC χ ({phase} phase)')
1583
+ ax.legend()
1584
+ if show_plot:
1585
+ plt.show()
1586
+ if return_figure:
1587
+ return fig, ax
1588
+ return None
1589
+
1590
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=figsize)
1591
+ for f in freqs:
1592
+ d = experiment[experiment['meas_freq'] == f]
1593
+ ax1.plot(d['meas_temp'], d['susc_chi_mass'], 'o-', label=f'{f} Hz')
1594
+ ax2.plot(d['meas_temp'], d['susc_chi_qdr_mass'], 'o-', label=f'{f} Hz')
1595
+ ax1.set_xlabel('Temperature (K)')
1596
+ ax1.set_ylabel('χ (m³/kg)')
1597
+ ax1.set_title('AC χ in phase')
1598
+ ax1.legend()
1599
+ ax2.set_xlabel('Temperature (K)')
1600
+ ax2.set_ylabel('χ (m³/kg)')
1601
+ ax2.set_title('AC χ out phase')
1602
+ ax2.legend()
1603
+ if show_plot:
1604
+ plt.show()
1605
+ if return_figure:
1606
+ return fig, (ax1, ax2)
1607
+
1608
+
1609
+ # hysteresis functions
1610
+ # ------------------------------------------------------------------------------------------------------------------
1611
+
1612
+ def extract_hysteresis_data(df, specimen_name):
1613
+ """
1614
+ Extracts and separates Hysteresis data
1615
+ for a specific specimen from a dataframe.
1616
+
1617
+ This function filters data for a given specimen and separates it based on
1618
+ different MagIC measurement method codes. It specifically looks for data
1619
+ corresponding to 'LP-HYS' Hysteresis Data
1620
+
1621
+ Parameters:
1622
+ df (pandas.DataFrame): The dataframe containing MPMS measurement data.
1623
+ specimen_name (str): The name of the specimen to filter data for.
1624
+
1625
+ Returns:
1626
+ tuple: A tuple containing four pandas.DataFrames:
1627
+ - fc_data: Data filtered for 'LP-FC' method if available, otherwise an empty DataFrame.
1628
+ - zfc_data: Data filtered for 'LP-ZFC' method if available, otherwise an empty DataFrame.
1629
+ - rtsirm_cool_data: Data filtered for 'LP-CW-SIRM:LP-MC' method if available, otherwise an empty DataFrame.
1630
+ - rtsirm_warm_data: Data filtered for 'LP-CW-SIRM:LP-MW' method if available, otherwise an empty DataFrame.
1631
+
1632
+ Example:
1633
+ >>> fc, zfc, rtsirm_cool, rtsirm_warm = extract_mpms_data_dc(measurements_df, 'Specimen_1')
1634
+ """
1635
+
1636
+ specimen_df = df[df['specimen'] == specimen_name]
1637
+
1638
+ hyst_data = specimen_df[specimen_df['method_codes'].str.contains('LP-HYS', na=False)]
1639
+
1640
+ return hyst_data
1641
+
1642
+ def plot_hysteresis_loop(field, magnetization, specimen_name, p=None, line_color='grey', line_width=1, label='', legend_location='bottom_right'):
1643
+ '''
1644
+ function to plot a hysteresis loop
1645
+
1646
+ Parameters
1647
+ ----------
1648
+ field : numpy array or list
1649
+ hysteresis loop field values
1650
+ magnetization : numpy array or list
1651
+ hysteresis loop magnetization values
1652
+
1653
+ Returns
1654
+ -------
1655
+ p : bokeh.plotting.figure
1656
+ '''
1657
+ if not _HAS_BOKEH:
1658
+ print("Bokeh is not installed. Please install it to enable hysteresis data processing.")
1659
+ return
1660
+
1661
+ assert len(field) == len(magnetization), 'Field and magnetization arrays must be the same length'
1662
+ if p is None:
1663
+ p = figure(title=f'{specimen_name} hysteresis loop',
1664
+ x_axis_label='Field (T)',
1665
+ y_axis_label='Magnetization (Am\u00B2/kg)',
1666
+ width=600,
1667
+ height=600, aspect_ratio=1)
1668
+ p.axis.axis_label_text_font_size = '12pt'
1669
+ p.axis.axis_label_text_font_style = 'normal'
1670
+ p.title.text_font_size = '14pt'
1671
+ p.title.text_font_style = 'bold'
1672
+ p.title.align = 'center'
1673
+ p.line(field, magnetization, line_width=line_width, color=line_color, legend_label=label)
1674
+ p.legend.click_policy="hide"
1675
+ p.legend.location = legend_location
1676
+ else:
1677
+ p.line(field, magnetization, line_width=line_width, color=line_color, legend_label=label)
1678
+ p.legend.location = legend_location
1679
+
1680
+ return p
1681
+
1682
+ def split_hysteresis_loop(field, magnetization):
1683
+ '''
1684
+ function to split a hysteresis loop into upper and lower branches
1685
+ by the change of sign in the applied field gradient
1686
+
1687
+ Parameters
1688
+ ----------
1689
+ field : numpy array or list
1690
+ hysteresis loop field values
1691
+ magnetization : numpy array or list
1692
+ hysteresis loop magnetization values
1693
+
1694
+ Returns
1695
+ -------
1696
+ upper_branch : tuple
1697
+ tuple of field and magnetization values for the upper branch
1698
+ lower_branch : tuple
1699
+ tuple of field and magnetization values for the lower branch
1700
+ '''
1701
+ assert len(field) == len(magnetization), 'Field and magnetization arrays must be the same length'
1702
+
1703
+ # identify loop turning point by change in sign of the field difference
1704
+ # split the data into upper and lower branches
1705
+ field_gradient = np.gradient(field)
1706
+ # There should just be one turning point in the field gradient
1707
+ turning_points = np.where(np.diff(np.sign(field_gradient)))[0]
1708
+ if len(turning_points) > 1:
1709
+ raise ValueError('More than one turning point found in the gradient of the applied field')
1710
+ turning_point = turning_points[0]
1711
+ upper_branch = [field[:turning_point+1], magnetization[:turning_point+1]]
1712
+ # sort the upper branch in reverse order
1713
+ upper_branch = [field[:turning_point+1][::-1], magnetization[:turning_point+1][::-1]]
1714
+ lower_branch = [field[turning_point+1:], magnetization[turning_point+1:]]
1715
+
1716
+ return upper_branch, lower_branch
1717
+
1718
+ def grid_hysteresis_loop(field, magnetization):
1719
+ '''
1720
+ function to grid a hysteresis loop into a regular grid
1721
+ with grid intervals equal to the average field step size calculated from the data
1722
+
1723
+ Parameters
1724
+ ----------
1725
+ field : numpy array or list
1726
+ hysteresis loop field values
1727
+ magnetization : numpy array or list
1728
+ hysteresis loop magnetization values
1729
+
1730
+ Returns
1731
+ -------
1732
+ grid_field : numpy array
1733
+ gridded field values
1734
+ grid_magnetization : numpy array
1735
+ gridded magnetization values
1736
+ '''
1737
+ assert len(field) == len(magnetization), 'Field and magnetization arrays must be the same length'
1738
+
1739
+ upper_branch, lower_branch = split_hysteresis_loop(field, magnetization)
1740
+
1741
+ # calculate the average field step size
1742
+ field_step = np.mean(np.abs(np.diff(upper_branch[0])))
1743
+ # grid the hysteresis loop
1744
+ upper_field = np.arange(np.max(field), 0, -field_step)
1745
+ upper_field = np.concatenate([upper_field, -upper_field[::-1]])
1746
+ lower_field = upper_field[::-1]
1747
+ grid_field = np.concatenate([upper_field, lower_field])
1748
+
1749
+ upper_branch_itp = np.interp(upper_field, upper_branch[0], upper_branch[1])
1750
+ lower_branch_itp = np.interp(lower_field, lower_branch[0], lower_branch[1])
1751
+ grid_magnetization = np.concatenate([upper_branch_itp, lower_branch_itp])
1752
+
1753
+ return grid_field, grid_magnetization
1754
+
1755
+ def ANOVA(xs, ys):
1756
+ '''
1757
+ ANOVA statistics for linear regression
1758
+
1759
+ Parameters
1760
+ ----------
1761
+ xs : numpy array
1762
+ x values
1763
+ ys : numpy array
1764
+ y values
1765
+
1766
+ Returns
1767
+ -------
1768
+ results : dict
1769
+ dictionary of the results of the ANOVA calculation
1770
+ and intermediate statistics for the ANOVA calculation
1771
+
1772
+ '''
1773
+
1774
+ xs = np.array(xs)
1775
+ ys = np.array(ys)
1776
+
1777
+ ys_mean = np.mean(ys)
1778
+
1779
+ # fit the gridded data by a straight line
1780
+ slope, intercept = np.polyfit(xs, ys, 1)
1781
+
1782
+ # AVOVA calculation
1783
+ # total sum of squares for the dependent variable (magnetization)
1784
+ SST = np.sum((ys - ys_mean)**2)
1785
+
1786
+ # sum of squares due to regression
1787
+ SSR = np.sum((slope * xs + intercept - ys_mean)**2)
1788
+
1789
+ # the remaining unexplained variation (noise and lack of fit)
1790
+ SSD = np.sum((ys - (slope * xs + intercept)) ** 2)
1791
+ R_squared = SSR/SST
1792
+
1793
+ results = {'slope':slope,
1794
+ 'intercept':intercept,
1795
+ 'SST':SST,
1796
+ 'SSR':SSR,
1797
+ 'SSD':SSD,
1798
+ 'R_squared': R_squared}
1799
+
1800
+ return results
1801
+
1802
+ def hyst_linearity_test(grid_field, grid_magnetization):
1803
+ '''
1804
+ function for testing the linearity of a hysteresis loop
1805
+
1806
+ Parameters
1807
+ ----------
1808
+ grid_field : numpy array
1809
+ gridded field values
1810
+ grid_magnetization : numpy array
1811
+ gridded magnetization values
1812
+
1813
+ Returns
1814
+ -------
1815
+ results : dict
1816
+ dictionary of the results of the linearity test
1817
+ and intermediate statistics for the ANOVA calculation
1818
+ '''
1819
+ grid_field = np.array(grid_field)
1820
+ grid_magnetization = np.array(grid_magnetization)
1821
+
1822
+ upper_branch, lower_branch = split_hysteresis_loop(grid_field, grid_magnetization)
1823
+
1824
+ anova_results = ANOVA(grid_field, grid_magnetization)
1825
+
1826
+ # fit the gridded data by a straight line
1827
+ slope, intercept = anova_results['slope'], anova_results['intercept']
1828
+
1829
+ # AVOVA calculation
1830
+ # total sum of squares for the dependent variable (magnetization)
1831
+ SST = anova_results['SST']
1832
+
1833
+ # sum of squares due to regression
1834
+ SSR = anova_results['SSR']
1835
+
1836
+ # the remaining unexplained variation (noise and lack of fit)
1837
+ SSD = anova_results['SSD']
1838
+
1839
+ R_squared = anova_results['R_squared']
1840
+
1841
+ # invert the lower branch to match the upper branch
1842
+ # and calculate the differences between the upper and the inverted lower branch
1843
+ # for any loop shifts and drift that are due to noise alone
1844
+ SSPE = np.sum((upper_branch[1] - (-lower_branch[1][::-1])) ** 2) / 2
1845
+
1846
+ # calculate the lack of fit statistic
1847
+ SSLF = SSD - SSPE
1848
+
1849
+ # mean square pure error
1850
+ MSPE = SSPE / (len(grid_field) / 2)
1851
+
1852
+ # mean square error due to lack of fit
1853
+ MSLF = SSLF / (len(grid_field)/2 - 2)
1854
+
1855
+ # mean squares due to regression
1856
+ MSR = SSR
1857
+
1858
+ # mean squares due to noise
1859
+ MSD = SSD / (len(grid_field) - 2)
1860
+
1861
+ # F-ratio for the linear component
1862
+ FL = MSR / MSD
1863
+
1864
+ # F-ratio for the non-linear component
1865
+ FNL = MSLF / MSPE
1866
+
1867
+ results = {
1868
+ 'SST': float(SST),
1869
+ 'SSR': float(SSR),
1870
+ 'SSD': float(SSD),
1871
+ 'R_squared': float(R_squared),
1872
+ 'SSPE': float(SSPE),
1873
+ 'SSLF': float(SSLF),
1874
+ 'MSPE': float(MSPE),
1875
+ 'MSLF': float(MSLF),
1876
+ 'MSR': float(MSR),
1877
+ 'MSD': float(MSD),
1878
+ 'FL': float(FL),
1879
+ 'FNL': float(FNL),
1880
+ 'slope': float(slope),
1881
+ 'intercept': float(intercept),
1882
+ 'loop_is_linear': bool(FNL < 1.25),
1883
+ }
1884
+
1885
+ return results
1886
+
1887
+ def linefit(xarr, yarr):
1888
+ """
1889
+ Linear regression fit: y = intercept + slope * x
1890
+ Returns: intercept, slope, R^2
1891
+ """
1892
+ xarr = np.asarray(xarr)
1893
+ yarr = np.asarray(yarr)
1894
+
1895
+ # Fit a line y = slope * x + intercept
1896
+ slope, intercept = np.polyfit(xarr, yarr, 1)
1897
+
1898
+ # Predict y using the fitted line
1899
+ y_pred = intercept + slope * xarr
1900
+
1901
+ # Total sum of squares
1902
+ ss_tot = np.sum((yarr - np.mean(yarr))**2)
1903
+
1904
+ # Residual sum of squares
1905
+ ss_res = np.sum((y_pred - np.mean(yarr))**2)
1906
+
1907
+ # R^2 score
1908
+ r2 = 1 - ss_res / ss_tot if ss_tot > 0 else 1
1909
+
1910
+ return intercept, slope, r2
1911
+
1912
+ def loop_H_off(loop_fields, loop_moments, H_shift):
1913
+ """
1914
+ Estimates a vertical shift (V_shift) and returns R² of a reflected loop.
1915
+
1916
+ Arguments:
1917
+ - loop_fields: List or array of magnetic field values.
1918
+ - loop_moments: Corresponding list or array of magnetic moments.
1919
+ - H_shift: Horizontal shift to apply to loop_fields.
1920
+
1921
+ Returns:
1922
+ - r2: R-squared value from linear regression between original and reflected data.
1923
+ - V_shift: Estimated vertical shift (mean of linear fit x-intercept).
1924
+ """
1925
+
1926
+ n = len(loop_fields)
1927
+
1928
+ # Apply horizontal shift
1929
+ loop_fields = loop_fields - H_shift
1930
+
1931
+ # Define bounds for symmetrical comparison
1932
+ min2 = np.min(loop_fields)
1933
+ max2 = np.max(loop_fields)
1934
+ min1 = -max2
1935
+ max1 = -min2
1936
+
1937
+ n1 = n // 2
1938
+ i2 = 0 # Python uses 0-based indexing
1939
+ x1 = []
1940
+ y1 = []
1941
+
1942
+ for i in range(n1, n):
1943
+ x = loop_fields[i]
1944
+ if min1 < x < max1:
1945
+ while -loop_fields[i2] < x and i2 < n - 1:
1946
+ i2 += 1
1947
+ if i2 > 0:
1948
+ dx = (-loop_fields[i2] - x) / (-loop_fields[i2] + loop_fields[i2 - 1])
1949
+ dy = dx * (-loop_moments[i2] + loop_moments[i2 - 1])
1950
+ y = -loop_moments[i2] - dy
1951
+ x1.append(loop_moments[i])
1952
+ y1.append(-y)
1953
+
1954
+ if len(x1) < 2:
1955
+ return 0.0, 0.0 # Not enough points for regression
1956
+
1957
+ intercept, slope, r2 = linefit(x1, y1)
1958
+ M_shift = intercept / 2
1959
+
1960
+ result = {'slope': slope, 'M_shift': M_shift, 'r2': r2}
1961
+ return result
1962
+
1963
+ def loop_Hshift_brent(loop_fields, loop_moments):
1964
+ def objective(H_shift):
1965
+ result = loop_H_off(loop_fields, loop_moments, H_shift)
1966
+ return result['r2']
1967
+
1968
+ ax = -np.max(loop_fields)/2
1969
+ bx = 0
1970
+ cx = -ax
1971
+ result = minimize_scalar(objective, method='brent', bracket=(ax, bx, cx), tol=1e-6)
1972
+
1973
+ opt_H_off = result.x
1974
+ opt_shift = loop_H_off(loop_fields, loop_moments, opt_H_off)
1975
+ opt_r2 = opt_shift['r2']
1976
+ opt_M_off = opt_shift['M_shift']
1977
+
1978
+ return opt_r2, opt_H_off, opt_M_off
1979
+
1980
+ def calc_Q(H, M, type='Q'):
1981
+ '''
1982
+ function for calculating quality factor Q for a hysteresis loop
1983
+ Q factor is defined by the log10 of the signal to noise ratio
1984
+ where signal is the sum of the square of the data
1985
+ which is the averaged sum over the upper and lower branches for Q
1986
+ and is the sum of the square of the upper branch for Qf
1987
+ '''
1988
+ assert type in ['Q', 'Qf'], 'type must be either Q or Qf'
1989
+ H = np.array(H)
1990
+ M = np.array(M)
1991
+ upper_branch, lower_branch = split_hysteresis_loop(H, M)
1992
+ Me = upper_branch[1] + lower_branch[1][::-1]
1993
+ if type == 'Q':
1994
+ M_sn = np.sqrt((np.sum(upper_branch[1]**2) + np.sum(lower_branch[1][::-1]**2))/2/np.sum(Me**2))
1995
+ elif type == 'Qf':
1996
+ M_sn = np.sqrt(np.sum(upper_branch[1]**2)/np.sum(Me**2))
1997
+
1998
+ Q = np.log10(M_sn)
1999
+ return M_sn, Q
2000
+
2001
+ def hyst_loop_centering(grid_field, grid_magnetization):
2002
+ '''
2003
+ function for finding the optimum applied field offset value for minimizing a linear fit through
2004
+ the Me based on the R2 value. The idea is maximizing the residual noise in the Me gives the best centered loop.
2005
+
2006
+ Parameters
2007
+ ----------
2008
+ grid_field : numpy array
2009
+ gridded field values
2010
+ grid_magnetization : numpy array
2011
+ gridded magnetization values
2012
+
2013
+ Returns
2014
+ -------
2015
+ opt_H_offset : float
2016
+ optimized applied field offset value for the loop
2017
+ opt_M_offset : float
2018
+ calculated magnetization offset value for the loop based on the optimized applied field offset
2019
+ (intercept of the fitted line using the upper branch and the inverted and optimally offsetted lower branch)
2020
+ R_squared : float
2021
+ R-squared value of the linear fit between the upper branch and the inverted and offsetted lower branch
2022
+
2023
+ '''
2024
+ grid_field = np.array(grid_field)
2025
+ grid_magnetization = np.array(grid_magnetization)
2026
+ R_squared, H_offset, M_offset = loop_Hshift_brent(grid_field, grid_magnetization)
2027
+
2028
+ M_sn, Q = calc_Q(grid_field, grid_magnetization)
2029
+
2030
+ # re-gridding after offset correction to ensure symmetry
2031
+ centered_H, centered_M = grid_hysteresis_loop(grid_field-H_offset/2, grid_magnetization-M_offset)
2032
+
2033
+ results = {'centered_H':centered_H,
2034
+ 'centered_M': centered_M,
2035
+ 'opt_H_offset':float(H_offset/2),
2036
+ 'opt_M_offset':float(M_offset),
2037
+ 'R_squared':float(R_squared),
2038
+ 'M_sn':float(M_sn),
2039
+ 'Q':float(Q),
2040
+ }
2041
+ return results
2042
+
2043
+ def linear_HF_fit(field, magnetization, HF_cutoff=0.8):
2044
+ '''
2045
+ function to fit a linear function to the high field portion of a hysteresis loop
2046
+
2047
+ Parameters
2048
+ ----------
2049
+ field : numpy array or list
2050
+ raw hysteresis loop field values
2051
+ magnetization : numpy array or list
2052
+ raw hysteresis loop magnetization values
2053
+
2054
+ Returns
2055
+ -------
2056
+ slope : float
2057
+ slope of the linear fit
2058
+ can be interpreted to be the paramagnetic/diamagnetic susceptibility
2059
+ intercept : float
2060
+ y-intercept of the linear fit
2061
+ can be interpreted to be the saturation magnetization of the ferromagnetic component
2062
+ '''
2063
+ assert len(field) == len(magnetization), 'Field and magnetization arrays must be the same length'
2064
+ assert HF_cutoff > 0 and HF_cutoff < 1, 'Portion must be between 0 and 1'
2065
+
2066
+ # adopting IRM's max field cutoff at 97% of the max field
2067
+ max_field_cutoff = 0.97
2068
+
2069
+ field = np.array(field)
2070
+ magnetization = np.array(magnetization)
2071
+
2072
+ # filter for the high field portion of each branch
2073
+
2074
+ high_field_index = np.where((np.abs(field) >= HF_cutoff*np.max(np.abs(field))) & (np.abs(field) <= max_field_cutoff*np.max(np.abs(field))))[0]
2075
+
2076
+ # invert points in the third quadrant to the first
2077
+ high_field = np.abs(field[high_field_index])
2078
+ high_field_magnetization = np.abs(magnetization[high_field_index])
2079
+
2080
+ # the slope would be the paramagnetic/diamagnetic susceptibility
2081
+ # the y-intercept would be the Ms value (saturation magnetization of the ferromagnetic component)
2082
+ slope, intercept = np.polyfit(high_field, high_field_magnetization, 1)
2083
+ chi_HF = slope * (4*np.pi/1e7)
2084
+ return chi_HF, intercept
2085
+
2086
+ def hyst_slope_correction(grid_field, grid_magnetization, chi_HF):
2087
+ '''
2088
+ function for subtracting the paramagnetic/diamagnetic slope from a hysteresis loop
2089
+ the input should be gridded field and magnetization values
2090
+
2091
+ Parameters
2092
+ ----------
2093
+ grid_field : numpy array
2094
+ gridded field values
2095
+ grid_magnetization : numpy array
2096
+ gridded magnetization values
2097
+ chi_HF : float
2098
+ X_HF
2099
+
2100
+ Returns
2101
+ -------
2102
+ grid_magnetization_ferro: numpy array
2103
+ corrected ferromagnetic component of the magnetization
2104
+ '''
2105
+ slope = chi_HF / (4*np.pi/1e7)
2106
+ assert len(grid_field) == len(grid_magnetization), 'Field and magnetization arrays must be the same length'
2107
+
2108
+ grid_field = np.array(grid_field)
2109
+ grid_magnetization = np.array(grid_magnetization)
2110
+
2111
+ grid_magnetization_ferro = grid_magnetization - slope*grid_field
2112
+
2113
+ return grid_magnetization_ferro
2114
+
2115
+ def find_y_crossing(x, y, y_target=0.0):
2116
+ """
2117
+ Finds the x-value where y crosses a given y_target, nearest to x = 0.
2118
+ Uses linear interpolation between adjacent points that bracket y_target.
2119
+
2120
+ Parameters:
2121
+ x (array-like): x-values
2122
+ y (array-like): y-values
2123
+ y_target (float): y-value at which to find crossing (default: 0)
2124
+
2125
+ Returns:
2126
+ x_cross (float or None): interpolated x at y = y_target nearest to x = 0, or None if not found
2127
+ """
2128
+ x = np.asarray(x)
2129
+ y = np.asarray(y)
2130
+
2131
+ for i in range(len(x) - 1):
2132
+ y0, y1 = y[i], y[i + 1]
2133
+ if (y0 - y_target) * (y1 - y_target) < 0: # sign change => crossing
2134
+ x0, x1 = x[i], x[i + 1]
2135
+ # Linear interpolation to find x at y = y_target
2136
+ x_cross = x0 + (y_target - y0) * (x1 - x0) / (y1 - y0)
2137
+ return x_cross
2138
+
2139
+ return None
2140
+
2141
+ def calc_Mr_Mrh_Mih_Brh(grid_field, grid_magnetization):
2142
+ '''
2143
+ function to calculate the Mrh and Mih values from a hysteresis loop
2144
+
2145
+ Parameters
2146
+ ----------
2147
+ grid_field : numpy array
2148
+ gridded field values
2149
+ grid_magnetization : numpy array
2150
+ gridded magnetization values
2151
+
2152
+ Returns
2153
+ -------
2154
+ H : numpy array
2155
+ field values of the upper branch (the two branches should have the same field values)
2156
+ Mrh : float
2157
+ remanent magnetization value
2158
+ Mih : float
2159
+ induced magnetization value
2160
+ Me : numpy array
2161
+ error on M(H), calculated as the subtraction of the inverted lower branch from the upper branch
2162
+ Brh : float
2163
+ median field of Mrh
2164
+
2165
+ '''
2166
+ # calculate Mrh bu subtracting the upper and lower branches of a hysterisis loop
2167
+ grid_field = np.array(grid_field)
2168
+ grid_magnetization = np.array(grid_magnetization)
2169
+
2170
+ grid_field = grid_field
2171
+ grid_magnetization = grid_magnetization
2172
+
2173
+ upper_branch, lower_branch = split_hysteresis_loop(grid_field, grid_magnetization)
2174
+
2175
+ Mrh = (upper_branch[1] - lower_branch[1])/2
2176
+ Mih = (upper_branch[1] + lower_branch[1])/2
2177
+ Me = upper_branch[1] + lower_branch[1][::-1]
2178
+
2179
+ H = upper_branch[0]
2180
+ Mr = np.interp(0, H, Mrh)
2181
+
2182
+ # Brh is the field corresponding to the m=Mr/2
2183
+ pos_H = H[np.where(H > 0)]
2184
+ pos_Mrh = Mrh[np.where(H > 0)]
2185
+ neg_H = H[np.where(H < 0)]
2186
+ neg_Mrh = Mrh[np.where(H < 0)]
2187
+ Brh_pos = find_y_crossing(pos_H, pos_Mrh, Mr/2)
2188
+ Brh_neg = find_y_crossing(neg_H, neg_Mrh, Mr/2)
2189
+ Brh = np.abs((Brh_pos - Brh_neg)/2)
2190
+
2191
+ return H, Mr, Mrh, Mih, Me, Brh
2192
+
2193
+ def calc_Bc(H, M):
2194
+ '''
2195
+ function for calculating the coercivity of the ferromagnetic component of a hysteresis loop
2196
+ the final Bc value is calculated as the average of the positive and negative Bc values
2197
+
2198
+ Parameters
2199
+ ----------
2200
+ H : numpy array
2201
+ field values
2202
+ M : numpy array
2203
+ magnetization values
2204
+
2205
+ Returns
2206
+ -------
2207
+ Bc : float
2208
+ coercivity of the ferromagnetic component of the hysteresis loop
2209
+ '''
2210
+ upper_branch, lower_branch = split_hysteresis_loop(H, M)
2211
+
2212
+ upper_Bc = find_y_crossing(upper_branch[0], upper_branch[1])
2213
+ lower_Bc = find_y_crossing(lower_branch[0], lower_branch[1])
2214
+ Bc = np.abs((upper_Bc - lower_Bc) / 2)
2215
+
2216
+ return Bc
2217
+
2218
+ def loop_saturation_stats(field, magnetization, HF_cutoff=0.8, max_field_cutoff=0.97):
2219
+ '''
2220
+ ANOVA statistics for the high field portion of a hysteresis loop
2221
+
2222
+ Parameters
2223
+ ----------
2224
+ field : numpy array
2225
+ field values
2226
+ magnetization : numpy array
2227
+ magnetization values
2228
+ HF_cutoff : float
2229
+ high field cutoff value
2230
+ default is 0.8
2231
+
2232
+ Returns
2233
+ -------
2234
+ results : dict
2235
+ dictionary of the results of the ANOVA calculation
2236
+ and intermediate statistics for the ANOVA calculation
2237
+
2238
+ '''
2239
+ field = np.array(field)
2240
+ magnetization = np.array(magnetization)
2241
+ upper_branch, lower_branch = split_hysteresis_loop(field, magnetization)
2242
+ Me = upper_branch[1] + lower_branch[1][::-1]
2243
+ # filter for the high field portion of each branch
2244
+ pos_high_field_index = np.where((field >= HF_cutoff*np.max(np.abs(field))) & (field <= max_field_cutoff*np.max(np.abs(field))))[0]
2245
+ neg_high_field_index = np.where((field <= -HF_cutoff*np.max(np.abs(field))) & (field >= -max_field_cutoff*np.max(np.abs(field))))[0]
2246
+
2247
+ # invert points in the third quadrant to the first
2248
+ pos_high_field = field[pos_high_field_index]
2249
+ pos_high_field_magnetization = magnetization[pos_high_field_index]
2250
+
2251
+ neg_high_field = field[neg_high_field_index]
2252
+ neg_high_field_magnetization = magnetization[neg_high_field_index]
2253
+ neg_high_field = -np.array(neg_high_field)
2254
+ neg_high_field_magnetization = -np.array(neg_high_field_magnetization)
2255
+
2256
+ high_field = np.concatenate([pos_high_field, neg_high_field])
2257
+ high_field_magnetization = np.concatenate([pos_high_field_magnetization, neg_high_field_magnetization])
2258
+
2259
+ anova_results = ANOVA(high_field, high_field_magnetization)
2260
+ SST = anova_results['SST']
2261
+ SSR = anova_results['SSR']
2262
+ SSD = anova_results['SSD']
2263
+ R_squared = anova_results['R_squared']
2264
+
2265
+ SSPE = np.sum((upper_branch[1] - (-lower_branch[1][::-1])) ** 2) / 2
2266
+ SSLF = SSD - SSPE
2267
+ MSR = SSR
2268
+ MSD = SSD / (len(high_field) - 2)
2269
+ MSPE = SSPE / (len(high_field) / 2)
2270
+ MSLF = SSLF / (len(high_field)/2 - 2)
2271
+
2272
+ FL = MSR / MSD
2273
+ FNL = MSLF / MSPE
2274
+
2275
+ results = {'SST':SST,
2276
+ 'SSR':SSR,
2277
+ 'SSD':SSD,
2278
+ 'R_squared': R_squared,
2279
+ 'SSPE':SSPE,
2280
+ 'SSLF':SSLF,
2281
+ 'MSPE':MSPE,
2282
+ 'MSR':MSR,
2283
+ 'MSD':MSD,
2284
+ 'FL':FL,
2285
+ 'FNL':FNL}
2286
+ return results
2287
+
2288
+
2289
+ def hyst_loop_saturation_test(grid_field, grid_magnetization, max_field_cutoff=0.97):
2290
+ '''
2291
+ function for testing the saturation of a hysteresis loop
2292
+ which is based on the testing of linearity of the loop in field ranges of 60%, 70%, and 80% of the maximum field (<97%)
2293
+ '''
2294
+
2295
+ FNL60 = loop_saturation_stats(grid_field, grid_magnetization, HF_cutoff=0.6, max_field_cutoff = max_field_cutoff)['FNL']
2296
+ FNL70 = loop_saturation_stats(grid_field, grid_magnetization, HF_cutoff=0.7, max_field_cutoff = max_field_cutoff)['FNL']
2297
+ FNL80 = loop_saturation_stats(grid_field, grid_magnetization, HF_cutoff=0.8, max_field_cutoff = max_field_cutoff)['FNL']
2298
+
2299
+ saturation_cutoff = 0
2300
+ if (FNL80 > 2.5) & (FNL70 > 2.5) & (FNL60 > 2.5):
2301
+ saturation_cutoff = 0.92 # IRM default
2302
+ else:
2303
+ if FNL80 < 2.5: #saturated at 80%
2304
+ saturation_cutoff = 0.8
2305
+ if FNL70 < 2.5: #saturated at 70%
2306
+ saturation_cutoff = 0.7
2307
+ if FNL60 < 2.5: #saturated at 60%
2308
+ saturation_cutoff = 0.6
2309
+ results = {'FNL60':FNL60, 'FNL70':FNL70, 'FNL80':FNL80, 'saturation_cutoff':saturation_cutoff, 'loop_is_saturated':(saturation_cutoff != 0.92)}
2310
+ results_dict = dict_in_native_python(results)
2311
+ return results_dict
2312
+
2313
+
2314
+ def loop_closure_test(H, Mrh, HF_cutoff=0.8):
2315
+ '''
2316
+ function for testing if the loop is open
2317
+
2318
+ Parameters
2319
+ ----------
2320
+ H: array-like
2321
+ field values
2322
+ Mrh: array-like
2323
+ remanence componentt
2324
+ HF_cutoff: float
2325
+ high field cutoff value taken as percentage of the max field value
2326
+
2327
+ Returns
2328
+ -------
2329
+ SNR: float
2330
+ high field signal to noise ratio
2331
+ HAR: float
2332
+ high field area ratio
2333
+ '''
2334
+ assert len(H) == len(Mrh), 'H, Mrh must have the same length'
2335
+ pos_H_index = np.where(H > 0)
2336
+ neg_H_index = np.where(H < 0)
2337
+ pos_H = H[pos_H_index]
2338
+ neg_H = H[neg_H_index]
2339
+ pos_Mrh = Mrh[pos_H_index]
2340
+ neg_Mrh = Mrh[neg_H_index]
2341
+
2342
+ pos_HF_index = np.where(H > HF_cutoff*np.max(H))
2343
+ neg_HF_index = np.where(H < -HF_cutoff*np.max(H))
2344
+ pos_HF = H[pos_HF_index]
2345
+ neg_HF = H[neg_HF_index]
2346
+ pos_HF_Mrh = Mrh[pos_HF_index]
2347
+ neg_HF_Mrh = Mrh[neg_HF_index]
2348
+
2349
+ average_Mrh = (pos_Mrh - neg_Mrh[::-1])/2
2350
+ # replace all negative values with 0
2351
+ average_Mrh[average_Mrh < 0] = 0
2352
+ average_HF_Mrh = (pos_HF_Mrh - neg_HF_Mrh[::-1])/2
2353
+ # replace all negative values with 0
2354
+ average_HF_Mrh[average_HF_Mrh < 0] = 0
2355
+ HF_Mrh_noise = pos_HF_Mrh + neg_HF_Mrh[::-1]
2356
+ # replace all negative values with 0
2357
+ HF_Mrh_noise[HF_Mrh_noise < 0] = 0
2358
+
2359
+ HF_Mrh_signal_RMS = np.sqrt(np.mean(average_HF_Mrh**2))
2360
+ HF_Mrh_noise_RMS = np.sqrt(np.mean(HF_Mrh_noise**2))
2361
+ SNR = 20*np.log10(HF_Mrh_signal_RMS/HF_Mrh_noise_RMS)
2362
+
2363
+ total_Mrh_area = np.trapz(pos_Mrh, pos_H) + np.trapz(-neg_Mrh[::-1], -neg_H[::-1])
2364
+ total_HF_Mrh_area = np.trapz(average_Mrh, pos_H)
2365
+
2366
+ HAR = 20*np.log10(total_HF_Mrh_area/total_Mrh_area)
2367
+ loop_is_closed = (SNR < 8) or (HAR < -48)
2368
+
2369
+ results = {'SNR':float(SNR),
2370
+ 'HAR':float(HAR),
2371
+ 'loop_is_closed':bool(loop_is_closed),
2372
+ }
2373
+ return results
2374
+
2375
+
2376
+ def drift_correction_Me(H, M):
2377
+ '''
2378
+ default IRM drift correction algorithm based on Me
2379
+
2380
+ Parameters
2381
+ ----------
2382
+ H : numpy array
2383
+ field values
2384
+ M : numpy array
2385
+ magnetization values
2386
+ '''
2387
+ # split loop branches
2388
+ upper_branch, lower_branch = split_hysteresis_loop(H, M)
2389
+ # calculate Me
2390
+ Me = upper_branch[1][::-1] + lower_branch[1]
2391
+
2392
+ loop_size = len(H) -1
2393
+ half_loop_size = loop_size // 2
2394
+ quarter_loop_size = loop_size // 4
2395
+ # calculate the smoothed Me using Savitzky-Golay filter
2396
+ # which allows inplementation of a polynomial fit to the data within each window
2397
+ smoothed_Me = savgol_filter(Me, window_length=11, polyorder=2, mode='interp')
2398
+ # determine whether the main drift field region
2399
+ main_drift_region = H[np.argmax(np.abs(smoothed_Me[:half_loop_size]))]
2400
+
2401
+ M_cor = copy.deepcopy(M)
2402
+ positive_field_cor = abs(main_drift_region) > np.max(H) * 0.75
2403
+
2404
+ if positive_field_cor:
2405
+ # if the ratio of drift in the high-field range (≥75% of the peak field) to the low-field range.
2406
+ # is high, then the positive field correction is applied
2407
+ for i in range(0, quarter_loop_size):
2408
+ M_cor[i] -= smoothed_Me[i]
2409
+ M_cor[loop_size - i] -= smoothed_Me[half_loop_size - i]
2410
+
2411
+ return M_cor
2412
+ else:
2413
+ # if positive field correctionis not preferred, we do upper branch drift correction
2414
+ window_size = 7
2415
+ # calculate running mean of the upper branch with a window size of 2k+1
2416
+ kernel = np.ones(window_size) / window_size
2417
+ Me_running_mean = np.convolve(Me, kernel, mode='same')
2418
+
2419
+ for i in range(len(Me_running_mean)):
2420
+
2421
+ M_cor[i] = M[i] - Me_running_mean[i]
2422
+ return M_cor
2423
+
2424
+ def prorated_drift_correction(field, magnetization):
2425
+ '''
2426
+ function to correct for the linear drift of a hysteresis loop
2427
+ take the difference between the magnetization measured at the maximum field on the upper and lower branches
2428
+ apply linearly prorated correction of M(H)
2429
+ this should be applied to the gridded data
2430
+
2431
+ Parameters
2432
+ ----------
2433
+ field : numpy array
2434
+ field values
2435
+ magnetization : numpy array
2436
+ magnetization values
2437
+
2438
+ Returns
2439
+ -------
2440
+ corrected_magnetization : numpy array
2441
+ corrected magnetization values
2442
+ '''
2443
+
2444
+ field = np.array(field)
2445
+ magnetization = np.array(magnetization)
2446
+ upper_branch, lower_branch = split_hysteresis_loop(field, magnetization)
2447
+
2448
+ # find the maximum field values for the upper and lower branches
2449
+ upper_branch_max_idx = np.argmax(upper_branch[0])
2450
+ lower_branch_max_idx = np.argmax(lower_branch[0])
2451
+
2452
+ # find the difference between the magnetization values at the maximum field values
2453
+ M_ce = upper_branch[1][upper_branch_max_idx] - lower_branch[1][lower_branch_max_idx]
2454
+
2455
+ # apply linearly prorated correction of M(H)
2456
+ corrected_magnetization = [M_ce * ((i-1)/(len(field)-1) - 1/2) + magnetization[i] for i in range(len(field))]
2457
+
2458
+ return np.array(corrected_magnetization)
2459
+
2460
+ def symmetric_averaging_drift_corr(field, magnetization):
2461
+
2462
+ field = np.array(field)
2463
+ magnetization = np.array(magnetization)
2464
+
2465
+ upper_branch, lower_branch = split_hysteresis_loop(field, magnetization)
2466
+
2467
+ # average the upper and inverted lower branches
2468
+ averaged_upper_branch = (upper_branch[1] - lower_branch[1][::-1]) / 2
2469
+
2470
+ # calculate tip-to-tip separation from both the upper and lower branches
2471
+ tip_to_tip_separation = (upper_branch[1][0] - lower_branch[1][0] + upper_branch[1][-1] - lower_branch[1][-1]) / 4
2472
+ # apply the tip-to-tip separation to the upper branch
2473
+ corrected_magnetization = averaged_upper_branch - tip_to_tip_separation
2474
+
2475
+ # append back in the lower branch which should just be the inverted corrected upper branch
2476
+ corrected_magnetization = np.concatenate([corrected_magnetization[::-1], -corrected_magnetization[::-1]])
2477
+
2478
+ return corrected_magnetization
2479
+
2480
+ def IRM_nonlinear_fit(H, chi_HF, Ms, a_1, a_2):
2481
+ '''
2482
+ function for calculating the IRM non-linear fit
2483
+
2484
+ Parameters
2485
+ ----------
2486
+ H : numpy array
2487
+ field values
2488
+ chi_HF : float
2489
+ high field susceptibility, converted to Tesla to match the unit of the field
2490
+ Ms : float
2491
+ saturation magnetization
2492
+ a_1 : float
2493
+ coefficient for H^(-1), needs to be negative
2494
+ a_2 : float
2495
+ coefficient for H^(-2), needs to be negative
2496
+
2497
+ '''
2498
+ chi_HF = chi_HF/(4*np.pi/1e7)
2499
+ return chi_HF * H + Ms + a_1 * H**(-1) + a_2 * H**(-2)
2500
+
2501
+ def IRM_nonlinear_fit_cost_function(params, H, M_obs):
2502
+ '''
2503
+ cost function for the IRM non-linear least squares fit optimization
2504
+
2505
+ Parameters
2506
+ ----------
2507
+ params : numpy array
2508
+ array of parameters to optimize
2509
+ H : numpy array
2510
+ field values
2511
+ M_obs : numpy array
2512
+ observed magnetization values
2513
+
2514
+ Returns
2515
+ -------
2516
+ residual : numpy array
2517
+ residual between the observed and predicted magnetization values
2518
+ '''
2519
+
2520
+ chi_HF, Ms, a_1, a_2 = params
2521
+ prediction = IRM_nonlinear_fit(H, chi_HF, Ms, a_1, a_2)
2522
+ return M_obs - prediction
2523
+
2524
+ def Fabian_nonlinear_fit(H, chi_HF, Ms, alpha, beta):
2525
+ '''
2526
+ function for calculating the Fabian non-linear fit
2527
+
2528
+ Parameters
2529
+ ----------
2530
+ H : numpy array
2531
+ field values
2532
+ chi_HF : float
2533
+ high field susceptibility
2534
+ Ms : float
2535
+ saturation magnetization
2536
+ alpha : float
2537
+ coefficient for H^(beta), needs to be negative
2538
+ beta : float
2539
+ coefficient for H^(beta), needs to be negative
2540
+
2541
+ '''
2542
+ chi_HF = chi_HF/(4*np.pi/1e7) # convert to Tesla
2543
+ return chi_HF * H + Ms + alpha * H**beta
2544
+
2545
+ def Fabian_nonlinear_fit_cost_function(params, H, M_obs):
2546
+ '''
2547
+ cost function for the Fabian non-linear least squares fit optimization
2548
+
2549
+ Parameters
2550
+ ----------
2551
+ params : numpy array
2552
+ array of parameters to optimize
2553
+ H : numpy array
2554
+ field values
2555
+ M_obs : numpy array
2556
+ observed magnetization values
2557
+
2558
+ Returns
2559
+ -------
2560
+ residual : numpy array
2561
+ residual between the observed and predicted magnetization values
2562
+ '''
2563
+
2564
+ chi_HF, Ms, alpha, beta = params
2565
+ prediction = Fabian_nonlinear_fit(H, chi_HF, Ms, alpha, beta)
2566
+ return M_obs - prediction
2567
+
2568
+ def Fabian_nonlinear_fit_fix_beta_cost_function(params, H, M_obs):
2569
+ '''
2570
+ cost function for the Fabian non-linear least squares fit optimization
2571
+ with beta fixed at -2
2572
+
2573
+ Parameters
2574
+ ----------
2575
+ params : numpy array
2576
+ array of parameters to optimize
2577
+ H : numpy array
2578
+ field values
2579
+ M_obs : numpy array
2580
+ observed magnetization values
2581
+
2582
+ Returns
2583
+ -------
2584
+ residual : numpy array
2585
+ residual between the observed and predicted magnetization values
2586
+ '''
2587
+ beta = -2
2588
+ chi_HF, Ms, alpha = params
2589
+ prediction = Fabian_nonlinear_fit(H, chi_HF, Ms, alpha, beta)
2590
+ return M_obs - prediction
2591
+
2592
+ def hyst_HF_nonlinear_optimization(H, M, HF_cutoff, fit_type, initial_guess=[1, 1, -0.1, -0.1], bounds=([0, 0, -np.inf, -np.inf], [np.inf, np.inf, 0, 0])):
2593
+ '''
2594
+ Optimize a high-field nonlinear fit
2595
+
2596
+ Parameters
2597
+ ----------
2598
+ H : numpy.ndarray
2599
+ Array of field values.
2600
+ M : numpy.ndarray
2601
+ Array of magnetization values.
2602
+ HF_cutoff : float
2603
+ Fraction of max(|H|) defining the lower bound of the high-field region.
2604
+ fit_type : {'IRM', 'Fabian', 'Fabian_fixed_beta'}
2605
+ Type of nonlinear model to fit.
2606
+ initial_guess : list of float, optional
2607
+ Initial parameter guess for the optimizer.
2608
+ Defaults to [1, 1, -0.1, -0.1]:
2609
+ χ_HF = 1, Mₛ = 1, a₁ = –0.1, a₂ = –0.1 (or α, β for Fabian).
2610
+ bounds : tuple of array-like, optional
2611
+ Lower and upper bounds for each parameter.
2612
+ Defaults to ([0, 0, -∞, -∞], [∞, ∞, 0, 0]):
2613
+ - Lower: χ_HF ≥ 0, Mₛ ≥ 0, a₁ ≥ –∞, a₂ ≥ –∞
2614
+ - Upper: χ_HF ≤ ∞, Mₛ ≤ ∞, a₁ ≤ 0, a₂ ≤ 0
2615
+ (for Fabian, α and β follow the same positions/limits).
2616
+
2617
+ Returns
2618
+ -------
2619
+ dict
2620
+ Fit results with keys:
2621
+ - 'chi_HF', 'Ms', 'a_1', 'a_2' (for IRM) or
2622
+ 'chi_HF', 'Ms', 'alpha', 'beta' (for Fabian variants)
2623
+ - 'Fnl_lin': float, ratio comparing nonlinear vs. linear fit
2624
+ '''
2625
+ HF_index = np.where((np.abs(H) >= HF_cutoff*np.max(np.abs(H))) & (np.abs(H) <= 0.97*np.max(np.abs(H))))[0]
2626
+
2627
+ HF_field = np.abs(H[HF_index])
2628
+ HF_magnetization = np.abs(M[HF_index])
2629
+
2630
+ if fit_type == 'IRM':
2631
+ cost_function = IRM_nonlinear_fit_cost_function
2632
+ results = least_squares(cost_function, initial_guess, bounds=bounds, args=(HF_field, HF_magnetization))
2633
+ elif fit_type == 'Fabian':
2634
+ cost_function = Fabian_nonlinear_fit_cost_function
2635
+ results = least_squares(cost_function, initial_guess, bounds=bounds, args=(HF_field, HF_magnetization))
2636
+ elif fit_type == 'Fabian_fixed_beta':
2637
+ cost_function = Fabian_nonlinear_fit_fix_beta_cost_function
2638
+ results = least_squares(cost_function, initial_guess[:3], bounds=(bounds[0][:3], bounds[1][:3]), args=(HF_field, HF_magnetization))
2639
+ else:
2640
+ raise ValueError('Fit type must be either IRM or Fabian')
2641
+
2642
+ if fit_type == 'IRM':
2643
+ final_result = {'chi_HF': results.x[0], 'Ms': results.x[1], 'a_1': results.x[2], 'a_2': results.x[3]}
2644
+ chi_HF, Ms, a_1, a_2 = results.x
2645
+ nonlinear_fit = IRM_nonlinear_fit(HF_field, chi_HF, Ms, a_1, a_2)
2646
+ elif fit_type == 'Fabian':
2647
+ final_result = {'chi_HF': results.x[0], 'Ms': results.x[1], 'alpha': results.x[2], 'beta': results.x[3]}
2648
+ chi_HF, Ms, alpha, beta = results.x
2649
+ nonlinear_fit = Fabian_nonlinear_fit(HF_field, chi_HF, Ms, alpha, beta)
2650
+ elif fit_type == 'Fabian_fixed_beta':
2651
+ final_result = {'chi_HF': results.x[0], 'Ms': results.x[1], 'alpha': results.x[2], 'beta': -2}
2652
+ chi_HF, Ms, alpha = results.x
2653
+ nonlinear_fit = Fabian_nonlinear_fit(HF_field, chi_HF, Ms, alpha, -2)
2654
+
2655
+ # let's also report the Fnl_lin which is a measure of whether the nonlinear fit is better than a linear fit
2656
+ # let's first make a linear fit
2657
+ linear_fit_ANOVA = ANOVA(HF_field, HF_magnetization)
2658
+ R_squared_l = 1 - linear_fit_ANOVA['R_squared']
2659
+
2660
+ # now calculate the nonlinear fit SST
2661
+ nl_SST = np.sum((HF_magnetization - np.mean(HF_magnetization)) ** 2)
2662
+
2663
+ # sum of squares due to regression
2664
+ nl_SSR = np.sum((nonlinear_fit - np.mean(HF_magnetization)) ** 2)
2665
+
2666
+ # the remaining unexplained variation (noise and lack of fit)
2667
+ nl_SSD = np.sum((HF_magnetization - nonlinear_fit) ** 2)
2668
+
2669
+ R_squared_nl = 1 - nl_SSR/nl_SST
2670
+
2671
+ # calculate the Fnl_lin stat
2672
+ Fnl_lin = R_squared_l / R_squared_nl
2673
+
2674
+ final_result['Fnl_lin'] = Fnl_lin
2675
+ final_result_dict = dict_in_native_python(final_result)
2676
+ return final_result_dict
2677
+
2678
+
2679
+ def process_hyst_loop(field, magnetization, specimen_name, show_results_table=True):
2680
+ '''
2681
+ function to process a hysteresis loop following the IRM decision tree
2682
+ Parameters
2683
+ ----------
2684
+ field : array
2685
+ array of field values
2686
+ magnetization : array
2687
+ array of magnetization values
2688
+
2689
+ Returns
2690
+ -------
2691
+ results : dict
2692
+ dictionary with the hysteresis processing results and a Bokeh plot
2693
+ '''
2694
+ # first grid the data into symmetric field values
2695
+ grid_fields, grid_magnetizations = grid_hysteresis_loop(field, magnetization)
2696
+
2697
+ # test linearity of the gridded original loop
2698
+ loop_linearity_test_results = hyst_linearity_test(grid_fields, grid_magnetizations)
2699
+
2700
+ # loop centering
2701
+ loop_centering_results = hyst_loop_centering(grid_fields, grid_magnetizations)
2702
+ # check if the quality factor Q is < 2
2703
+ if loop_centering_results['Q'] < 2:
2704
+ # in case the loop quality is bad, no field correction is applied
2705
+ loop_centering_results['opt_H_offset'] = 0
2706
+ loop_centering_results['centered_H'] = grid_fields
2707
+
2708
+ centered_H, centered_M = loop_centering_results['centered_H'], loop_centering_results['centered_M']
2709
+
2710
+ # drift correction
2711
+ drift_corr_M = drift_correction_Me(centered_H, centered_M)
2712
+
2713
+ # calculate Mr, Mrh, Mih, Me, Brh
2714
+ H, Mr, Mrh, Mih, Me, Brh = calc_Mr_Mrh_Mih_Brh(centered_H, drift_corr_M)
2715
+
2716
+ # check if the loop is closed
2717
+ loop_closure_test_results = loop_closure_test(H, Mrh)
2718
+
2719
+ # check if the loop is saturated (high field linearity test)
2720
+ loop_saturation_stats = hyst_loop_saturation_test(centered_H, drift_corr_M)
2721
+
2722
+ if loop_saturation_stats['loop_is_saturated']:
2723
+ # linear high field correction
2724
+ chi_HF, Ms = linear_HF_fit(centered_H, drift_corr_M, loop_saturation_stats['saturation_cutoff'])
2725
+ Fnl_lin = None
2726
+ else:
2727
+ # do non linear approach to saturation fit
2728
+ NL_fit_result = hyst_HF_nonlinear_optimization(centered_H, drift_corr_M, 0.6, 'IRM')
2729
+ chi_HF, Ms, Fnl_lin = NL_fit_result['chi_HF'], NL_fit_result['Ms'], NL_fit_result['Fnl_lin']
2730
+
2731
+ # apply high field correction
2732
+ slope_corr_M = hyst_slope_correction(centered_H, drift_corr_M, chi_HF)
2733
+
2734
+ # calculate the Msn and Q factor for the ferromagentic component
2735
+ M_sn_f, Qf = calc_Q(centered_H, slope_corr_M)
2736
+
2737
+ # calculate the coercivity Bc
2738
+ Bc = calc_Bc(centered_H, slope_corr_M)
2739
+
2740
+ # calculate the shape parameter of Fabian 2003
2741
+ E_hyst = np.trapz(Mrh, H)
2742
+ sigma = np.log(E_hyst / 2 / Bc / Ms)
2743
+
2744
+ # plot original loop
2745
+ p = plot_hysteresis_loop(grid_fields, grid_magnetizations, specimen_name, line_color='orange', label='raw loop')
2746
+ # plot centered loop
2747
+ p_centered = plot_hysteresis_loop(centered_H, centered_M, specimen_name, p=p, line_color='red', label=specimen_name+' offset corrected')
2748
+ # plot drift corrected loop
2749
+ p_drift_corr = plot_hysteresis_loop(centered_H, drift_corr_M, specimen_name, p=p_centered, line_color='pink', label=specimen_name+' drift corrected')
2750
+ # plot slope corrected loop
2751
+ p_slope_corr = plot_hysteresis_loop(centered_H, slope_corr_M, specimen_name, p=p_drift_corr, line_color='blue', label=specimen_name+' slope corrected')
2752
+ # plot Mrh
2753
+ p_slope_corr.line(H, Mrh, line_color='green', legend_label='Mrh', line_width=1)
2754
+ p_slope_corr.line(H, Mih, line_color='purple', legend_label='Mih', line_width=1)
2755
+ p_slope_corr.line(H, Me, line_color='brown', legend_label='Me', line_width=1)
2756
+ show(p)
2757
+ results = {'gridded_H': grid_fields,
2758
+ 'gridded_M': grid_magnetizations,
2759
+ 'linearity_test_results': loop_linearity_test_results,
2760
+ 'loop_is_linear': loop_linearity_test_results['loop_is_linear'],
2761
+ 'FNL': loop_linearity_test_results['FNL'],
2762
+ 'loop_centering_results': loop_centering_results,
2763
+ 'centered_H': centered_H,
2764
+ 'centered_M': centered_M,
2765
+ 'drift_corrected_M': drift_corr_M,
2766
+ 'slope_corrected_M': slope_corr_M,
2767
+ 'loop_closure_test_results': loop_closure_test_results,
2768
+ 'loop_is_closed': loop_closure_test_results['loop_is_closed'],
2769
+ 'loop_saturation_stats': loop_saturation_stats,
2770
+ 'loop_is_saturated': loop_saturation_stats['loop_is_saturated'],
2771
+ 'M_sn':loop_centering_results['M_sn'],
2772
+ 'Q': loop_centering_results['Q'],
2773
+ 'H': H, 'Mr': Mr, 'Mrh': Mrh,
2774
+ 'Mih': Mih, 'Me': Me, 'Brh': Brh, 'sigma': sigma,
2775
+ 'chi_HF': chi_HF,
2776
+ 'FNL60': loop_saturation_stats['FNL60'],
2777
+ 'FNL70': loop_saturation_stats['FNL70'],
2778
+ 'FNL80': loop_saturation_stats['FNL80'],
2779
+ 'Ms': Ms, 'Bc': Bc, 'M_sn_f': M_sn_f,
2780
+ 'Qf': Qf, 'Fnl_lin': Fnl_lin,
2781
+ 'plot': p}
2782
+
2783
+ if show_results_table:
2784
+ summary = {
2785
+ 'Mr': [Mr],
2786
+ 'Ms': [Ms],
2787
+ 'Bc': [Bc],
2788
+ 'Brh': [Brh],
2789
+ 'sigma': [sigma],
2790
+ 'Q': [loop_centering_results['Q']],
2791
+ 'Qf': [Qf],
2792
+ 'chi_HF':[chi_HF],
2793
+ 'FNL60': [loop_saturation_stats['FNL60']],
2794
+ 'FNL70': [loop_saturation_stats['FNL70']],
2795
+ 'FNL80': [loop_saturation_stats['FNL80']],
2796
+ }
2797
+ src = ColumnDataSource(summary)
2798
+ cols = [
2799
+ TableColumn(field=param, title=param)
2800
+ for param in summary
2801
+ ]
2802
+ data_table = DataTable(
2803
+ source=src, columns=cols,
2804
+ width=p_slope_corr.width, height=100
2805
+ )
2806
+ data_table.index_position = None
2807
+ layout = column(data_table)
2808
+ show(layout)
2809
+ return results
2810
+
2811
+ def process_hyst_loops(
2812
+ hyst_experiments,
2813
+ measurements,
2814
+ field_col="meas_field_dc",
2815
+ magn_col="magn_mass",
2816
+ show_results_table=True,
2817
+ ):
2818
+ """
2819
+ Process multiple hysteresis loops in batch.
2820
+
2821
+ Parameters
2822
+ ----------
2823
+ hyst_experiments : DataFrame
2824
+ Must contain columns "experiment" and "specimen".
2825
+ measurements : DataFrame
2826
+ Must contain an "experiment" column and the data columns.
2827
+ field_col : str, optional
2828
+ Name of the column in `measurements` holding field values.
2829
+ Defaults to "meas_field_dc".
2830
+ magn_col : str, optional
2831
+ Name of the column in `measurements` holding magnetization values.
2832
+ Defaults to "magn_mass".
2833
+ show_results_table : bool, optional
2834
+ If True, display the summary table below each plot.
2835
+
2836
+ Returns
2837
+ -------
2838
+ list of dict
2839
+ Each dict is the output of `process_hyst_loop` for one specimen.
2840
+ """
2841
+ results = []
2842
+ for _, row in hyst_experiments.iterrows():
2843
+ exp = row["experiment"]
2844
+ spec = row["specimen"]
2845
+ df = (
2846
+ measurements[measurements["experiment"] == exp]
2847
+ .reset_index(drop=True)
2848
+ )
2849
+ res = process_hyst_loop(
2850
+ df[field_col].values,
2851
+ df[magn_col].values,
2852
+ spec,
2853
+ show_results_table=show_results_table,
2854
+ )
2855
+ results.append(res)
2856
+ return results
2857
+
2858
+
2859
+ def add_hyst_stats_to_specimens_table(specimens_df, experiment_name, hyst_results):
2860
+ '''
2861
+ function to export the hysteresis data to a MagIC specimen data table
2862
+
2863
+ Parameters
2864
+ ----------
2865
+ specimens_df : pandas.DataFrame
2866
+ dataframe with the specimens data
2867
+ experiment_name : str
2868
+ name of the experiment
2869
+ hyst_results : dict
2870
+ dictionary with the hysteresis data
2871
+ as output from the rmag.process_hyst_loop function
2872
+
2873
+ updates the specimen table in place
2874
+ '''
2875
+
2876
+ result_keys_MagIC = ['specimen',
2877
+ 'Ms',
2878
+ 'Mr',
2879
+ 'Bc',
2880
+ 'chi_HF']
2881
+
2882
+ MagIC_columns=['specimen',
2883
+ 'hyst_ms_mass',
2884
+ 'hyst_mr_mass',
2885
+ 'hyst_bc',
2886
+ 'hyst_xhf']
2887
+
2888
+ # add MagIC available columns to the specimens table
2889
+ for result_key, col in zip(result_keys_MagIC, MagIC_columns):
2890
+ if col not in specimens_df.columns:
2891
+ # add the column to the specimens table
2892
+ specimens_df[col] = np.nan
2893
+
2894
+ specimens_df.loc[specimens_df['experiments'] == experiment_name, col] = hyst_results[result_key]
2895
+
2896
+ # dump the rest of the stats to the description column
2897
+ additional_keys = ['Q', 'Qf', 'sigma',
2898
+ 'Brh', 'FNL', 'FNL60', 'FNL70', 'FNL80',
2899
+ 'Fnl_lin', 'loop_is_linear', 'loop_is_closed', 'loop_is_saturated']
2900
+ additional_stats_dict = {}
2901
+ for key in additional_keys:
2902
+ additional_stats_dict[key] = hyst_results[key]
2903
+
2904
+ # check if the description cell is type string
2905
+ if isinstance(specimens_df[specimens_df['experiments'] == experiment_name]['description'].iloc[0], str):
2906
+ # unpack the string to a dict, then add the new stats, then pack it back to a string
2907
+ description_dict = eval(specimens_df[specimens_df['experiments'] == experiment_name]['description'].iloc[0])
2908
+ for key in additional_keys:
2909
+ if key in description_dict:
2910
+ # if the key already exists, update it
2911
+ description_dict[key] = hyst_results[key]
2912
+ else:
2913
+ # if the key does not exist, add it
2914
+ description_dict[key] = hyst_results[key]
2915
+ # pack the dict back to a string
2916
+ specimens_df.loc[specimens_df['experiments'] == experiment_name, 'description'] = str(description_dict)
2917
+ else:
2918
+ # if not, create a new dict
2919
+ specimens_df.loc[specimens_df['experiments'] == experiment_name, 'description'] = str(additional_stats_dict)
2920
+ return
2921
+
2922
+ # X-T functions
2923
+ # ------------------------------------------------------------------------------------------------------------------
2924
+
2925
+ def split_warm_cool(experiment,temperature_column='meas_temp',
2926
+ magnetic_column='susc_chi_mass'):
2927
+ """
2928
+ Split a thermomagnetic curve into heating and cooling portions. Default
2929
+ columns are 'meas_temp' and 'susc_chi_mass' for susceptibility measurements.
2930
+ Funcation can also be used for other warming then cooling data.
2931
+
2932
+ Parameters
2933
+ ----------
2934
+ experiment : pandas.DataFrame
2935
+ the experiment data
2936
+ temperature_column : str, optional
2937
+ name of the temperature column (default 'meas_temp')
2938
+ magnetic_column : str, optional
2939
+ name of the magnetization/susceptibility column
2940
+ (default 'susc_chi_mass')
2941
+
2942
+ Returns
2943
+ -------
2944
+ warm_T : list[float]
2945
+ temperatures for the heating cycle
2946
+ warm_X : list[float]
2947
+ magnetization/susceptibility for the heating cycle
2948
+ cool_T : list[float]
2949
+ temperatures for the cooling cycle
2950
+ cool_X : list[float]
2951
+ magnetization/susceptibility for the cooling cycle
2952
+ """
2953
+ Tlist = experiment[temperature_column] # temperature list
2954
+ Xlist = experiment[magnetic_column] # Chi list
2955
+
2956
+ warmorcool = np.array(np.insert((np.diff(Tlist) > 0 )* 1, 0, 1))
2957
+ # print(warmorcool)
2958
+ warm_T = [Tlist[i] for i in range(len(warmorcool)) if warmorcool[i]==1]
2959
+ cool_T = [Tlist[i] for i in range(len(warmorcool)) if warmorcool[i]==0]
2960
+ warm_X = [Xlist[i] for i in range(len(warmorcool)) if warmorcool[i]==1]
2961
+ cool_X = [Xlist[i] for i in range(len(warmorcool)) if warmorcool[i]==0]
2962
+
2963
+ return warm_T, warm_X, cool_T, cool_X
2964
+
2965
+
2966
+ def plot_X_T(
2967
+ experiment,
2968
+ temperature_column="meas_temp",
2969
+ magnetic_column="susc_chi_mass",
2970
+ temp_unit="C",
2971
+ smooth_window=0,
2972
+ remove_holder=True,
2973
+ plot_derivative=True,
2974
+ plot_inverse=False,
2975
+ return_figure=False,
2976
+ ):
2977
+ """
2978
+ Plot the high-temperature X–T curve, and optionally its derivative
2979
+ and reciprocal using Bokeh.
2980
+
2981
+ Parameters:
2982
+ experiment (pandas.DataFrame):
2983
+ The IRM experiment data exported into MagIC format.
2984
+ temperature_column (str, optional):
2985
+ Name of the temperature column. Defaults to "meas_temp".
2986
+ magnetic_column (str, optional):
2987
+ Name of the susceptibility column. Defaults to "susc_chi_mass".
2988
+ temp_unit (str, optional):
2989
+ Unit of temperature, either "K" or "C". Defaults to "C".
2990
+ smooth_window (int, optional):
2991
+ Window size for running-average smoothing. Defaults to 0.
2992
+ remove_holder (bool, optional):
2993
+ If True, subtract the minimum holder signal. Defaults to True.
2994
+ plot_derivative (bool, optional):
2995
+ If True, generate dX/dT plot. Defaults to True.
2996
+ plot_inverse (bool, optional):
2997
+ If True, generate 1/X plot. Defaults to False.
2998
+ return_figure (bool, optional):
2999
+ If True, return the Bokeh figure objects. Defaults to False.
3000
+
3001
+ Returns:
3002
+ tuple[bokeh.plotting.figure.Figure, ...] or None:
3003
+ The requested Bokeh figures if return_figure is True;
3004
+ otherwise, None.
3005
+ """
3006
+ warm_T, warm_X, cool_T, cool_X = split_warm_cool(
3007
+ experiment,
3008
+ temperature_column=temperature_column,
3009
+ magnetic_column=magnetic_column,
3010
+ )
3011
+
3012
+ if temp_unit == "C":
3013
+ warm_T = [T - 273.15 for T in warm_T]
3014
+ cool_T = [T - 273.15 for T in cool_T]
3015
+ else:
3016
+ raise ValueError('temp_unit must be either "K" or "C"')
3017
+
3018
+ if remove_holder:
3019
+ holder_w = min(warm_X)
3020
+ holder_c = min(cool_X)
3021
+ warm_X = [X - holder_w for X in warm_X]
3022
+ cool_X = [X - holder_c for X in cool_X]
3023
+
3024
+ swT, swX = smooth_moving_avg(warm_T, warm_X, smooth_window)
3025
+ scT, scX = smooth_moving_avg(cool_T, cool_X, smooth_window)
3026
+
3027
+ width = 900
3028
+ height = int(width / 1.618)
3029
+ title = experiment["specimen"].unique()[0]
3030
+
3031
+ p = figure(
3032
+ title=title,
3033
+ width=width,
3034
+ height=height,
3035
+ x_axis_label=f"Temperature (°{temp_unit})",
3036
+ y_axis_label="k (m³ kg⁻¹)",
3037
+ tools="pan,wheel_zoom,box_zoom,reset,save",
3038
+ )
3039
+
3040
+ r_warm_c = p.scatter(
3041
+ warm_T, warm_X, legend_label="Heating",
3042
+ color="red", alpha=0.5, size=6,
3043
+ )
3044
+ r_warm_l = p.line(
3045
+ swT, swX, legend_label="Heating – smoothed",
3046
+ line_width=2, color="red",
3047
+ )
3048
+
3049
+ r_cool_c = p.scatter(
3050
+ cool_T, cool_X, legend_label="Cooling",
3051
+ color="blue", alpha=0.5, size=6,
3052
+ )
3053
+ r_cool_l = p.line(
3054
+ scT, scX, legend_label="Cooling – smoothed",
3055
+ line_width=2, color="blue",
3056
+ )
3057
+
3058
+ p.add_tools(
3059
+ HoverTool(renderers=[r_warm_c, r_warm_l],
3060
+ tooltips=[("T", "@x"), ("Heating X", "@y")])
3061
+ )
3062
+ p.add_tools(
3063
+ HoverTool(renderers=[r_cool_c, r_cool_l],
3064
+ tooltips=[("T", "@x"), ("Cooling X", "@y")])
3065
+ )
3066
+
3067
+ p.grid.grid_line_color = "lightgray"
3068
+ p.outline_line_color = "black"
3069
+ p.background_fill_color = "white"
3070
+ p.legend.location = "top_left"
3071
+
3072
+ figs = [p]
3073
+
3074
+ if plot_derivative:
3075
+ p_dx = figure(
3076
+ title=f"{title} – dX/dT",
3077
+ width=width,
3078
+ height=height,
3079
+ x_axis_label=f"Temperature (°{temp_unit})",
3080
+ y_axis_label="dX/dT",
3081
+ tools="pan,wheel_zoom,box_zoom,reset,save",
3082
+ )
3083
+ dx_w = np.gradient(swX, swT)
3084
+ dx_c = np.gradient(scX, scT)
3085
+ r_dx_w = p_dx.line(
3086
+ swT, dx_w, legend_label="Heating – dX/dT",
3087
+ line_width=2, color="red"
3088
+ )
3089
+ r_dx_c = p_dx.line(
3090
+ scT, dx_c, legend_label="Cooling – dX/dT",
3091
+ line_width=2, color="blue"
3092
+ )
3093
+ p_dx.add_tools(
3094
+ HoverTool(renderers=[r_dx_w],
3095
+ tooltips=[("T", "@x"), ("dX/dT (heat)", "@y")])
3096
+ )
3097
+ p_dx.add_tools(
3098
+ HoverTool(renderers=[r_dx_c],
3099
+ tooltips=[("T", "@x"), ("dX/dT (cool)", "@y")])
3100
+ )
3101
+ p_dx.grid.grid_line_color = "lightgray"
3102
+ p_dx.outline_line_color = "black"
3103
+ p_dx.background_fill_color = "white"
3104
+ p_dx.legend.location = "top_left"
3105
+ figs.append(p_dx)
3106
+
3107
+ if plot_inverse:
3108
+ p_inv = figure(
3109
+ title=f"{title} – 1/X",
3110
+ width=width,
3111
+ height=height,
3112
+ x_axis_label=f"Temperature (°{temp_unit})",
3113
+ y_axis_label="1/X",
3114
+ tools="pan,wheel_zoom,box_zoom,reset,save",
3115
+ )
3116
+ # compute inverse safely (zeros become NaN)
3117
+ swX_arr = np.array(swX)
3118
+ scX_arr = np.array(scX)
3119
+ inv_w = np.divide(1.0, swX_arr, out=np.full_like(swX_arr, np.nan), where=swX_arr != 0.0)
3120
+ inv_c = np.divide(1.0, scX_arr, out=np.full_like(scX_arr, np.nan), where=scX_arr != 0.0)
3121
+
3122
+ # mask to finite values only
3123
+ mask_w = np.isfinite(inv_w)
3124
+ mask_c = np.isfinite(inv_c)
3125
+
3126
+ # plot heating inverse
3127
+ r_inv_w = p_inv.line(
3128
+ np.array(swT)[mask_w],
3129
+ inv_w[mask_w],
3130
+ legend_label="Heating – 1/X",
3131
+ line_width=2, color="red",
3132
+ )
3133
+
3134
+ # plot cooling inverse
3135
+ r_inv_c = p_inv.line(
3136
+ np.array(scT)[mask_c],
3137
+ inv_c[mask_c],
3138
+ legend_label="Cooling – 1/X",
3139
+ line_width=2, color="blue",
3140
+ )
3141
+
3142
+ p_inv.add_tools(
3143
+ HoverTool(renderers=[r_inv_w],
3144
+ tooltips=[("T", "@x"), ("1/X (heat)", "@y")])
3145
+ )
3146
+ p_inv.add_tools(
3147
+ HoverTool(renderers=[r_inv_c],
3148
+ tooltips=[("T", "@x"), ("1/X (cool)", "@y")])
3149
+ )
3150
+ p_inv.grid.grid_line_color = "lightgray"
3151
+ p_inv.outline_line_color = "black"
3152
+ p_inv.background_fill_color = "white"
3153
+ p_inv.legend.location = "top_left"
3154
+ figs.append(p_inv)
3155
+
3156
+ for fig in figs:
3157
+ show(fig)
3158
+
3159
+ if return_figure:
3160
+ return tuple(figs)
3161
+ return None
3162
+
3163
+
3164
+ def smooth_moving_avg(
3165
+ x,
3166
+ y,
3167
+ x_window,
3168
+ window_type="hanning",
3169
+ pad_mode="edge",
3170
+ return_variance=False,
3171
+ ):
3172
+ """
3173
+ Smooth y vs x using an x-space moving window and numpy window functions.
3174
+
3175
+ Parameters:
3176
+ x (array-like):
3177
+ 1-D sequence of independent variable values.
3178
+ y (array-like):
3179
+ 1-D sequence of dependent variable values.
3180
+ x_window (float):
3181
+ Width of the x-window centered on each point; must be >= 0.
3182
+ If zero, no smoothing is applied.
3183
+ window_type (str, optional):
3184
+ One of ['flat', 'hanning', 'hamming', 'bartlett', 'blackman'].
3185
+ 'flat' is a simple running mean. Defaults to 'hanning'.
3186
+ pad_mode (str, optional):
3187
+ Mode for numpy.pad to reduce edge artifacts (e.g., 'edge',
3188
+ 'constant', 'nearest'). Defaults to 'edge'.
3189
+ return_variance (bool, optional):
3190
+ If True, return weighted variances of x and y as well.
3191
+ Otherwise, only return smoothed x and y. Defaults to False.
3192
+
3193
+ Returns:
3194
+ smoothed_x, smoothed_y (, x_var, y_var)
3195
+ """
3196
+ # convert to numpy arrays
3197
+ x = np.asarray(x)
3198
+ y = np.asarray(y)
3199
+
3200
+ # validate dimensions
3201
+ if x.ndim != 1 or y.ndim != 1 or x.size != y.size:
3202
+ raise ValueError("`x` and `y` must be 1-D arrays of equal length.")
3203
+
3204
+ # handle non-positive window
3205
+ if x_window < 0:
3206
+ raise ValueError("`x_window` must be non-negative.")
3207
+ if x_window == 0:
3208
+ if return_variance:
3209
+ x_var = np.zeros_like(x, dtype=float)
3210
+ y_var = np.zeros_like(y, dtype=float)
3211
+ return x, y, x_var, y_var
3212
+ return x, y
3213
+
3214
+ # always pad to handle edge effects
3215
+ pad_n = x.size
3216
+ x_arr = np.pad(x, pad_n, mode=pad_mode)
3217
+ y_arr = np.pad(y, pad_n, mode=pad_mode)
3218
+
3219
+ n = x.size
3220
+ sm_x = np.empty(n)
3221
+ sm_y = np.empty(n)
3222
+ if return_variance:
3223
+ x_var = np.empty(n)
3224
+ y_var = np.empty(n)
3225
+ half = x_window / 2.0
3226
+
3227
+ for i, center in enumerate(x):
3228
+ mask = (x_arr >= center - half) & (x_arr <= center + half)
3229
+ idx = np.nonzero(mask)[0]
3230
+ if idx.size:
3231
+ xx = x_arr[idx]
3232
+ yy = y_arr[idx]
3233
+ m = idx.size
3234
+ if window_type == "flat":
3235
+ w = np.ones(m)
3236
+ else:
3237
+ w = getattr(np, window_type)(m)
3238
+ wsum = w.sum()
3239
+ mean_x = (w * xx).sum() / wsum
3240
+ mean_y = (w * yy).sum() / wsum
3241
+ if return_variance:
3242
+ vx = (w * (xx - mean_x) ** 2).sum() / wsum
3243
+ vy = (w * (yy - mean_y) ** 2).sum() / wsum
3244
+ else:
3245
+ mean_x = center
3246
+ mean_y = y[i]
3247
+ if return_variance:
3248
+ vx = vy = 0.0
3249
+
3250
+ sm_x[i] = mean_x
3251
+ sm_y[i] = mean_y
3252
+ if return_variance:
3253
+ x_var[i] = vx
3254
+ y_var[i] = vy
3255
+
3256
+ if return_variance:
3257
+ return sm_x, sm_y, x_var, y_var
3258
+ return sm_x, sm_y
3259
+
3260
+
3261
+ def X_T_running_average(temp_list, chi_list, temp_window):
3262
+ """
3263
+ Compute running averages and variances of susceptibility over a sliding
3264
+ temperature window.
3265
+
3266
+ Parameters
3267
+ ----------
3268
+ temp_list : Sequence[float]
3269
+ Ordered list of temperatures (must be same length as chi_list).
3270
+ chi_list : Sequence[float]
3271
+ List of susceptibility values corresponding to each temperature.
3272
+ temp_window : float
3273
+ Total width of the temperature window. Each point averages data in
3274
+ [T_i - temp_window/2, T_i + temp_window/2].
3275
+
3276
+ Returns
3277
+ -------
3278
+ avg_temps : List[float]
3279
+ The mean temperature in each window (one per input point).
3280
+ avg_chis : List[float]
3281
+ The mean susceptibility in each window.
3282
+ temp_vars : List[float]
3283
+ The variance of temperatures in each window.
3284
+ chi_vars : List[float]
3285
+ The variance of susceptibility values in each window.
3286
+ """
3287
+ if not temp_list or not chi_list or temp_window <= 0:
3288
+ return temp_list, chi_list, [], []
3289
+
3290
+ avg_temps = []
3291
+ avg_chis = []
3292
+ temp_vars = []
3293
+ chi_vars = []
3294
+ n = len(temp_list)
3295
+
3296
+ for i in range(n):
3297
+ # Determine the temperature range for the current point
3298
+ temp_center = temp_list[i]
3299
+ start_temp = temp_center - temp_window / 2
3300
+ end_temp = temp_center + temp_window / 2
3301
+
3302
+ # Get the indices within the temperature range
3303
+ indices = [j for j, t in enumerate(temp_list) if start_temp <= t <= end_temp]
3304
+
3305
+ # Calculate the average temperature and susceptibility for the current window
3306
+ if indices:
3307
+ temp_range = [temp_list[j] for j in indices]
3308
+ chi_range = [chi_list[j] for j in indices]
3309
+ avg_temp = sum(temp_range) / len(temp_range)
3310
+ avg_chi = sum(chi_range) / len(chi_range)
3311
+ temp_var = np.var(temp_range)
3312
+ chi_var = np.var(chi_range)
3313
+ else:
3314
+ avg_temp = temp_center
3315
+ avg_chi = chi_list[i]
3316
+ temp_var = 0
3317
+ chi_var = 0
3318
+
3319
+ avg_temps.append(avg_temp)
3320
+ avg_chis.append(avg_chi)
3321
+ temp_vars.append(temp_var)
3322
+ chi_vars.append(chi_var)
3323
+
3324
+ return avg_temps, avg_chis, temp_vars, chi_vars
3325
+
3326
+
3327
+ def optimize_moving_average_window(experiment, min_temp_window=0, max_temp_window=50, steps=50, colormapwarm='tab20b', colormapcool='tab20c'):
3328
+ warm_T, warm_X, cool_T, cool_X = split_warm_cool(experiment)
3329
+ windows = np.linspace(min_temp_window, max_temp_window, steps)
3330
+ fig, axs = plt.subplots(ncols=2, nrows=1, figsize=(12, 6))
3331
+
3332
+ # Normalize the colormap
3333
+ norm = colors.Normalize(vmin=min_temp_window, vmax=max_temp_window)
3334
+
3335
+ for window in windows:
3336
+ _, warm_avg_chis, _, warm_chi_vars = smooth_moving_avg(warm_T, warm_X, window, return_variance=True)
3337
+ warm_avg_rms, warm_avg_variance = calculate_avg_variance_and_rms(warm_X, warm_avg_chis, warm_chi_vars)
3338
+ _, cool_avg_chis, _, cool_chi_vars = smooth_moving_avg(cool_T, cool_X, window, return_variance=True)
3339
+ cool_avg_rms, cool_avg_variance = calculate_avg_variance_and_rms(cool_X, cool_avg_chis, cool_chi_vars)
3340
+
3341
+ axs[0].scatter(warm_avg_variance, warm_avg_rms, c=window, cmap=colormapwarm, norm=norm)
3342
+ axs[1].scatter(cool_avg_variance, cool_avg_rms, c=window, cmap=colormapcool, norm=norm)
3343
+ # ax.text(warm_avg_variance, warm_avg_rms, f'{window:.2f}°C', fontsize=12, ha='right')
3344
+ # ax.text(cool_avg_variance, cool_avg_rms, f'{window:.2f}°C', fontsize=12, ha='right')
3345
+ for ax in axs:
3346
+ ax.set_xlabel('Average Variance', fontsize=14)
3347
+ ax.set_ylabel('Average RMS', fontsize=14)
3348
+
3349
+ ax.invert_yaxis()
3350
+ # show the colormaps and make sure the range is correct
3351
+ warm_cbar = plt.colorbar(plt.cm.ScalarMappable(cmap=colormapwarm, norm=norm), orientation='horizontal', ax=axs[0])
3352
+ warm_cbar.set_label('Warm cycle window size (°C)')
3353
+ cool_cbar = plt.colorbar(plt.cm.ScalarMappable(cmap=colormapcool, norm=norm), orientation='horizontal', ax=axs[1])
3354
+ cool_cbar.set_label('Cool cycle window size (°C)')
3355
+ plt.suptitle('Optimization of running average window size', fontsize=16)
3356
+ return fig, axs
3357
+
3358
+
3359
+ def calculate_avg_variance_and_rms(chi_list, avg_chis, chi_vars):
3360
+ rms_list = np.sqrt([(chi - avg_chi)**2 for chi, avg_chi in zip(chi_list, avg_chis)])
3361
+ total_rms = np.sum(rms_list)
3362
+ avg_rms = total_rms / len(rms_list)
3363
+
3364
+ total_variance = np.sum(chi_vars)
3365
+ avg_variance = total_variance / len(chi_vars)
3366
+
3367
+ return avg_rms, avg_variance
3368
+
3369
+
3370
+ # backfield data processing functions
3371
+ # ------------------------------------------------------------------------------------------------------------------
3372
+ def backfield_data_processing(experiment, field='treat_dc_field', magnetization='magn_mass', smooth_mode='lowess', smooth_frac=0.0, drop_first=False):
3373
+ '''
3374
+ Function to process the backfield data including shifting the magnetic
3375
+ moment to be positive values taking the log base 10 of the magnetic
3376
+ field values and writing these new fields into the experiment attribute
3377
+ table
3378
+
3379
+ Parameters
3380
+ ----------
3381
+ experiment : DataFrame
3382
+ DataFrame containing the backfield data
3383
+ field : str
3384
+ The name of the treatment field column in the DataFrame
3385
+ magnetization : str
3386
+ The name of the magnetization column in the DataFrame
3387
+ smooth_frac : float
3388
+ Fraction of the data to be used for LOWESS smoothing, value must be between 0 and 1
3389
+ drop_first : bool
3390
+ Whether to drop the first data point or not
3391
+ in some cases you may want to drop the first data point to avoid negative log values
3392
+
3393
+ Returns
3394
+ -------
3395
+ DataFrame
3396
+ The processed experiment DataFrame with new attributes.
3397
+ '''
3398
+ assert smooth_mode in ['lowess', 'spline'], 'smooth_mode must be either lowess or spline'
3399
+ assert smooth_frac >= 0 and smooth_frac <= 1, 'smooth_frac must be between 0 and 1'
3400
+ assert isinstance(drop_first, bool), 'drop_first must be a boolean'
3401
+
3402
+ experiment = experiment.reset_index(drop=True)
3403
+ # check and make sure to force drop first row if the first treat field is in the wrong direction
3404
+ if experiment[field].iloc[0] > 0:
3405
+ drop_first = True
3406
+ if drop_first:
3407
+ experiment = experiment.iloc[1:].reset_index(drop=1)
3408
+
3409
+ if find_y_crossing(experiment[field], experiment[magnetization]) is not None:
3410
+ Bcr = np.abs(find_y_crossing(experiment[field], experiment[magnetization]))
3411
+ else:
3412
+ Bcr = np.nan
3413
+ # to plot the backfield data in the conventional way, we need to shift the magnetization to be positive
3414
+ experiment['magn_mass_shift'] = [i - experiment[magnetization].min() for i in experiment[magnetization]]
3415
+ # we then calculate the log10 of the treatment fields
3416
+ experiment['log_dc_field'] = np.log10(-experiment[field]*1e3)
3417
+ if smooth_mode == 'spline':
3418
+ # spline smoothing
3419
+ x = experiment['log_dc_field']
3420
+ y = experiment['magn_mass_shift']
3421
+ y_mean = np.mean(y)
3422
+ y_std = np.std(y)
3423
+ y_scaled = (y - y_mean) / y_std
3424
+
3425
+ # Map it to actual s value
3426
+ s = smooth_frac * len(x) * y_mean
3427
+
3428
+ spl = UnivariateSpline(x, y_scaled, s=s)
3429
+ experiment['smoothed_magn_mass_shift'] = spl(x) * y_std + y_mean
3430
+ experiment['smoothed_log_dc_field'] = x
3431
+ elif smooth_mode == 'lowess':
3432
+ # loess smoothing
3433
+ spl = lowess(experiment['magn_mass_shift'], experiment['log_dc_field'], frac=smooth_frac)
3434
+ experiment['smoothed_magn_mass_shift'] = spl[:, 1]
3435
+ experiment['smoothed_log_dc_field'] = spl[:, 0]
3436
+ return experiment, Bcr
3437
+
3438
+ def plot_backfield_data(
3439
+ experiment,
3440
+ field="treat_dc_field",
3441
+ magnetization="magn_mass",
3442
+ figsize=(5, 12),
3443
+ plot_raw=True,
3444
+ plot_processed=True,
3445
+ plot_spectrum=True,
3446
+ interactive=False,
3447
+ return_figure=True,
3448
+ show_plot=True,
3449
+ ):
3450
+ """
3451
+ Plot backfield data: raw, processed, and coercivity spectrum.
3452
+
3453
+ Data processing steps:
3454
+ - Raw: magnetization vs. field (T).
3455
+ - Processed: magn_mass_shift = magn_mass − min(magn_mass);
3456
+ log_dc_field = log10(−field·1e3) (log10 mT axis).
3457
+ - Spectrum: derivative −ΔM/Δ(log B).
3458
+
3459
+ Parameters
3460
+ ----------
3461
+ experiment : DataFrame
3462
+ Must contain raw and, if requested, processed columns.
3463
+ plot_raw : bool
3464
+ plot_processed : bool
3465
+ plot_spectrum : bool
3466
+ interactive : bool
3467
+ return_figure : bool
3468
+ show_plot : bool
3469
+
3470
+ Returns
3471
+ -------
3472
+ Matplotlib (fig, axes) or Bokeh grid or None
3473
+ """
3474
+ # check columns
3475
+ req = []
3476
+ if plot_raw:
3477
+ req += [field, magnetization]
3478
+ if plot_processed or plot_spectrum:
3479
+ req += [
3480
+ "log_dc_field",
3481
+ "magn_mass_shift",
3482
+ "smoothed_log_dc_field",
3483
+ "smoothed_magn_mass_shift",
3484
+ ]
3485
+ missing = [c for c in req if c not in experiment.columns]
3486
+ if missing:
3487
+ raise KeyError(f"Missing columns: {missing}")
3488
+
3489
+ # prepare spectrum
3490
+ if plot_spectrum:
3491
+ log_b = experiment["log_dc_field"]
3492
+ shift_m = experiment["magn_mass_shift"]
3493
+ raw_dy = -np.diff(shift_m) / np.diff(log_b)
3494
+ raw_dx_log = log_b.rolling(2).mean().dropna()
3495
+ smooth_dy = -np.diff(experiment["smoothed_magn_mass_shift"]) / np.diff(
3496
+ experiment["smoothed_log_dc_field"]
3497
+ )
3498
+ smooth_dx_log = experiment["smoothed_log_dc_field"].rolling(2).mean().dropna()
3499
+ # axis: convert log10(mT) → mT for plotting, but axis scale remains log
3500
+ raw_dx = 10**raw_dx_log
3501
+ smooth_dx = 10**smooth_dx_log
3502
+
3503
+ if interactive:
3504
+ tools = [
3505
+ HoverTool(tooltips=[("Field (mT)", "@x"), ("Mag", "@y")]),
3506
+ "pan,box_zoom,reset"
3507
+ ]
3508
+ figs = []
3509
+ palette = Category10[3]
3510
+
3511
+ if plot_raw:
3512
+ p0 = figure(
3513
+ title="Raw backfield",
3514
+ x_axis_label="Field (T)",
3515
+ y_axis_label="Magnetization",
3516
+ tools=tools,
3517
+ sizing_mode="stretch_width",
3518
+ )
3519
+ p0.scatter(
3520
+ experiment[field],
3521
+ experiment[magnetization],
3522
+ legend_label="raw",
3523
+ color=palette[0],
3524
+ size=6,
3525
+ )
3526
+ p0.line(experiment[field], experiment[magnetization], color=palette[0])
3527
+ p0.legend.click_policy = "hide"
3528
+ figs.append(p0)
3529
+
3530
+ if plot_processed:
3531
+ x_shifted = 10 ** experiment["log_dc_field"]
3532
+ x_smooth = 10 ** experiment["smoothed_log_dc_field"]
3533
+ p1 = figure(
3534
+ title="Processed backfield",
3535
+ x_axis_label="Field (mT)",
3536
+ y_axis_label="Magnetization",
3537
+ x_axis_type="log",
3538
+ tools=tools,
3539
+ sizing_mode="stretch_width",
3540
+ )
3541
+ p1.scatter(
3542
+ x_shifted,
3543
+ experiment["magn_mass_shift"],
3544
+ legend_label="shifted",
3545
+ color=palette[1],
3546
+ size=6,
3547
+ )
3548
+ p1.line(
3549
+ x_smooth,
3550
+ experiment["smoothed_magn_mass_shift"],
3551
+ color=palette[1],
3552
+ legend_label="smoothed",
3553
+ )
3554
+ p1.legend.click_policy = "hide"
3555
+ figs.append(p1)
3556
+
3557
+ if plot_spectrum:
3558
+ p2 = figure(
3559
+ title="Coercivity spectrum",
3560
+ x_axis_label="Field (mT)",
3561
+ y_axis_label="dM/dB",
3562
+ x_axis_type="log",
3563
+ tools=tools,
3564
+ sizing_mode="stretch_width",
3565
+ )
3566
+ p2.scatter(raw_dx, raw_dy, legend_label="raw spectrum",
3567
+ color=palette[2], size=6)
3568
+ p2.line(smooth_dx, smooth_dy, color=palette[2],
3569
+ legend_label="smoothed spectrum")
3570
+ p2.legend.click_policy = "hide"
3571
+ figs.append(p2)
3572
+
3573
+ grid = gridplot(figs, ncols=1, sizing_mode="stretch_width")
3574
+ if show_plot:
3575
+ show(grid)
3576
+ if return_figure:
3577
+ return grid
3578
+ return None
3579
+
3580
+ # static Matplotlib
3581
+ panels = []
3582
+ if plot_raw:
3583
+ panels.append("raw")
3584
+ if plot_processed:
3585
+ panels.append("processed")
3586
+ if plot_spectrum:
3587
+ panels.append("spectrum")
3588
+
3589
+ n = len(panels)
3590
+ fig, axes = plt.subplots(nrows=n, ncols=1, figsize=figsize)
3591
+ if n == 1:
3592
+ axes = [axes]
3593
+
3594
+ for ax, panel in zip(axes, panels):
3595
+ if panel == "raw":
3596
+ ax.scatter(
3597
+ experiment[field], experiment[magnetization], c="k", s=10, label="raw"
3598
+ )
3599
+ ax.plot(experiment[field], experiment[magnetization], c="k")
3600
+ ax.set(title="raw backfield", xlabel="field (T)", ylabel="magnetization")
3601
+ ax.legend()
3602
+ elif panel == "processed":
3603
+ ax.scatter(
3604
+ experiment["log_dc_field"],
3605
+ experiment["magn_mass_shift"],
3606
+ c="gray",
3607
+ s=10,
3608
+ label="shifted",
3609
+ )
3610
+ ax.plot(
3611
+ experiment["smoothed_log_dc_field"],
3612
+ experiment["smoothed_magn_mass_shift"],
3613
+ c="k",
3614
+ label="smoothed",
3615
+ )
3616
+ ticks = ax.get_xticks()
3617
+ ax.set_xticklabels([f"{round(10**t, 1)}" for t in ticks])
3618
+ ax.set(
3619
+ title="processed", xlabel="field (mT)", ylabel="magnetization"
3620
+ )
3621
+ ax.legend()
3622
+ else: # spectrum
3623
+ ax.scatter(raw_dx_log, raw_dy, c="gray", s=10, label="raw spectrum")
3624
+ ax.plot(smooth_dx_log, smooth_dy, c="k", label="smoothed spectrum")
3625
+ ticks = ax.get_xticks()
3626
+ ax.set_xticklabels([f"{round(10**t, 1)}" for t in ticks])
3627
+ ax.set(title="spectrum", xlabel="field (mT)", ylabel="dM/dB")
3628
+ ax.legend()
3629
+
3630
+ fig.tight_layout()
3631
+ if show_plot:
3632
+ plt.show()
3633
+ if return_figure:
3634
+ return fig, axes
3635
+ return None
3636
+
3637
+
3638
+ def backfield_unmixing(field, magnetization, n_comps=1, parameters=None, iter=True, n_iter=3, skewed=True):
3639
+ '''
3640
+ backfield unmixing for a single experiment
3641
+
3642
+ Parameters
3643
+ ----------
3644
+ field : np.array
3645
+ The field values in log10 unit in the experiment
3646
+ magnetization : np.array
3647
+ The magnetization values in the experiment
3648
+ n_comps : int
3649
+ Number of components to unmix the data into
3650
+ params : Pandas DataFrame
3651
+ Initial values for the model parameters
3652
+ should be constructed as the following columns:
3653
+ - amplitude in arbitrary scale
3654
+ - center in unit of mT
3655
+ - sigma in unit of mT
3656
+ - gamma in arbitrary scale
3657
+ |amplitude|center|sigma|gamma|
3658
+ |---|---|---|---|
3659
+ |1.0|100|10|0.0|
3660
+ |...|...|...|...|
3661
+ the program will automatically go through the rows and extract these inital parameter values
3662
+ If the parameters are not given, we will run an automated program to make initial guess
3663
+
3664
+ iter : bool
3665
+ Whether to iterate the fitting process or not. It is useful to iterate the fitting process
3666
+ to make sure the parameters are converged
3667
+ n_iter : int
3668
+ Number of iterations to run the fitting process
3669
+ skewed : bool
3670
+ Whether to use skewed Gaussian model or not
3671
+ if False, the program will use normal Gaussian model
3672
+
3673
+ Returns
3674
+ -------
3675
+ result : lmfit.model.ModelResult
3676
+ The result of the fitting process
3677
+ parameters : DataFrame
3678
+ The updated parameters table
3679
+ '''
3680
+
3681
+ assert n_comps > 0, 'n_component must be greater than 0'
3682
+ assert isinstance(n_comps, int), 'n_component must be an integer'
3683
+ assert isinstance(parameters, pd.DataFrame), f"Expected a pandas DataFrame, but got {type(parameters).__name__}"
3684
+ assert n_comps == parameters.shape[0], 'number of components must match the number of rows in the parameters table'
3685
+ assert n_iter > 0, 'n_iter must be greater than 0'
3686
+
3687
+ if not iter:
3688
+ n_iter = 1
3689
+
3690
+ # re-calculate the derivatives based on the smoothed data columns
3691
+ smoothed_derivatives_y = -np.diff(magnetization)/np.diff(field)
3692
+ smoothed_derivatives_x = pd.Series(field).rolling(window=2).mean().dropna()
3693
+
3694
+ # create the model depending on the number of components specified
3695
+ composite_model = None
3696
+ params = Parameters()
3697
+ for i in range(n_comps):
3698
+ prefix = f'g{i+1}_'
3699
+ model = SkewedGaussianModel(prefix=prefix)
3700
+
3701
+ # Initial parameter guesses
3702
+ params.add(f'{prefix}amplitude', value=parameters['amplitude'][i])
3703
+ params.add(f'{prefix}center', value=np.log10(parameters['center'][i]))
3704
+ params.add(f'{prefix}sigma', value=np.log10(parameters['sigma'][i]))
3705
+ params.add(f'{prefix}gamma', value=parameters['gamma'][i])
3706
+
3707
+ # now let's set bounds to the parameters to help fitting algorithm converge
3708
+ params[f'{prefix}amplitude'].min = 0 # Bounds for amplitude parameters
3709
+ params[f'{prefix}amplitude'].max = 1 # Bounds for proportion/amplitude parameters
3710
+ params[f'{prefix}center'].min = np.min(field) # Bounds for center parameters
3711
+ params[f'{prefix}center'].max = np.max(field) # Bounds for center parameters
3712
+ params[f'{prefix}sigma'].min = 0
3713
+ params[f'{prefix}sigma'].max = np.max(field)-np.min(field) # Bounds for sigma parameters
3714
+
3715
+ # restrict to normal distribution if skewed is False
3716
+ if skewed == False:
3717
+ params[f'{prefix}gamma'].set(value=0, vary=False)
3718
+
3719
+ if composite_model is None:
3720
+ composite_model = model
3721
+ else:
3722
+ composite_model += model
3723
+
3724
+ def fitting_function(y, params, x):
3725
+ result = composite_model.fit(y, params, x=x)
3726
+ for i in range(n_comps):
3727
+ prefix = f'g{i+1}_'
3728
+ parameters.loc[i, 'amplitude'] = result.params[f'{prefix}amplitude'].value # convert back to original scale
3729
+ parameters.loc[i, 'center'] = 10**result.params[f'{prefix}center'].value # convert back to mT
3730
+ parameters.loc[i, 'sigma'] = 10**result.params[f'{prefix}sigma'].value # convert back to mT
3731
+ parameters.loc[i, 'gamma'] = result.params[f'{prefix}gamma'].value
3732
+ return result, parameters
3733
+
3734
+ result, parameters = fitting_function(smoothed_derivatives_y/np.max(smoothed_derivatives_y), params, x=smoothed_derivatives_x)
3735
+
3736
+ if iter:
3737
+ for i in range(n_iter):
3738
+ result, parameters = fitting_function(smoothed_derivatives_y/np.max(smoothed_derivatives_y), result.params, x=smoothed_derivatives_x)
3739
+
3740
+ return result, parameters
3741
+
3742
+
3743
+ def plot_backfield_unmixing_result(experiment, result, sigma=2, figsize=(8,6), n=200):
3744
+ '''
3745
+ function for plotting the backfield unmixing results
3746
+
3747
+ Parameters
3748
+ ----------
3749
+ experiment : pandas.DataFrame
3750
+ the backfield experiment data
3751
+ result : lmfit.model.ModelResult
3752
+ the result of the backfield unmixing
3753
+ sigma : float
3754
+ the sigma value for the uncertainty band
3755
+ figsize : tuple
3756
+ the figure size
3757
+ n : int
3758
+ the number of points for the x-axis interpolation
3759
+ you may choose a large enough number so that the result components
3760
+ and the best fit curves are smooth
3761
+
3762
+ Returns
3763
+ -------
3764
+ fig : matplotlib.figure.Figure
3765
+ the figure object
3766
+ ax : matplotlib.axes._axes.Axes
3767
+ the axes object
3768
+ '''
3769
+ raw_derivatives_y = -np.diff(experiment['magn_mass_shift'])/np.diff(experiment['log_dc_field'])
3770
+ raw_derivatives_x = experiment['log_dc_field'].rolling(window=2).mean().dropna()
3771
+ smoothed_derivatives_x = experiment['smoothed_log_dc_field'].rolling(window=2).mean().dropna()
3772
+
3773
+ x_interp = np.linspace(smoothed_derivatives_x.min(), smoothed_derivatives_x.max(), n)
3774
+ best_fit_interp = result.eval(x=x_interp) * np.max(raw_derivatives_y)
3775
+ dely_interp = result.eval_uncertainty(x=x_interp, sigma=sigma) * np.max(raw_derivatives_y)
3776
+ # impose bounds on the dely to be smaller than the best fit
3777
+ dely_interp = [min(dely_interp[i], best_fit_interp[i]) for i in range(len(dely_interp))]
3778
+ fig, ax = plt.subplots(figsize=figsize)
3779
+ # first plot the scatter raw dMdB data
3780
+ ax.scatter(raw_derivatives_x, raw_derivatives_y, c='grey', marker='o', s=10, label='raw coercivity spectrum')
3781
+ # plot the total best fit
3782
+ ax.plot(x_interp, best_fit_interp, '-', color='k', alpha=0.6, label='total spectrum best fit')
3783
+ ax.fill_between(x_interp,
3784
+ [max(best_fit_interp[j]-dely_interp[j],0) for j in range(len(best_fit_interp))],
3785
+ best_fit_interp+dely_interp,
3786
+ color="#8A8A8A",
3787
+ label=f'total {sigma}-$\sigma$ band', alpha=0.5)
3788
+ if len(result.components) > 1:
3789
+ for i in range(len(result.components)):
3790
+ this_comp_interp = result.eval_components(x=x_interp)[f'g{i+1}_'] * np.max(raw_derivatives_y)
3791
+ this_dely = result.dely_comps[f'g{i+1}_'] * np.max(raw_derivatives_y)
3792
+ # impose bounds on the dely to be smaller than the best fit for the component
3793
+ this_dely = [min(this_dely[j], this_comp_interp[j]) for j in range(len(this_dely))]
3794
+ ax.plot(x_interp, this_comp_interp, c=f'C{i}', label=f'component #{i+1}, {sigma}-$\sigma$ band')
3795
+ lower_bound = [max(this_comp_interp[j]-this_dely[j],0) for j in range(len(this_comp_interp))]
3796
+ upper_bound = this_comp_interp+this_dely
3797
+ ax.fill_between(x_interp,
3798
+ lower_bound,
3799
+ upper_bound,
3800
+ color=f'C{i}', alpha=0.5)
3801
+
3802
+ xticks = ax.get_xticks()
3803
+ ax.set_xticklabels([f'{int(10**i)}' for i in xticks])
3804
+ ax.legend()
3805
+ ax.set_title('coercivity unmixing results')
3806
+ ax.set_xlabel('treatment field (mT)', fontsize=14)
3807
+ ax.set_ylabel('dM/dB', fontsize=14)
3808
+ return fig, ax
3809
+
3810
+ def interactive_backfield_fit(field, magnetization, n_components, skewed=True, figsize=(10, 6)):
3811
+ """
3812
+ Function for interactive backfield unmixing using skew‑normal distributions.
3813
+ No uncertainty propagation is shown; this function is useful for estimating
3814
+ initial guesses for parameters.
3815
+
3816
+ *Important note:* in a Jupyter notebook use the `%matplotlib widget`
3817
+ command to enable live figure updates.
3818
+
3819
+ Parameters
3820
+ ----------
3821
+ field : array‑like
3822
+ The field values in log scale.
3823
+ magnetization : array‑like
3824
+ The magnetization values in log scale.
3825
+ n_components : int
3826
+ The number of skew‑normal components to fit.
3827
+ figsize : tuple, optional
3828
+ The size of the figure to display. Default is (10, 6).
3829
+
3830
+ Returns
3831
+ -------
3832
+ pandas.DataFrame
3833
+ A DataFrame of the most recent fit parameters with columns
3834
+ `amplitude`, `center`, `sigma`, and `gamma`. This DataFrame is
3835
+ updated in place as sliders are moved.
3836
+ """
3837
+
3838
+ final_fit = {"df": None}
3839
+
3840
+ # Calculate the smoothed derivative
3841
+ smoothed_derivatives_y = -np.diff(magnetization) / np.diff(field)
3842
+ smoothed_derivatives_x = pd.Series(field).rolling(window=2).mean().dropna()
3843
+
3844
+ fig, ax = plt.subplots(figsize=figsize)
3845
+ fig.canvas.header_visible = False
3846
+
3847
+ # Store all sliders and text
3848
+ sliders = []
3849
+ texts = []
3850
+
3851
+ def create_slider_dict(name, min_val, max_val, step, description, value=0.0):
3852
+ return {
3853
+ f"{name}_{i}": FloatSlider(
3854
+ value=value,
3855
+ min=min_val,
3856
+ max=max_val,
3857
+ step=step,
3858
+ description=f'{description}_{i+1}',
3859
+ continuous_update=False
3860
+ )
3861
+ for i in range(n_components)
3862
+ }
3863
+
3864
+ amp_slidebars = create_slider_dict('amplitude', 0.0, 1, 0.01, 'amplitude')
3865
+ center_slidebars = create_slider_dict('center', 0.0, 10**np.max(field), 10**np.max(field) / 100, 'center', value=10**np.max(field)/2)
3866
+ sigma_slidebars = create_slider_dict('sigma', 0.0, 10**np.max(field), 10**np.max(field) / 100, 'sigma', value=10**np.max(field)/2)
3867
+ gamma_slidebars = create_slider_dict('gamma', -10.0, 10.0, 0.01, 'gamma')
3868
+
3869
+ # Collect all sliders by component for display and registration
3870
+
3871
+ for i in range(n_components):
3872
+ # Create sliders for each component
3873
+ amp_slider = amp_slidebars[f'amplitude_{i}']
3874
+ center_slider = center_slidebars[f'center_{i}']
3875
+ sigma_slider = sigma_slidebars[f'sigma_{i}']
3876
+ gamma_slider = gamma_slidebars[f'gamma_{i}']
3877
+ # Create a dictionary for each component
3878
+ d = {
3879
+ 'amplitude': amp_slider,
3880
+ 'center': center_slider,
3881
+ 'sigma': sigma_slider,
3882
+ 'gamma': gamma_slider
3883
+ }
3884
+ # Add sliders to the list
3885
+ if skewed:
3886
+ sliders.append(VBox([amp_slider, center_slider, sigma_slider, gamma_slider]))
3887
+ else:
3888
+ sliders.append(VBox([amp_slider, center_slider, sigma_slider]))
3889
+
3890
+ # now add the same amount of text boxes to update the best fit parameters on the fly
3891
+ for i in range(n_components):
3892
+ # make text boxes for each parameter
3893
+ amp_text = widgets.Text(value=str(amp_slidebars[f'amplitude_{i}'].value), description=f'amplitude_{i+1}')
3894
+ center_text = widgets.Text(value=str(center_slidebars[f'center_{i}'].value), description=f'center_{i+1}')
3895
+ sigma_text = widgets.Text(value=str(sigma_slidebars[f'sigma_{i}'].value), description=f'sigma_{i+1}')
3896
+ gamma_text = widgets.Text(value=str(gamma_slidebars[f'gamma_{i}'].value), description=f'gamma_{i+1}')
3897
+ # add the text boxes to the texts list
3898
+ if skewed:
3899
+ texts.append(VBox([amp_text, center_text, sigma_text, gamma_text]))
3900
+ else:
3901
+ texts.append(VBox([amp_text, center_text, sigma_text]))
3902
+
3903
+ # Display sliders
3904
+ display(HBox(sliders))
3905
+ display(HBox(texts))
3906
+
3907
+ def update_plot(*args):
3908
+ ax.clear()
3909
+ ax.scatter(smoothed_derivatives_x, smoothed_derivatives_y, marker='o', s=5, alpha=0.5, color='grey', label='original data')
3910
+ ax.set_xlabel('Field', fontsize=12)
3911
+ ax.set_ylabel('dM/dB', fontsize=12)
3912
+
3913
+ # Get values from sliders
3914
+ amp = [amp_slidebars[f'amplitude_{i}'].value for i in range(n_components)]
3915
+ center = [center_slidebars[f'center_{i}'].value for i in range(n_components)]
3916
+ sigma = [sigma_slidebars[f'sigma_{i}'].value for i in range(n_components)]
3917
+ gamma = [gamma_slidebars[f'gamma_{i}'].value for i in range(n_components)]
3918
+
3919
+ # Create a DataFrame for the parameters
3920
+ parameters = pd.DataFrame({
3921
+ 'amplitude': amp,
3922
+ 'center': center,
3923
+ 'sigma': sigma,
3924
+ 'gamma': gamma
3925
+ })
3926
+
3927
+ result, updated_parameters = backfield_unmixing(field, magnetization, n_comps=n_components, parameters=parameters, skewed=skewed)
3928
+ # update the text boxes with the updated parameters
3929
+ for i in range(n_components):
3930
+ amp_text = texts[i].children[0]
3931
+ center_text = texts[i].children[1]
3932
+ sigma_text = texts[i].children[2]
3933
+ amp_text.value = str(updated_parameters['amplitude'][i].round(4))
3934
+ center_text.value = str(updated_parameters['center'][i].round(4))
3935
+ sigma_text.value = str(updated_parameters['sigma'][i].round(4))
3936
+ if skewed:
3937
+ gamma_text = texts[i].children[3]
3938
+ gamma_text.value = str(updated_parameters['gamma'][i].round(4))
3939
+
3940
+ ax.plot(field, result.eval(x=field)*np.max(smoothed_derivatives_y), '-', color='k', alpha=0.6, label='total spectrum best fit')
3941
+ if len(result.components) > 1:
3942
+ for i in range(len(result.components)):
3943
+ this_comp = result.eval_components(x=field)[f'g{i+1}_']*np.max(smoothed_derivatives_y)
3944
+ ax.plot(field, this_comp, c=f'C{i}', label=f'component #{i+1}')
3945
+
3946
+ ax.set_xticklabels([f'{int(10**i)}' for i in ax.get_xticks()])
3947
+ ax.legend()
3948
+
3949
+ fig.canvas.draw()
3950
+ if final_fit["df"] is None:
3951
+ final_fit["df"] = updated_parameters.copy()
3952
+ else:
3953
+ # overwrite the existing DataFrame’s values
3954
+ for col in updated_parameters.columns:
3955
+ final_fit["df"][col] = updated_parameters[col].values
3956
+
3957
+ # Attach observers
3958
+ for box in sliders:
3959
+ for slider in box.children:
3960
+ if isinstance(slider, FloatSlider):
3961
+ slider.observe(update_plot, names='value')
3962
+
3963
+ update_plot()
3964
+ return final_fit["df"]
3965
+
3966
+ def backfield_MaxUnmix(field, magnetization, n_comps=1, parameters=None, skewed=True, n_resample=100, proportion=0.95, figsize=(10, 6)):
3967
+ '''
3968
+ function for performing the MaxUnmix backfield unmixing algorithm
3969
+ The components are modelled as skew-normal distributions
3970
+ The uncertainties are calculated based on a bootstrap approach
3971
+ where the given data points are bootstrap resampled with replacement
3972
+ and the unmixing is done with the same original estimates for each iteration
3973
+
3974
+ Parameters
3975
+ ----------
3976
+ field : array-like
3977
+ The field values in log10 scale
3978
+ magnetization : array-like
3979
+ The magnetization values
3980
+ parameters : DataFrame
3981
+ The parameters for the unmixing. The DataFrame should contain the following columns:
3982
+ 'amplitude', 'center', 'sigma', 'gamma'
3983
+ The number of rows in the DataFrame should be equal to n_comps
3984
+ skewed : bool
3985
+ Whether to use skewed Gaussian model or not
3986
+ if not, the program will use normal Gaussian model
3987
+ n_resample : int, optional
3988
+ The number of bootstrap resamples. The default is 100.
3989
+ proportion : float, optional
3990
+ The proportion of the data to be used for the bootstrap resampling. The default is 0.95.
3991
+ The actual number of resampled points per iteration is calculated as int(len(field) * proportion)
3992
+ n_comps : int, optional
3993
+ The number of components to be used for the unmixing. The default is 1.
3994
+ '''
3995
+
3996
+ assert len(parameters) == n_comps, f"Number of rows in parameters ({len(parameters)}) should be equal to n_comps ({n_comps})"
3997
+ assert proportion > 0 and proportion <= 1, f"proportion should be between 0 and 1, but got {proportion}"
3998
+ assert parameters is not None, f"parameters should not be None"
3999
+
4000
+ field = np.array(field)
4001
+ magnetization = np.array(magnetization)
4002
+ dMdB = -np.diff(magnetization) / np.diff(field)
4003
+ B = pd.Series(field).rolling(window=2).mean().dropna().to_numpy()
4004
+
4005
+ B_high_resolution = np.linspace(np.min(B), np.max(B), 200)
4006
+ # store the total dMdB fits
4007
+ all_total_dMdB = np.zeros((n_resample, len(B_high_resolution)))
4008
+ # store dMdB fits for each component
4009
+ all_component_dMdB = np.zeros((n_resample, n_comps, len(B_high_resolution)))
4010
+ # store distrbution parameters
4011
+ all_parameters = np.zeros((n_resample, n_comps, 4))
4012
+ for iter in range(n_resample):
4013
+ # bootstrap resample with replacement of the data
4014
+ index_resample = np.random.choice(len(B), size=int(len(B) * proportion), replace=True)
4015
+ B_resample = B[index_resample]
4016
+ dMdB_resample = dMdB[index_resample]
4017
+
4018
+ params = Parameters()
4019
+ # Create a composite model
4020
+ composite_model = None
4021
+ for i in range(n_comps):
4022
+ prefix = f'g{i+1}_'
4023
+ model = SkewedGaussianModel(prefix=prefix)
4024
+ # Initial parameter guesses
4025
+ params.add(f'{prefix}amplitude', value=parameters['amplitude'][i])
4026
+ params.add(f'{prefix}center', value=np.log10(parameters['center'][i]))
4027
+ params.add(f'{prefix}sigma', value=np.log10(parameters['sigma'][i]))
4028
+ params.add(f'{prefix}gamma', value=parameters['gamma'][i])
4029
+
4030
+ # now let's set bounds to the parameters to help fitting algorithm converge
4031
+ params[f'{prefix}amplitude'].min = 0 # Bounds for amplitude parameters
4032
+ params[f'{prefix}amplitude'].max = 1 # Bounds for proportion/amplitude parameters
4033
+ params[f'{prefix}center'].min = np.min(B) # Bounds for center parameters
4034
+ params[f'{prefix}center'].max = np.max(B) # Bounds for center parameters
4035
+ params[f'{prefix}sigma'].min = 0
4036
+ params[f'{prefix}sigma'].max = np.max(B)-np.min(B) # Bounds for sigma parameters
4037
+
4038
+ # restrict to normal distribution if skewed is False
4039
+ if skewed == False:
4040
+ params[f'{prefix}gamma'].set(value=0, vary=False)
4041
+
4042
+ if composite_model is None:
4043
+ composite_model = model
4044
+ else:
4045
+ composite_model += model
4046
+
4047
+ result = composite_model.fit(dMdB_resample/np.max(dMdB_resample), params, x=B_resample)
4048
+ all_parameters[iter] = np.array([[result.params[f'g{i+1}_amplitude'].value,
4049
+ result.params[f'g{i+1}_center'].value,
4050
+ result.params[f'g{i+1}_sigma'].value,
4051
+ result.params[f'g{i+1}_gamma'].value] for i in range(n_comps)])
4052
+ all_total_dMdB[iter] = result.eval(x=B_high_resolution)*np.max(dMdB_resample)
4053
+ for j in range(n_comps):
4054
+ prefix = f'g{j+1}_'
4055
+ # get the component model
4056
+ this_comp = result.eval_components(x=B_high_resolution)[f'{prefix}']*np.max(dMdB_resample)
4057
+ # store the component model
4058
+ all_component_dMdB[iter][j] = this_comp
4059
+ # calculate the 2.5, 50, and 97.5 percentiles of the bootstrap resamples
4060
+ dMdB_2_5 = np.percentile(all_total_dMdB, 2.5, axis=0)
4061
+ dMdB_50 = np.percentile(all_total_dMdB, 50, axis=0)
4062
+ dMdB_97_5 = np.percentile(all_total_dMdB, 97.5, axis=0)
4063
+
4064
+ # calculate the 2.5, 50, and 97.5 percentiles of the bootstrap resamples for each component
4065
+ dMdB_2_5_components = np.percentile(all_component_dMdB, 2.5, axis=0)
4066
+ dMdB_50_components = np.percentile(all_component_dMdB, 50, axis=0)
4067
+ dMdB_97_5_components = np.percentile(all_component_dMdB, 97.5, axis=0)
4068
+
4069
+ # calculate the mean and std of the parameters
4070
+ mean_parameters = np.mean(all_parameters, axis=0)
4071
+ std_parameters = np.std(all_parameters, axis=0)
4072
+
4073
+ # report a dictionary of the mean and std of the parameters
4074
+ parameters_dict = {}
4075
+ for i in range(n_comps):
4076
+ this_parameters_dict = {
4077
+ f'g{i+1}_amplitude': mean_parameters[i][0],
4078
+ f'g{i+1}_center': 10**mean_parameters[i][1],
4079
+ f'g{i+1}_sigma': 10**mean_parameters[i][2],
4080
+ f'g{i+1}_gamma': mean_parameters[i][3],
4081
+ f'g{i+1}_amplitude_std': std_parameters[i][0],
4082
+ f'g{i+1}_center_std': 10**std_parameters[i][1],
4083
+ f'g{i+1}_sigma_std': 10**std_parameters[i][2],
4084
+ f'g{i+1}_gamma_std': std_parameters[i][3]
4085
+ }
4086
+ parameters_dict.update(this_parameters_dict)
4087
+
4088
+ fig, ax = plt.subplots(figsize=figsize)
4089
+ ax.scatter(B, dMdB, marker='o', s=5, alpha=0.5, color='grey', label='original data')
4090
+ ax.plot(B_high_resolution, dMdB_50, '-', color='k', alpha=0.6, label='total best fit')
4091
+ ax.fill_between(B_high_resolution, dMdB_2_5, dMdB_97_5, color='k', alpha=0.2, label='total 95% CI')
4092
+ # plot the components
4093
+ for k in range(n_comps):
4094
+ ax.plot(B_high_resolution, dMdB_50_components[k], c=f'C{k}', label=f'component #{k+1}')
4095
+ ax.fill_between(B_high_resolution, dMdB_2_5_components[k], dMdB_97_5_components[k], color=f'C{k}', alpha=0.2, label=f'component #{k+1} 95% CI')
4096
+ ax.set_xlabel('Field (mT)', fontsize=12)
4097
+ ax.set_ylabel('dM/dB', fontsize=12)
4098
+ ax.set_xticklabels([f'{int(10**i)}' for i in ax.get_xticks()])
4099
+ ax.legend()
4100
+ fig.canvas.header_visible = False
4101
+
4102
+ return ax, parameters_dict
4103
+
4104
+
4105
+ def add_unmixing_stats_to_specimens_table(specimens_df, experiment_name, unmix_result, method='lmfit'):
4106
+ '''
4107
+ function to export the hysteresis data to a MagIC specimen data table
4108
+
4109
+ Parameters
4110
+ ----------
4111
+ specimens_df : pd.DataFrame
4112
+ dataframe with the specimen data
4113
+ experiment_name : str
4114
+ name of the experiment
4115
+ unmix_result : dict
4116
+ dictionary with the unmixing results
4117
+ as output from rmag.backfield_MaxUnmix() or
4118
+ from rmag.backfield_unmixing()
4119
+
4120
+ updates the specimen table in place
4121
+ '''
4122
+ if method == 'lmfit':
4123
+ unmix_result_dict = unmix_result.params.valuesdict()
4124
+ elif method == 'MaxUnmix':
4125
+ unmix_result_dict = unmix_result
4126
+ else:
4127
+ raise ValueError(f"method should be either 'lmfit' or 'MaxUnmix', but got {method}")
4128
+ # check if the description cell is type string
4129
+ if isinstance(specimens_df[specimens_df['experiments'] == experiment_name]['description'].iloc[0], str):
4130
+ # unpack the string to a dict, then add the new stats, then pack it back to a string
4131
+ description_dict = eval(specimens_df[specimens_df['experiments'] == experiment_name]['description'].iloc[0])
4132
+ for key in unmix_result_dict:
4133
+ if key in description_dict:
4134
+ # if the key already exists, update it
4135
+ description_dict[key] = unmix_result_dict[key]
4136
+ else:
4137
+ # if the key does not exist, add it
4138
+ description_dict[key] = unmix_result_dict[key]
4139
+ # pack the dict back to a string
4140
+ specimens_df.loc[specimens_df['experiments'] == experiment_name, 'description'] = str(description_dict)
4141
+ else:
4142
+ # if not, create a new dict
4143
+ specimens_df.loc[specimens_df['experiments'] == experiment_name, 'description'] = str(unmix_result_dict)
4144
+ return
4145
+
4146
+ def add_Bcr_to_specimens_table(specimens_df, experiment_name, Bcr):
4147
+ """
4148
+ Add the Bcr value to the MagIC specimens table
4149
+ the controled vocabulary for backfield derived Bcr is rem_bcr
4150
+
4151
+ Parameters
4152
+ ----------
4153
+ specimens_df : pandas.DataFrame
4154
+ The specimens table from the MagIC database
4155
+ experiment_name : str
4156
+ The name of the experiment to which the Bcr value belongs
4157
+ Bcr : float
4158
+ The Bcr value to be added to the specimens table
4159
+ """
4160
+ # first check if the rem_bcr column exists
4161
+ if 'rem_bcr' not in specimens_df.columns:
4162
+ # add the rem_bcr column to the specimens table
4163
+ specimens_df['rem_bcr'] = np.nan
4164
+ # add the Bcr value to the specimens table
4165
+ specimens_df.loc[specimens_df['experiments'] == experiment_name, 'rem_bcr'] = Bcr
4166
+
4167
+ return
4168
+
4169
+
4170
+ # Day plot function
4171
+ # ------------------------------------------------------------------------------------------------------------------
4172
+ def day_plot_MagIC(specimen_data,
4173
+ by ='specimen',
4174
+ Mr = 'hyst_mr_mass',
4175
+ Ms = 'hyst_ms_mass',
4176
+ Bcr = 'rem_bcr',
4177
+ Bc = 'hyst_bc',
4178
+ **kwargs):
4179
+ """
4180
+ Function to plot a Day plot from a MagIC specimens table.
4181
+
4182
+ Parameters
4183
+ ----------
4184
+ specimen_data : pandas.DataFrame
4185
+ DataFrame containing the specimens data.
4186
+ by : str
4187
+ Column name to group by (default is 'specimen').
4188
+ Mr : str
4189
+ Column name for the remanence (default is 'hyst_mr_mass').
4190
+ Ms : str
4191
+ Column name for the saturation magnetization (default is 'hyst_ms_mass').
4192
+ Bcr : str
4193
+ Column name for the coercivity (default is 'hyst_bcr').
4194
+ Bc : str
4195
+ Column name for the coercivity of remanence (default is 'hyst_bc').
4196
+ **kwargs : keyword arguments
4197
+ Additional arguments to pass to the plotting function.
4198
+
4199
+ Returns
4200
+ -------
4201
+ ax : matplotlib.axes.Axes
4202
+ The axes object containing the plot.
4203
+ """
4204
+ summary_sats = specimen_data.groupby(by).agg({Mr: 'mean', Ms: 'mean', Bcr: 'mean', Bc: 'mean'}).reset_index()
4205
+ summary_sats = summary_sats.dropna()
4206
+
4207
+ ax = day_plot(Mr = summary_sats[Mr],
4208
+ Ms = summary_sats[Ms],
4209
+ Bcr = summary_sats[Bcr],
4210
+ Bc = summary_sats[Bc],
4211
+ **kwargs)
4212
+ return ax
4213
+
4214
+ def day_plot(Mr, Ms, Bcr, Bc,
4215
+ Mr_Ms_lower=0.05, Mr_Ms_upper=0.5, Bc_Bcr_lower=1.5, Bc_Bcr_upper=4,
4216
+ plot_day_lines = True,
4217
+ plot_MD_slope=True,
4218
+ plot_SP_SD_mixing=[10, 15, 25, 30],
4219
+ plot_SD_MD_mixing=True,
4220
+ color='black', marker='o',
4221
+ label = 'sample', alpha=1,
4222
+ lc='black', lw=0.5,
4223
+ legend=True, figsize=(8,6)):
4224
+ '''
4225
+ function to plot given Ms, Mr, Bc, Bcr values either as single values or list/array of values
4226
+ plots Mr/Ms vs Bc/Bcr.
4227
+
4228
+ Parameters
4229
+ ----------
4230
+ Ms : float or array-like
4231
+ saturation magnetization
4232
+ Mr : float or array-like
4233
+ remanent magnetization
4234
+ Bc : float or array-like
4235
+ coercivity
4236
+ Bcr : float or array-like
4237
+ coercivity of remanence
4238
+ color : str, optional
4239
+ color of the points. The default is 'black'.
4240
+ marker : str, optional
4241
+ marker style of the points. The default is 'o'.
4242
+ label : str, optional
4243
+ label for the points. The default is 'sample'.
4244
+ alpha : float, optional
4245
+ transparency of the points. The default is 1.
4246
+ lc : str, optional
4247
+ color of the lines. The default is 'black'.
4248
+ lw : float, optional
4249
+ line width of the lines. The default is 0.5.
4250
+ legend : bool, optional
4251
+ whether to show the legend. The default is True.
4252
+ figsize : tuple, optional
4253
+ size of the figure. The default is (6,6).
4254
+
4255
+ Returns
4256
+ -------
4257
+ ax : matplotlib.axes._axes.Axes
4258
+ the axes object of the plot.
4259
+
4260
+ '''
4261
+ # force numpy arrays
4262
+ Ms = np.asarray(Ms)
4263
+ Mr = np.asarray(Mr)
4264
+ Bc = np.asarray(Bc)
4265
+ Bcr = np.asarray(Bcr)
4266
+ Bcr_Bc = Bcr/Bc
4267
+ Mr_Ms = Mr/Ms
4268
+ _, ax = plt.subplots(figsize = figsize)
4269
+ # plotting SD, PSD, MD regions
4270
+ if plot_day_lines:
4271
+ ax.axhline(Mr_Ms_lower, color = lc, lw = lw)
4272
+ ax.axhline(Mr_Ms_upper, color = lc, lw = lw)
4273
+ ax.axvline(Bc_Bcr_lower, color = lc, lw = lw)
4274
+ ax.axvline(Bc_Bcr_upper, color = lc, lw = lw)
4275
+ ax.text(1.1, 0.55, 'SD', color = 'k', fontsize = 12)
4276
+ ax.text(2.0, 0.06, 'PSD', color = 'k', fontsize = 12)
4277
+ ax.text(5.0, 0.006, 'MD', color = 'k', fontsize = 12)
4278
+
4279
+ if plot_MD_slope:
4280
+ MD_Bcr_Bc = np.linspace(4, 20, 100)
4281
+ MD_Mr_Ms = 1/MD_Bcr_Bc * 45/480
4282
+ ax.plot(MD_Bcr_Bc, MD_Mr_Ms, color = lc, lw = lw, label = 'MD slope')
4283
+ if len(plot_SP_SD_mixing) > 0:
4284
+ # get the SP saturation curve
4285
+ Bcr_Bc_SP, Mr_Ms_SP = SP_saturation_curve()
4286
+ ax.plot(Bcr_Bc_SP, Mr_Ms_SP, color = lc, lw = lw, ls='--', label = 'SP saturation curve')
4287
+ for i, SP_size in enumerate(plot_SP_SD_mixing):
4288
+ mixing_Bcr_Bc, mixing_Mr_Ms = SP_SD_mixture(SP_size)
4289
+ # filter out anything above the SP saturation curve
4290
+ Mr_Ms_SP_cutoff = np.interp(mixing_Bcr_Bc, Bcr_Bc_SP, Mr_Ms_SP)
4291
+ mask = mixing_Mr_Ms < Mr_Ms_SP_cutoff
4292
+ mixing_Bcr_Bc = mixing_Bcr_Bc[mask]
4293
+ mixing_Mr_Ms = mixing_Mr_Ms[mask]
4294
+ ax.plot(mixing_Bcr_Bc, mixing_Mr_Ms, color = 'C'+str(i), lw = lw, ls='--', label = f'SP size {SP_size} nm')
4295
+ if plot_SD_MD_mixing:
4296
+ # get the SD/MD mixing curve
4297
+ mixing_Bcr_Bc, mixing_Mr_Ms = SD_MD_mixture()
4298
+ # filter out anything above the SP saturation curve
4299
+ Mr_Ms_SP_cutoff = np.interp(mixing_Bcr_Bc, Bcr_Bc_SP, Mr_Ms_SP)
4300
+ mask = mixing_Mr_Ms < Mr_Ms_SP_cutoff
4301
+ mixing_Bcr_Bc = mixing_Bcr_Bc[mask]
4302
+ mixing_Mr_Ms = mixing_Mr_Ms[mask]
4303
+ ax.plot(mixing_Bcr_Bc, mixing_Mr_Ms, color = 'k', lw = lw, ls='-.', label = 'SD/MD mixture')
4304
+ # plot the data
4305
+ ax.scatter(Bcr_Bc, Mr_Ms, color = color, marker = marker, label = label, alpha=alpha)
4306
+
4307
+ ax.set_xlim(1, 100)
4308
+ ax.set_ylim(0.005, 1)
4309
+ ax.set_xscale('log')
4310
+ ax.set_yscale('log')
4311
+ ax.set_xticks([1, 2, 5, 10, 20, 50, 100], [1, 2, 5, 10, 20, 50, 100])
4312
+ ax.set_yticks([0.01, 0.02, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1], [0.01, 0.02, 0.05, 0.1, 0.2, 0.3, 0.4, 0.5, 1])
4313
+ ax.set_xlabel('Bcr/Bc', fontsize=12)
4314
+ ax.set_ylabel('Mr/Ms', fontsize=12)
4315
+ ax.set_title('Day plot', fontsize=14)
4316
+
4317
+ if legend:
4318
+ ax.legend(loc='lower right', fontsize=10)
4319
+ return ax
4320
+
4321
+ def squareness_Bc(Ms, Mr, Bc, color = 'black', marker = 'o', label = 'sample', alpha=1, lc = 'black', lw=0.5, legend=True, figsize = (6,6)):
4322
+ '''
4323
+ fuction for making squareness coercivity plot
4324
+ plots Mr/Ms vs Bc
4325
+ '''
4326
+ # force numpy arrays
4327
+ Ms = np.asarray(Ms)
4328
+ Mr = np.asarray(Mr)
4329
+ Bc = np.asarray(Bc)
4330
+ Mr_Ms = Mr/Ms
4331
+ _, ax = plt.subplots(figsize = figsize)
4332
+ ax.scatter(Bc, Mr_Ms, color = color, marker = marker, label = label, alpha=alpha, zorder = 100)
4333
+
4334
+ return ax
4335
+
4336
+ def Langevin_alpha(V, Ms, H, T):
4337
+ '''
4338
+ Langevin alpha calculation
4339
+
4340
+ Parameters
4341
+ ----------
4342
+ V : float
4343
+ volume of the particle
4344
+ Ms : float
4345
+ saturation magnetization
4346
+ H : float
4347
+ applied field
4348
+ T : float
4349
+ temperature in Kelvin
4350
+
4351
+ Returns
4352
+ -------
4353
+ alpha : float
4354
+ Langevin alpha value
4355
+ '''
4356
+ mu0 = 4 * np.pi * 1e-7
4357
+ k = 1.38064852e-23
4358
+
4359
+ return mu0*Ms * V * H / (k * T)
4360
+
4361
+ def Langevin(alpha):
4362
+ '''
4363
+ Langevin function
4364
+
4365
+ Parameters
4366
+ ----------
4367
+ alpha : float
4368
+ Langevin alpha value
4369
+
4370
+ Returns
4371
+ -------
4372
+ L : float
4373
+ Langevin function value
4374
+ '''
4375
+ return 1 / np.tanh(alpha) - 1 / alpha
4376
+
4377
+ def magnetite_Ms(T):
4378
+ '''
4379
+ Magnetite saturation magnetization calculation
4380
+
4381
+ Parameters
4382
+ ----------
4383
+ T : float
4384
+ temperature in Celsius
4385
+
4386
+ Returns
4387
+ -------
4388
+ Ms : float
4389
+ saturation magnetization value
4390
+ '''
4391
+ return 737.384 * 51.876 * (580 - T)**0.4
4392
+
4393
+ def chi_SP(SP_size, T):
4394
+ '''
4395
+ SP size distribution function
4396
+
4397
+ Parameters
4398
+ ----------
4399
+ SP_size : float
4400
+ size of the superparamagnetic particle in nm
4401
+ T : float
4402
+ temperature in Kelvin
4403
+
4404
+ Returns
4405
+ -------
4406
+ chi : float
4407
+ susceptibility value
4408
+ '''
4409
+ mu0 = 4 * np.pi * 1e-7
4410
+ k = 1.38064852e-23
4411
+ Ms = magnetite_Ms(T - 273.15)
4412
+ V = 4/3*np.pi*(SP_size/2)**3 / 1e27
4413
+ return mu0 * V * Ms**2 / 3 / k / T
4414
+
4415
+
4416
+ def SP_SD_mixture(SP_size, SD_Mr_Ms = 0.5, SD_Bcr_Bc = 1.25, X_sd = 3, T = 300):
4417
+ '''
4418
+ function to calculate the SP/SD mixture curve according to Dunlop (2002)
4419
+ Parameters
4420
+ ----------
4421
+ SP_size : float
4422
+ size of the superparamagnetic particle in nm
4423
+ SD_Mr_Ms : float, optional
4424
+ remanent to saturation magnetization ratio. The default is 0.5.
4425
+ SD_Bcr_Bc : float, optional
4426
+ remanent coercivity to coercivity ratio. The default is 1.25.
4427
+ X_sd : float, optional
4428
+ approximate Mrs/Bc slope. The default is 3 for magnetite
4429
+ T : float, optional
4430
+ temperature in Kelvin. The default is 300.
4431
+ Returns
4432
+ -------
4433
+ Bcr_Bc : numpy.ndarray
4434
+ coercivity ratio array
4435
+ Mrs_Ms : numpy.ndarray
4436
+ saturation magnetization ratio array
4437
+ '''
4438
+ f_sd = 1/np.logspace(0, 2, 100)
4439
+ f_sp = 1 - f_sd
4440
+ Mrs_Ms = f_sd * SD_Mr_Ms
4441
+ X_sp = chi_SP(SP_size, T)
4442
+ Bcr_Bc = 1 / (f_sd * X_sd / (f_sd * X_sd + f_sp * X_sp)) * SD_Bcr_Bc
4443
+
4444
+ return Bcr_Bc, Mrs_Ms
4445
+
4446
+ def SP_saturation_curve(SD_Mr_Ms=0.5, SD_Bcr_Bc = 1.25):
4447
+ '''
4448
+ function to calculate the SP saturation curve according to Dunlop (2002)
4449
+
4450
+ Parameters
4451
+ ----------
4452
+ SD_Mr_Ms : float, optional
4453
+ saturation magnetization ratio. The default is 0.5.
4454
+ SD_Bcr_Bc : float, optional
4455
+ remanence coercivity to coercivity ratio. The default is 1.25.
4456
+ Returns
4457
+ -------
4458
+ Bcr_Bc : numpy.ndarray
4459
+ coercivity ratio array
4460
+ Mrs_Ms : numpy.ndarray
4461
+ saturation magnetization ratio array
4462
+ '''
4463
+ f_sp = np.linspace(0, 1/3, 100)
4464
+ f_sd = 1 - f_sp
4465
+ Mrs_Ms = f_sd * SD_Mr_Ms
4466
+ Bcr_Bc = 1 / (1 - (f_sp/f_sd) / SD_Mr_Ms) * SD_Bcr_Bc
4467
+ return Bcr_Bc, Mrs_Ms
4468
+
4469
+ def SD_MD_mixture(Mr_Ms_SD = 0.5, Mr_Ms_MD = 0.019,
4470
+ Bc_SD = 400, Bc_MD = 43,
4471
+ Bcr_SD = 500, Bcr_MD = 230,
4472
+ X_sd = 0.6, X_MD = 0.209,
4473
+ Xr_SD = 0.48, Xr_MD = 0.039):
4474
+ '''
4475
+ function to calculate the SD/MD mixture curve according to Dunlop (2002)
4476
+ Parameters
4477
+ ----------
4478
+ Mr_Ms_SD : float
4479
+ remanent to saturation magnetization ratio for SD. The default is 0.5.
4480
+ Mr_Ms_MD : float
4481
+ remanent to saturation magnetization ratio for MD. The default is 0.019.
4482
+ Bc_SD : float
4483
+ coercivity for SD. The default is 400.
4484
+ Bc_MD : float
4485
+ coercivity for MD. The default is 43.
4486
+ Bcr_SD : float
4487
+ coercivity of remanence for SD. The default is 500.
4488
+ Bcr_MD : float
4489
+ coercivity of remanence for MD. The default is 230.
4490
+ X_sd : float
4491
+ approximate Mrs/Bc slope for SD. The default is 0.6.
4492
+ X_MD : float
4493
+ approximate Mrs/Bc slope for MD. The default is 0.209.
4494
+ Xr_SD : float
4495
+ approximate Mrs/Bcr slope for SD. The default is 0.48.
4496
+ Xr_MD : float
4497
+ approximate Mrs/Bcr slope for MD. The default is 0.039.
4498
+ Returns
4499
+ -------
4500
+ Bcr_Bc : numpy.ndarray
4501
+ coercivity ratio array
4502
+ Mrs_Ms : numpy.ndarray
4503
+ saturation magnetization ratio array
4504
+
4505
+ * the default values are fro the IRM database
4506
+ '''
4507
+ f_sd = np.linspace(0, 1, 100)
4508
+ f_md = 1 - f_sd
4509
+ Mrs_Ms = f_sd * Mr_Ms_SD + f_md * Mr_Ms_MD
4510
+ Bc = (f_sd * X_sd * Bc_SD + f_md * X_MD * Bc_MD) / (f_sd * X_sd + f_md * X_MD)
4511
+ Bcr = (f_sd * Xr_SD * Bcr_SD + f_md * Xr_MD * Bcr_MD) / (f_sd * Xr_SD + f_md * Xr_MD)
4512
+ Bcr_Bc = Bcr / Bc
4513
+ return Bcr_Bc, Mrs_Ms