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