solarwindpy 0.0.1.dev0__py3-none-any.whl → 0.1.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.

Potentially problematic release.


This version of solarwindpy might be problematic. Click here for more details.

Files changed (412) hide show
  1. plans/.velocity/metrics.json +96 -0
  2. plans/0-overview-template.md +268 -0
  3. plans/N-phase-template.md +106 -0
  4. plans/PLAN_AUDIT_SUMMARY.md +173 -0
  5. plans/TEMPLATE-USAGE-GUIDE.md +198 -0
  6. plans/__init__.py +1 -0
  7. plans/abandoned/compaction-agent-system/0-Overview.md +123 -0
  8. plans/abandoned/compaction-agent-system/agents-index-update-plan.md +109 -0
  9. plans/abandoned/compaction-agent-system/compacted_state.md +85 -0
  10. plans/abandoned/compaction-agent-system/implementation-plan.md +107 -0
  11. plans/abandoned/compaction-agent-system/system-validation-report.md +159 -0
  12. plans/abandoned/compaction-agent-system/usage-guide.md +210 -0
  13. plans/abandoned/hook-system-enhancement/0-Overview.md +214 -0
  14. plans/abandoned/hook-system-enhancement/1-Phase1-Core-Infrastructure.md +313 -0
  15. plans/abandoned/hook-system-enhancement/2-Phase2-Intelligent-Testing.md +385 -0
  16. plans/abandoned/hook-system-enhancement/3-Phase3-Physics-Validation.md +444 -0
  17. plans/abandoned/hook-system-enhancement/4-Phase4-Performance-Monitoring.md +458 -0
  18. plans/abandoned/hook-system-enhancement/5-Phase5-Developer-Experience.md +532 -0
  19. plans/abandoned/hook-system-enhancement/6-Implementation-Timeline.md +274 -0
  20. plans/abandoned/hook-system-enhancement/7-Risk-Management.md +376 -0
  21. plans/abandoned/hook-system-enhancement/8-Testing-Strategy.md +579 -0
  22. plans/abandoned/readthedocs-automation/0-Overview.md +247 -0
  23. plans/abandoned/readthedocs-automation/1-Emergency-Documentation-Fixes.md +270 -0
  24. plans/abandoned/readthedocs-automation/2-Template-System-Enhancement.md +811 -0
  25. plans/abandoned/readthedocs-automation/3-Quality-Audit-ReadTheDocs-Integration.md +844 -0
  26. plans/abandoned/readthedocs-automation/4-Plan-Consolidation-Cleanup.md +632 -0
  27. plans/abandoned/readthedocs-automation/9-Closeout.md +207 -0
  28. plans/abandoned/readthedocs-automation/ABANDONMENT_REASON.md +72 -0
  29. plans/cicd-architecture-redesign/0-Overview.md +193 -0
  30. plans/cicd-architecture-redesign/1-Workflow-Creation.md +103 -0
  31. plans/cicd-architecture-redesign/2-Version-Detection.md +123 -0
  32. plans/cicd-architecture-redesign/3-Deployment-Gates.md +169 -0
  33. plans/cicd-architecture-redesign/4-RC-Testing.md +194 -0
  34. plans/cicd-architecture-redesign/5-TestPyPI-Validation.md +264 -0
  35. plans/cicd-architecture-redesign/6-Production-Release.md +263 -0
  36. plans/cicd-architecture-redesign/7-Cleanup.md +243 -0
  37. plans/cicd-architecture-redesign/8-Documentation.md +285 -0
  38. plans/cicd-architecture-redesign/Closeout.md +225 -0
  39. plans/closeout-template.md +259 -0
  40. plans/completed/circular-import-audit/0-Overview.md +152 -0
  41. plans/completed/circular-import-audit/1-Static-Dependency-Analysis.md +62 -0
  42. plans/completed/circular-import-audit/2-Dynamic-Import-Testing.md +56 -0
  43. plans/completed/circular-import-audit/3-Performance-Impact-Assessment.md +56 -0
  44. plans/completed/circular-import-audit/4-Issue-Remediation.md +78 -0
  45. plans/completed/circular-import-audit/5-Preventive-Infrastructure.md +89 -0
  46. plans/completed/claude-settings-ecosystem-alignment/0-Overview.md +162 -0
  47. plans/completed/claude-settings-ecosystem-alignment/1-Security-Foundation.md +148 -0
  48. plans/completed/claude-settings-ecosystem-alignment/2-Hook-Integration.md +158 -0
  49. plans/completed/claude-settings-ecosystem-alignment/3-Agent-System-Integration.md +177 -0
  50. plans/completed/claude-settings-ecosystem-alignment/4-Enhanced-Workflow-Automation.md +159 -0
  51. plans/completed/claude-settings-ecosystem-alignment/5-Validation-Monitoring.md +181 -0
  52. plans/completed/claude-settings-ecosystem-alignment/compacted_session_state.md +290 -0
  53. plans/completed/combined_plan_with_checklist_documentation/1-Overview-and-Goals.md +51 -0
  54. plans/completed/combined_plan_with_checklist_documentation/2-Toolchain-and-Hosting.md +69 -0
  55. plans/completed/combined_plan_with_checklist_documentation/3-Repository-Structure.md +61 -0
  56. plans/completed/combined_plan_with_checklist_documentation/4-Configuration-and-Standards.md +70 -0
  57. plans/completed/combined_plan_with_checklist_documentation/5-Documentation-Content.md +62 -0
  58. plans/completed/combined_plan_with_checklist_documentation/6-CI-CD-and-Validation.md +58 -0
  59. plans/completed/combined_plan_with_checklist_documentation/7-Maintenance.md +55 -0
  60. plans/completed/combined_test_plan_with_checklist_fitfunctions/0-Overview.md +135 -0
  61. plans/completed/combined_test_plan_with_checklist_fitfunctions/1-Common-fixtures.md +59 -0
  62. plans/completed/combined_test_plan_with_checklist_fitfunctions/10-power_laws.md +56 -0
  63. plans/completed/combined_test_plan_with_checklist_fitfunctions/2-core.py-FitFunction.md +118 -0
  64. plans/completed/combined_test_plan_with_checklist_fitfunctions/3-gaussians.py-Gaussian-GaussianNormalized-GaussianLn.md +69 -0
  65. plans/completed/combined_test_plan_with_checklist_fitfunctions/4-trend_fits.py-TrendFit.md +99 -0
  66. plans/completed/combined_test_plan_with_checklist_fitfunctions/5-plots.py-FFPlot.md +98 -0
  67. plans/completed/combined_test_plan_with_checklist_fitfunctions/6-tex_info.py-TeXinfo.md +79 -0
  68. plans/completed/combined_test_plan_with_checklist_fitfunctions/7-Justification.md +49 -0
  69. plans/completed/combined_test_plan_with_checklist_fitfunctions/8-exponentials.md +64 -0
  70. plans/completed/combined_test_plan_with_checklist_fitfunctions/9-lines.md +58 -0
  71. plans/completed/combined_test_plan_with_checklist_plotting/0-Overview.md +142 -0
  72. plans/completed/combined_test_plan_with_checklist_plotting/1-base.py.md +90 -0
  73. plans/completed/combined_test_plan_with_checklist_plotting/10-labels-special.py.md +102 -0
  74. plans/completed/combined_test_plan_with_checklist_plotting/11-labels-chemistry.py.md +212 -0
  75. plans/completed/combined_test_plan_with_checklist_plotting/12-labels-composition.py.md +242 -0
  76. plans/completed/combined_test_plan_with_checklist_plotting/13-labels-datetime.py.md +247 -0
  77. plans/completed/combined_test_plan_with_checklist_plotting/14-labels-elemental_abundance.py.md +274 -0
  78. plans/completed/combined_test_plan_with_checklist_plotting/15-visual-validation.md +256 -0
  79. plans/completed/combined_test_plan_with_checklist_plotting/16-integration-testing.md +266 -0
  80. plans/completed/combined_test_plan_with_checklist_plotting/17-performance-benchmarks.md +267 -0
  81. plans/completed/combined_test_plan_with_checklist_plotting/18-Fixtures-and-Utilities.md +86 -0
  82. plans/completed/combined_test_plan_with_checklist_plotting/2-agg_plot.py.md +90 -0
  83. plans/completed/combined_test_plan_with_checklist_plotting/3-histograms.py.md +201 -0
  84. plans/completed/combined_test_plan_with_checklist_plotting/4-scatter.py.md +167 -0
  85. plans/completed/combined_test_plan_with_checklist_plotting/5-spiral.py.md +216 -0
  86. plans/completed/combined_test_plan_with_checklist_plotting/6-orbits.py.md +108 -0
  87. plans/completed/combined_test_plan_with_checklist_plotting/7-tools.py.md +86 -0
  88. plans/completed/combined_test_plan_with_checklist_plotting/8-select_data_from_figure.py.md +97 -0
  89. plans/completed/combined_test_plan_with_checklist_plotting/9-labels-base.py.md +88 -0
  90. plans/completed/combined_test_plan_with_checklist_solar_activity/.gitkeep +0 -0
  91. plans/completed/combined_test_plan_with_checklist_solar_activity/0-Overview.md +170 -0
  92. plans/completed/combined_test_plan_with_checklist_solar_activity/1-Package-Entry-Point-__init__.py.md +121 -0
  93. plans/completed/combined_test_plan_with_checklist_solar_activity/2-Core-Base-Classes-base.py.md +142 -0
  94. plans/completed/combined_test_plan_with_checklist_solar_activity/3-Plotting-Helpers-plots.py.md +123 -0
  95. plans/completed/combined_test_plan_with_checklist_solar_activity/4-LISIRD-Sub-package.md +119 -0
  96. plans/completed/combined_test_plan_with_checklist_solar_activity/5-Extrema-Calculator.md +103 -0
  97. plans/completed/combined_test_plan_with_checklist_solar_activity/6-Sunspot-Number-Sub-package.md +163 -0
  98. plans/completed/combined_test_plan_with_checklist_solar_activity/7-Sunspot-Number-Init.py.md +217 -0
  99. plans/completed/combined_test_plan_with_checklist_solar_activity/compacted_state.md +52 -0
  100. plans/completed/compaction-agent-modernization/0-Overview.md +156 -0
  101. plans/completed/compaction-agent-modernization/1-Architecture-Audit-Gap-Analysis.md +132 -0
  102. plans/completed/compaction-agent-modernization/2-Token-Baseline-Recalibration.md +153 -0
  103. plans/completed/compaction-agent-modernization/3-Agent-Reference-Updates.md +184 -0
  104. plans/completed/compaction-agent-modernization/4-Compression-Algorithm-Modernization.md +238 -0
  105. plans/completed/compaction-agent-modernization/5-Workflow-Integration-Streamlining.md +252 -0
  106. plans/completed/compaction-agent-modernization/6-Template-Structure-Optimization.md +240 -0
  107. plans/completed/compaction-agent-modernization/7-Integration-Testing-Validation.md +292 -0
  108. plans/completed/compaction-hook-enhancement/0-Overview.md +150 -0
  109. plans/completed/compaction-hook-enhancement/1-Token-Estimation-Enhancement.md +179 -0
  110. plans/completed/compaction-hook-enhancement/2-Compression-Intelligence.md +294 -0
  111. plans/completed/compaction-hook-enhancement/3-Git-Integration-Metadata.md +310 -0
  112. plans/completed/compaction-hook-enhancement/4-Session-Continuity-Features.md +358 -0
  113. plans/completed/compaction-hook-enhancement/5-Testing-Strategy.md +404 -0
  114. plans/completed/compaction-hook-enhancement/6-Integration-Roadmap.md +319 -0
  115. plans/completed/compaction-hook-enhancement/compacted_state.md +142 -0
  116. plans/completed/docstring-audit-enhancement/0-Overview.md +274 -0
  117. plans/completed/docstring-audit-enhancement/1-Infrastructure-Setup-and-Validation-Tools.md +206 -0
  118. plans/completed/docstring-audit-enhancement/2-Core-Physics-Modules-Enhancement.md +237 -0
  119. plans/completed/docstring-audit-enhancement/3-Fitfunctions-Mathematical-Modules-Enhancement.md +188 -0
  120. plans/completed/docstring-audit-enhancement/4-Plotting-Visualization-Modules-Enhancement.md +243 -0
  121. plans/completed/docstring-audit-enhancement/5-Specialized-Modules-Enhancement.md +216 -0
  122. plans/completed/docstring-audit-enhancement/6-Validation-and-Integration.md +216 -0
  123. plans/completed/fitfunctions-testing-implementation/0-Overview.md +130 -0
  124. plans/completed/fitfunctions-testing-implementation/1-Test-Infrastructure-Setup.md +79 -0
  125. plans/completed/fitfunctions-testing-implementation/2-Common-Fixtures-Test-Utilities.md +104 -0
  126. plans/completed/fitfunctions-testing-implementation/3-Core-FitFunction-Testing.md +168 -0
  127. plans/completed/fitfunctions-testing-implementation/4-Specialized-Function-Classes.md +210 -0
  128. plans/completed/fitfunctions-testing-implementation/5-Advanced-Classes-Testing.md +214 -0
  129. plans/completed/fitfunctions-testing-implementation/6-Plotting-Integration-Testing.md +231 -0
  130. plans/completed/fitfunctions-testing-implementation/7-Extended-Coverage-BONUS.md +184 -0
  131. plans/completed/numpy-docstring-conversion-plan/numpy-docstring-conversion-plan.md +118 -0
  132. plans/completed/pr-review-remediation/0-Overview.md +138 -0
  133. plans/completed/pr-review-remediation/1-Critical-Safety-Improvements.md +179 -0
  134. plans/completed/pr-review-remediation/2-Smart-Timeouts-Validation.md +399 -0
  135. plans/completed/pr-review-remediation/3-Enhanced-GitHub-Integration.md +258 -0
  136. plans/completed/pr-review-remediation/compacted_state.md +66 -0
  137. plans/completed/python-310-migration/0-Overview.md +390 -0
  138. plans/completed/python-310-migration/1-Planning-Setup.md +164 -0
  139. plans/completed/python-310-migration/2-Implementation.md +256 -0
  140. plans/completed/python-310-migration/3-Testing-Validation.md +335 -0
  141. plans/completed/python-310-migration/4-Documentation-Release.md +274 -0
  142. plans/completed/python-310-migration/5-Closeout.md +252 -0
  143. plans/completed/requirements-management-consolidation/0-Overview.md +118 -0
  144. plans/completed/requirements-management-consolidation/1-Documentation-Validation-Environment-Setup.md +116 -0
  145. plans/completed/requirements-management-consolidation/2-Requirements-Consolidation.md +161 -0
  146. plans/completed/requirements-management-consolidation/3-Workflow-Automation-Final-Integration.md +196 -0
  147. plans/completed/single-ecosystem-plan-implementation/0-Overview.md +83 -0
  148. plans/completed/single-ecosystem-plan-implementation/1-Plan-Preservation-Session-Management.md +38 -0
  149. plans/completed/single-ecosystem-plan-implementation/2-File-Structure-Optimization.md +43 -0
  150. plans/completed/single-ecosystem-plan-implementation/3-Plan-Migration-Archive-Setup.md +82 -0
  151. plans/completed/single-ecosystem-plan-implementation/4-Agent-System-Transformation.md +108 -0
  152. plans/completed/single-ecosystem-plan-implementation/5-Template-System-Enhancement.md +131 -0
  153. plans/completed/single-ecosystem-plan-implementation/6-Final-Validation-Testing.md +120 -0
  154. plans/completed/test-directory-consolidation/0-Overview.md +51 -0
  155. plans/completed/test-directory-consolidation/1-Structure-Preparation.md +82 -0
  156. plans/completed/test-directory-consolidation/2-File-Migration.md +100 -0
  157. plans/completed/test-directory-consolidation/3-Import-Transformation.md +117 -0
  158. plans/completed/test-directory-consolidation/4-Configuration-Consolidation.md +140 -0
  159. plans/completed/test-directory-consolidation/5-Validation.md +152 -0
  160. plans/completed/test-directory-consolidation/6-Cleanup.md +156 -0
  161. plans/completed/test-planning-agents-architecture/0-Overview.md +79 -0
  162. plans/completed/test-planning-agents-architecture/1-Branch-Isolation-Testing.md +49 -0
  163. plans/completed/test-planning-agents-architecture/2-Cross-Branch-Coordination.md +51 -0
  164. plans/completed/test-planning-agents-architecture/3-Merge-Workflow-Testing.md +48 -0
  165. plans/deployment-semver-pypi-rtd/0-Overview.md +463 -0
  166. plans/deployment-semver-pypi-rtd/1-Semantic-Versioning-Foundation.md +136 -0
  167. plans/deployment-semver-pypi-rtd/2-PyPI-Deployment-Infrastructure.md +168 -0
  168. plans/deployment-semver-pypi-rtd/3-Release-Automation.md +214 -0
  169. plans/deployment-semver-pypi-rtd/4-Plan-Closeout.md +543 -0
  170. plans/deployment-semver-pypi-rtd/compacted_session_state.md +172 -0
  171. plans/deployment-semver-pypi-rtd/compacted_state.md +131 -0
  172. plans/documentation-code-audit/0-Overview.md +393 -0
  173. plans/documentation-code-audit/1-Discovery-Inventory.md +183 -0
  174. plans/documentation-code-audit/2-Execution-Environment-Setup.md +263 -0
  175. plans/documentation-code-audit/3-Systematic-Validation.md +322 -0
  176. plans/documentation-code-audit/4-Code-Example-Remediation.md +358 -0
  177. plans/documentation-code-audit/5-Physics-MultiIndex-Compliance.md +464 -0
  178. plans/documentation-code-audit/6-Doctest-Integration.md +523 -0
  179. plans/documentation-code-audit/7-Reporting-Documentation.md +498 -0
  180. plans/documentation-code-audit/8-Closeout.md +456 -0
  181. plans/documentation-rebuild-session/compacted_state.md +109 -0
  182. plans/documentation-rendering-fixes/0-Overview.md +104 -0
  183. plans/documentation-rendering-fixes/1-Sphinx-Build-Diagnostics-Warning-Audit.md +101 -0
  184. plans/documentation-rendering-fixes/2-Configuration-Infrastructure-Fixes.md +113 -0
  185. plans/documentation-rendering-fixes/3-Docstring-Syntax-Audit-Repair.md +131 -0
  186. plans/documentation-rendering-fixes/4-HTML-Page-Rendering-Verification.md +113 -0
  187. plans/documentation-rendering-fixes/5-Advanced-Documentation-Quality-Assurance.md +119 -0
  188. plans/documentation-rendering-fixes/6-Documentation-Build-Optimization-Testing.md +129 -0
  189. plans/documentation-rendering-fixes/compacted_state.md +132 -0
  190. plans/documentation-template-fix/0-Overview.md +197 -0
  191. plans/documentation-template-fix/1-Template-System-Analysis.md +269 -0
  192. plans/documentation-template-fix/2-Template-Modification.md +609 -0
  193. plans/documentation-template-fix/3-Build-System-Integration.md +766 -0
  194. plans/documentation-template-fix/4-Testing-Validation.md +1399 -0
  195. plans/documentation-template-fix/5-Documentation-Training.md +602 -0
  196. plans/documentation-workflow-fix/0-Overview.md +222 -0
  197. plans/documentation-workflow-fix/1-Immediate-Fixes.md +238 -0
  198. plans/documentation-workflow-fix/2-Configuration-Setup.md +298 -0
  199. plans/documentation-workflow-fix/3-Pre-commit-Integration.md +382 -0
  200. plans/documentation-workflow-fix/4-Workflow-Improvements.md +446 -0
  201. plans/documentation-workflow-fix/5-Documentation-and-Training.md +527 -0
  202. plans/duplicate-object-warnings-fix-plan.md +130 -0
  203. plans/github-issues-migration/0-Overview.md +510 -0
  204. plans/github-issues-migration/1-Foundation-Label-System.md +180 -0
  205. plans/github-issues-migration/2-Migration-Tool-Rewrite.md +235 -0
  206. plans/github-issues-migration/3-CLI-Integration-Automation.md +169 -0
  207. plans/github-issues-migration/4-Validated-Migration.md +252 -0
  208. plans/github-issues-migration/5-Documentation-Training.md +171 -0
  209. plans/github-issues-migration/6-Closeout.md +179 -0
  210. plans/github-workflows-repair/repair-plan.md +299 -0
  211. plans/issues_from_plans.py +342 -0
  212. plans/pr-270-doc-validation-fixes/0-Overview.md +354 -0
  213. plans/pr-270-doc-validation-fixes/1-Critical-PR-Fixes.md +117 -0
  214. plans/pr-270-doc-validation-fixes/2-Framework-Right-Sizing.md +129 -0
  215. plans/pr-270-doc-validation-fixes/3-Sustainable-Documentation.md +126 -0
  216. plans/pr-270-doc-validation-fixes/4-Closeout-Migration.md +143 -0
  217. plans/pr-270-doc-validation-fixes/PLAN_COMPLETED.md +149 -0
  218. plans/python-310-migration/0-Overview.md +390 -0
  219. plans/python-310-migration/1-Planning-Setup.md +164 -0
  220. plans/python-310-migration/2-Implementation.md +256 -0
  221. plans/python-310-migration/3-Testing-Validation.md +335 -0
  222. plans/python-310-migration/4-Documentation-Release.md +274 -0
  223. plans/python-310-migration/5-Closeout.md +252 -0
  224. plans/readthedocs-simplified/0-Overview.md +243 -0
  225. plans/readthedocs-simplified/1-Immediate-Fixes.md +216 -0
  226. plans/readthedocs-simplified/2-Template-Simplification.md +278 -0
  227. plans/readthedocs-simplified/3-ReadTheDocs-Setup.md +298 -0
  228. plans/readthedocs-simplified/4-Testing-Validation.md +328 -0
  229. plans/readthedocs-simplified/5-Closeout.md +231 -0
  230. plans/readthedocs-simplified/compacted_state.md +127 -0
  231. plans/session-compaction-2025-08-12/compacted_state.md +114 -0
  232. plans/session-compaction-2025-08-13/compacted_state.md +145 -0
  233. plans/session-continuity-protocol/0-Overview.md +35 -0
  234. plans/session-continuity-protocol/1-Core-Principles-Framework.md +40 -0
  235. plans/session-continuity-protocol/2-Pre-Session-Validation-System.md +79 -0
  236. plans/session-continuity-protocol/3-Context-Switching-Prevention.md +87 -0
  237. plans/session-continuity-protocol/4-Progress-Tracking-Recovery.md +100 -0
  238. plans/sphinx-warnings-analysis.md +222 -0
  239. plans/systemprompt-optimization/0-Overview.md +447 -0
  240. plans/systemprompt-optimization/1-Deploy-SystemPrompt.md +114 -0
  241. plans/systemprompt-optimization/2-Documentation-Alignment.md +198 -0
  242. plans/systemprompt-optimization/3-Monitoring-Infrastructure.md +396 -0
  243. plans/systemprompt-optimization/4-Implementation-Script.md +450 -0
  244. plans/systemprompt-optimization/9-Closeout.md +165 -0
  245. plans/systemprompt-optimization/compacted_state.md +143 -0
  246. plans/template-value-propositions/0-Overview.md +357 -0
  247. plans/template-value-propositions/1-Value-Proposition-Framework-Design.md +144 -0
  248. plans/template-value-propositions/2-Plan-Template-Enhancement.md +178 -0
  249. plans/template-value-propositions/3-Value-Generator-Hook-Implementation.md +291 -0
  250. plans/template-value-propositions/4-Value-Validator-Hook-Implementation.md +274 -0
  251. plans/template-value-propositions/5-Documentation-Agent-Updates.md +219 -0
  252. plans/template-value-propositions/6-Integration-Testing-Validation.md +247 -0
  253. plans/tests-audit/0-Overview.md +410 -0
  254. plans/tests-audit/1-Discovery-Inventory.md +170 -0
  255. plans/tests-audit/2-Physics-Validation-Audit.md +195 -0
  256. plans/tests-audit/3-Architecture-Compliance.md +195 -0
  257. plans/tests-audit/4-Numerical-Stability-Analysis.md +203 -0
  258. plans/tests-audit/5-Documentation-Enhancement.md +220 -0
  259. plans/tests-audit/6-Audit-Deliverables.md +220 -0
  260. plans/tests-audit/7-Closeout.md +252 -0
  261. plans/tests-audit/artifacts/ARCHITECTURE_COMPLIANCE_REPORT.md +315 -0
  262. plans/tests-audit/artifacts/ARCHITECTURE_RECOMMENDATIONS.md +943 -0
  263. plans/tests-audit/artifacts/COMPREHENSIVE_AUDIT_REPORT.md +356 -0
  264. plans/tests-audit/artifacts/CONTRIBUTING_ENHANCED_TEMPLATE.md +419 -0
  265. plans/tests-audit/artifacts/COVERAGE_GAP_ANALYSIS.md +152 -0
  266. plans/tests-audit/artifacts/DOCUMENTATION_ENHANCEMENT_REPORT.md +502 -0
  267. plans/tests-audit/artifacts/EXECUTIVE_AUDIT_SUMMARY.md +129 -0
  268. plans/tests-audit/artifacts/IMPLEMENTATION_ROADMAP.md +647 -0
  269. plans/tests-audit/artifacts/NUMERICAL_RECOMMENDATIONS.md +739 -0
  270. plans/tests-audit/artifacts/NUMERICAL_STABILITY_GUIDE_TEMPLATE.rst +451 -0
  271. plans/tests-audit/artifacts/NUMERICAL_STABILITY_REPORT.md +301 -0
  272. plans/tests-audit/artifacts/PHASE_3_SUMMARY.md +280 -0
  273. plans/tests-audit/artifacts/PHASE_4_SUMMARY.md +229 -0
  274. plans/tests-audit/artifacts/PHASE_5_SUMMARY.md +292 -0
  275. plans/tests-audit/artifacts/PHASE_6_CLOSEOUT.md +278 -0
  276. plans/tests-audit/artifacts/PHYSICS_GUIDE_TEMPLATE.rst +268 -0
  277. plans/tests-audit/artifacts/PHYSICS_VALIDATION_REPORT.md +235 -0
  278. plans/tests-audit/artifacts/TECHNICAL_DELIVERABLES_PACKAGE.md +2502 -0
  279. plans/tests-audit/artifacts/TEST_INVENTORY.csv +1204 -0
  280. plans/tests-audit/artifacts/TEST_INVENTORY.md +135 -0
  281. plans/tests-audit/artifacts/test_discovery_analysis.py +231 -0
  282. plans/tests-audit/artifacts/test_parser.py +395 -0
  283. solarwindpy/README.md +3 -0
  284. solarwindpy/Untitled.ipynb +54 -0
  285. solarwindpy/__init__.py +74 -0
  286. solarwindpy/core/__init__.py +23 -0
  287. solarwindpy/core/alfvenic_turbulence.py +804 -0
  288. solarwindpy/core/base.py +267 -0
  289. solarwindpy/core/ions.py +309 -0
  290. solarwindpy/core/plasma.py +2133 -0
  291. solarwindpy/core/spacecraft.py +256 -0
  292. solarwindpy/core/tensor.py +90 -0
  293. solarwindpy/core/units_constants.py +199 -0
  294. solarwindpy/core/vector.py +328 -0
  295. solarwindpy/fitfunctions/__init__.py +20 -0
  296. solarwindpy/fitfunctions/core.py +734 -0
  297. solarwindpy/fitfunctions/exponentials.py +188 -0
  298. solarwindpy/fitfunctions/gaussians.py +264 -0
  299. solarwindpy/fitfunctions/lines.py +116 -0
  300. solarwindpy/fitfunctions/moyal.py +71 -0
  301. solarwindpy/fitfunctions/plots.py +751 -0
  302. solarwindpy/fitfunctions/power_laws.py +209 -0
  303. solarwindpy/fitfunctions/tex_info.py +568 -0
  304. solarwindpy/fitfunctions/trend_fits.py +482 -0
  305. solarwindpy/instabilities/__init__.py +16 -0
  306. solarwindpy/instabilities/beta_ani.py +82 -0
  307. solarwindpy/instabilities/verscharen2016.py +631 -0
  308. solarwindpy/plotting/__init__.py +33 -0
  309. solarwindpy/plotting/agg_plot.py +489 -0
  310. solarwindpy/plotting/base.py +465 -0
  311. solarwindpy/plotting/hist1d.py +405 -0
  312. solarwindpy/plotting/hist2d.py +1035 -0
  313. solarwindpy/plotting/histograms.py +1845 -0
  314. solarwindpy/plotting/labels/__init__.py +104 -0
  315. solarwindpy/plotting/labels/base.py +686 -0
  316. solarwindpy/plotting/labels/chemistry.py +19 -0
  317. solarwindpy/plotting/labels/composition.py +100 -0
  318. solarwindpy/plotting/labels/datetime.py +235 -0
  319. solarwindpy/plotting/labels/elemental_abundance.py +73 -0
  320. solarwindpy/plotting/labels/special.py +794 -0
  321. solarwindpy/plotting/orbits.py +515 -0
  322. solarwindpy/plotting/scatter.py +99 -0
  323. solarwindpy/plotting/select_data_from_figure.py +329 -0
  324. solarwindpy/plotting/spiral.py +980 -0
  325. solarwindpy/plotting/tools.py +434 -0
  326. solarwindpy/scripts/__init__.py +1 -0
  327. solarwindpy/scripts/logs/.gitignore +1 -0
  328. solarwindpy/solar_activity/__init__.py +53 -0
  329. solarwindpy/solar_activity/base.py +605 -0
  330. solarwindpy/solar_activity/lisird/__init__.py +3 -0
  331. solarwindpy/solar_activity/lisird/extrema_calculator.py +394 -0
  332. solarwindpy/solar_activity/lisird/lisird.py +319 -0
  333. solarwindpy/solar_activity/plots.py +116 -0
  334. solarwindpy/solar_activity/sunspot_number/.DS_Store +0 -0
  335. solarwindpy/solar_activity/sunspot_number/__init__.py +3 -0
  336. solarwindpy/solar_activity/sunspot_number/sidc.py +556 -0
  337. solarwindpy/solar_activity/sunspot_number/ssn_extrema.csv +72 -0
  338. solarwindpy/solar_activity/sunspot_number/ssn_extrema.csv.silso +72 -0
  339. solarwindpy/tools/__init__.py +162 -0
  340. solarwindpy-0.1.0.dist-info/METADATA +181 -0
  341. solarwindpy-0.1.0.dist-info/RECORD +409 -0
  342. {solarwindpy-0.0.1.dev0.dist-info → solarwindpy-0.1.0.dist-info}/WHEEL +1 -1
  343. solarwindpy-0.1.0.dist-info/licenses/LICENSE.rst +32 -0
  344. solarwindpy-0.1.0.dist-info/top_level.txt +3 -0
  345. tests/__init__.py +1 -0
  346. tests/conftest.py +10 -0
  347. tests/core/__init__.py +1 -0
  348. tests/core/test_alfvenic_turbulence.py +544 -0
  349. tests/core/test_base.py +112 -0
  350. tests/core/test_base_head_tail.py +29 -0
  351. tests/core/test_base_mi_tuples.py +11 -0
  352. tests/core/test_core_verify_datetimeindex.py +32 -0
  353. tests/core/test_ions.py +325 -0
  354. tests/core/test_plasma.py +2581 -0
  355. tests/core/test_plasma_io.py +12 -0
  356. tests/core/test_quantities.py +507 -0
  357. tests/core/test_spacecraft.py +210 -0
  358. tests/core/test_units_constants.py +22 -0
  359. tests/data/epoch.csv +4 -0
  360. tests/data/plasma.csv +4 -0
  361. tests/data/spacecraft.csv +4 -0
  362. tests/fitfunctions/conftest.py +60 -0
  363. tests/fitfunctions/test_core.py +193 -0
  364. tests/fitfunctions/test_exponentials.py +342 -0
  365. tests/fitfunctions/test_gaussians.py +142 -0
  366. tests/fitfunctions/test_lines.py +349 -0
  367. tests/fitfunctions/test_moyal.py +258 -0
  368. tests/fitfunctions/test_plots.py +258 -0
  369. tests/fitfunctions/test_power_laws.py +365 -0
  370. tests/fitfunctions/test_tex_info.py +183 -0
  371. tests/fitfunctions/test_trend_fit_properties.py +31 -0
  372. tests/fitfunctions/test_trend_fits.py +244 -0
  373. tests/plotting/__init__.py +1 -0
  374. tests/plotting/labels/__init__.py +1 -0
  375. tests/plotting/labels/test_chemistry.py +243 -0
  376. tests/plotting/labels/test_composition.py +345 -0
  377. tests/plotting/labels/test_datetime.py +445 -0
  378. tests/plotting/labels/test_elemental_abundance.py +366 -0
  379. tests/plotting/labels/test_init.py +66 -0
  380. tests/plotting/labels/test_labels_base.py +347 -0
  381. tests/plotting/labels/test_special.py +550 -0
  382. tests/plotting/test_agg_plot.py +602 -0
  383. tests/plotting/test_base.py +752 -0
  384. tests/plotting/test_fixtures_utilities.py +775 -0
  385. tests/plotting/test_histograms.py +546 -0
  386. tests/plotting/test_integration.py +675 -0
  387. tests/plotting/test_orbits.py +435 -0
  388. tests/plotting/test_performance.py +708 -0
  389. tests/plotting/test_scatter.py +752 -0
  390. tests/plotting/test_select_data_from_figure.py +1209 -0
  391. tests/plotting/test_spiral.py +573 -0
  392. tests/plotting/test_tools.py +607 -0
  393. tests/plotting/test_visual_validation.py +465 -0
  394. tests/solar_activity/__init__.py +1 -0
  395. tests/solar_activity/lisird/__init__.py +1 -0
  396. tests/solar_activity/lisird/test_extrema_calculator.py +593 -0
  397. tests/solar_activity/lisird/test_lisird_id.py +187 -0
  398. tests/solar_activity/sunspot_number/__init__.py +1 -0
  399. tests/solar_activity/sunspot_number/test_init.py +399 -0
  400. tests/solar_activity/sunspot_number/test_sidc.py +465 -0
  401. tests/solar_activity/sunspot_number/test_sidc_id.py +223 -0
  402. tests/solar_activity/sunspot_number/test_sidc_loader.py +275 -0
  403. tests/solar_activity/sunspot_number/test_ssn_extrema.py +406 -0
  404. tests/solar_activity/test_base.py +656 -0
  405. tests/solar_activity/test_init.py +396 -0
  406. tests/solar_activity/test_plots.py +371 -0
  407. tests/test_circular_imports.py +408 -0
  408. tests/test_issue_titles.py +25 -0
  409. tests/test_statusline.py +298 -0
  410. solarwindpy-0.0.1.dev0.dist-info/METADATA +0 -14
  411. solarwindpy-0.0.1.dev0.dist-info/RECORD +0 -4
  412. solarwindpy-0.0.1.dev0.dist-info/top_level.txt +0 -1
@@ -0,0 +1,2581 @@
1
+ #!/usr/bin/env python
2
+ """Tests for the :class:`Plasma` container."""
3
+ import pandas as pd
4
+ import numpy as np
5
+ import itertools
6
+ import pandas.testing as pdt
7
+
8
+ from abc import ABC, abstractproperty, abstractmethod
9
+
10
+ from scipy import constants
11
+ from scipy.constants import physical_constants
12
+
13
+ # import test_base as base
14
+ from . import test_base as base
15
+
16
+ from solarwindpy import vector
17
+ from solarwindpy import ions
18
+ from solarwindpy import plasma
19
+ from solarwindpy import spacecraft
20
+ from solarwindpy import alfvenic_turbulence
21
+
22
+ pd.set_option("mode.chained_assignment", "raise")
23
+
24
+
25
+ class PlasmaTestBase(ABC):
26
+ @classmethod
27
+ def set_object_testing(cls):
28
+ # print(cls.__class__, "set_object_testing", flush=True)
29
+ # print("Data", cls.data, sep="\n")
30
+ data = cls.data
31
+ plas = plasma.Plasma(data, *cls().species.split("+"))
32
+
33
+ par = data.w.par.pow(2)
34
+ per = data.w.per.pow(2)
35
+ scalar = ((2.0 * per) + par).multiply(1.0 / 3.0).pipe(np.sqrt)
36
+ cols = scalar.columns.to_series().apply(lambda x: ("w", "scalar", x))
37
+ scalar.columns = pd.MultiIndex.from_tuples(cols, names=["M", "C", "S"])
38
+ data = pd.concat([data, scalar], axis=1, sort=True)
39
+
40
+ cls.object_testing = plas
41
+ cls.data = data
42
+ # print("Done with", cls.__class__, flush=True)
43
+
44
+ @abstractproperty
45
+ def species(self):
46
+ pass
47
+
48
+ @property
49
+ def stuple(self):
50
+ return tuple(self.species.split("+"))
51
+
52
+ @property
53
+ def species_combinations(self):
54
+ r"""The various combinations of the species for use in testing Plasma
55
+ methods."""
56
+ from itertools import combinations, chain
57
+
58
+ stuple = self.stuple
59
+ ncombinations = np.arange(start=1, stop=len(stuple) + 1)
60
+ if ncombinations.any():
61
+ combos = chain(*[combinations(stuple, n) for n in ncombinations])
62
+ return combos
63
+ else:
64
+ return None
65
+
66
+ @property
67
+ def mass(self):
68
+ trans = {
69
+ "a": "alpha particle",
70
+ "p": "p,roton",
71
+ "p1": "proton",
72
+ "p2": "proton",
73
+ "e": "electron",
74
+ }
75
+ m = {s: physical_constants["%s mass" % trans[s]][0] for s in self.stuple}
76
+ return pd.Series(m)
77
+
78
+ @property
79
+ def mass_in_mp(self):
80
+ trans = {
81
+ "a": physical_constants["alpha particle-proton mass ratio"][0],
82
+ "p": 1,
83
+ "p1": 1,
84
+ "p2": 1,
85
+ "e": physical_constants["electron-proton mass ratio"][0],
86
+ }
87
+ return pd.Series({s: trans[s] for s in self.stuple})
88
+
89
+ @property
90
+ def m_amu(self):
91
+ r"""Masses in amu."""
92
+ a = physical_constants["alpha particle mass in u"][0]
93
+ p = physical_constants["proton mass in u"][0]
94
+ e = physical_constants["electron mass in u"][0]
95
+ out = {"a": a, "p": p, "p1": p, "p2": p, "e": e}
96
+ return pd.Series({s: out[s] for s in self.stuple})
97
+
98
+ @property
99
+ def charges(self):
100
+ out = {
101
+ "e": -constants.e,
102
+ "p": constants.e,
103
+ "p1": constants.e,
104
+ "p2": constants.e,
105
+ "a": 2 * constants.e,
106
+ }
107
+ return pd.Series({s: out[s] for s in self.stuple})
108
+
109
+ @property
110
+ def charge_states(self):
111
+ out = {"e": -1.0, "p": 1.0, "p1": 1.0, "p2": 1.0, "a": 2.0}
112
+ return pd.Series({s: out[s] for s in self.stuple})
113
+
114
+ def test_ions(self):
115
+ ions_ = pd.Series({s: ions.Ion(self.data, s) for s in self.stuple})
116
+ pdt.assert_index_equal(ions_.index, self.object_testing.ions.index)
117
+ for k, i in ions_.items():
118
+ pdt.assert_frame_equal(
119
+ i.data,
120
+ self.object_testing.ions.loc[k].data,
121
+ "Unequal data for ion: %s" % k,
122
+ )
123
+
124
+ def test_conform_species(self):
125
+ r"""Just test that the species is a valid input."""
126
+ slist = (
127
+ "a",
128
+ "e",
129
+ "p1",
130
+ "p2",
131
+ "a+p1",
132
+ "p1+p2",
133
+ "a+p2",
134
+ "a+p1+p2",
135
+ "a+p1+e",
136
+ "p1+p2+e",
137
+ "a+p2+e",
138
+ "a+p1+p2+e",
139
+ )
140
+ # Check that the exception isn't raised.
141
+ for s in slist:
142
+ self.assertEqual(
143
+ self.object_testing._conform_species(s), tuple(sorted(s.split("+")))
144
+ )
145
+
146
+ slist = (
147
+ "a,p1",
148
+ "p1,p2",
149
+ "a,p2",
150
+ "a,p1,p2",
151
+ "a,p1,e",
152
+ "p1,p2,e",
153
+ "a,p2,e",
154
+ "a,p1,p2,e",
155
+ "a,p1+p2",
156
+ "a,p1+e",
157
+ "a+e,p1,p2",
158
+ )
159
+ for s in slist:
160
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
161
+ self.object_testing._conform_species(s)
162
+
163
+ if "+" in s:
164
+ # A species list for which one species contains "+" is not
165
+ # uniformly parsable.
166
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
167
+ self.object_testing._conform_species(*s.split(","))
168
+
169
+ def test_chk_species_success(self):
170
+ # print(self.slist)
171
+ self.assertEqual(self.stuple, self.object_testing._chk_species(*self.stuple))
172
+
173
+ # Ensure exception isn't raised for plasma's individual species.
174
+ for s in self.stuple:
175
+ # Automatically passes if no exception raised.
176
+ self.object_testing._chk_species(s)
177
+
178
+ # Check that any subset of the species and "s0+s1+..." pass,
179
+ # but "s0,s1" don't.
180
+ # combo_sizes = np.arange(start=2, stop=len(self.stuple) + 1)
181
+ bad_species_msg = "Invalid species:"
182
+ # if combo_sizes.any():
183
+ # for size in combo_sizes:
184
+ # combos = itertools.combinations(stuple, size)
185
+ for combo in self.species_combinations:
186
+ # print(combo, s, sep="\n")
187
+ self.object_testing._chk_species(*combo)
188
+ self.object_testing._chk_species("+".join(combo))
189
+ bad_species = ",".join(combo)
190
+ if "," in bad_species:
191
+ with self.assertRaisesRegex(ValueError, bad_species_msg):
192
+ self.object_testing._chk_species(bad_species)
193
+
194
+ @abstractmethod
195
+ def test_chk_species_fail(self):
196
+ r"""This method must be subclassed to test for species that fail the
197
+ `_chk_species` tests.
198
+
199
+ The code will look something like:
200
+ for s in bad_species:
201
+ with self.assertRaisesRegex(ValueError,
202
+ "Requested species unavailable."):
203
+ self.object_testing._chk_species(*s)
204
+ """
205
+ pass
206
+
207
+ def test_species(self):
208
+ self.assertEqual(self.object_testing.species, self.stuple)
209
+
210
+ def test__set_species(self):
211
+ with self.assertRaisesRegex(
212
+ ValueError, "You must specify a species to instantiate a Plasma."
213
+ ):
214
+ plasma.Plasma(self.object_testing.data)
215
+
216
+ def test_bfield(self):
217
+ b = self.data.b.xs("", axis=1, level="S").loc[:, ["x", "y", "z"]]
218
+ self.assertEqual(vector.BField(b), self.object_testing.bfield)
219
+ self.assertEqual(vector.BField(b), self.object_testing.b)
220
+ self.assertEqual(self.object_testing.b, self.object_testing.bfield)
221
+
222
+ def test_number_density(self):
223
+ ot = self.object_testing
224
+
225
+ # print_inline_debug_info = False
226
+ ions_ = pd.concat(
227
+ {s: ot.ions[s].number_density for s in self.stuple},
228
+ axis=1,
229
+ names=["S"],
230
+ sort=True,
231
+ )
232
+ total_ions = ions_.sum(axis=1)
233
+ total_ions.name = "+".join(self.stuple)
234
+ sum_species = self.species
235
+
236
+ pdt.assert_series_equal(total_ions, ot.number_density(sum_species))
237
+ pdt.assert_series_equal(total_ions, ot.n(sum_species))
238
+ pdt.assert_series_equal(ot.n(sum_species), ot.number_density(sum_species))
239
+
240
+ # print("", *self.species_combinations, sep="\n")
241
+ # Check that plasma returns each ion species independently.
242
+ for s in self.species_combinations:
243
+ this_ion = ions_.loc[:, s[0] if len(s) == 1 else s]
244
+
245
+ # if print_inline_debug_info:
246
+ # print(s)
247
+ # print("<Ion>", type(this_ion))
248
+ # print(this_ion)
249
+ # print("<Plasma>", type(ot.number_density(*s)))
250
+ # print(ot.number_density(*s))
251
+
252
+ if isinstance(this_ion, pd.Series):
253
+ fcn = pdt.assert_series_equal
254
+ else:
255
+ fcn = pdt.assert_frame_equal
256
+
257
+ fcn(this_ion, ot.number_density(*s))
258
+ fcn(this_ion, ot.n(*s))
259
+ fcn(ot.n(*s), ot.number_density(*s))
260
+
261
+ if len(s) > 1:
262
+ this_ion = this_ion.sum(axis=1)
263
+ this_ion.name = "+".join(sorted(s))
264
+ pdt.assert_series_equal(this_ion, ot.number_density("+".join(s)))
265
+ pdt.assert_series_equal(this_ion, ot.n("+".join(s)))
266
+ pdt.assert_series_equal(
267
+ ot.number_density("+".join(s)), ot.n("+".join(s))
268
+ )
269
+
270
+ def test_mass_density(self):
271
+ ot = self.object_testing
272
+
273
+ # print_inline_debug_info = False
274
+ ions_ = pd.concat(
275
+ {s: ot.ions[s].mass_density for s in self.stuple},
276
+ axis=1,
277
+ names=["S"],
278
+ sort=True,
279
+ )
280
+ total_ions = ions_.sum(axis=1)
281
+ total_ions.name = "+".join(self.stuple)
282
+ sum_species = self.species
283
+
284
+ pdt.assert_series_equal(total_ions, ot.mass_density(sum_species))
285
+ pdt.assert_series_equal(total_ions, ot.rho(sum_species))
286
+ pdt.assert_series_equal(ot.rho(sum_species), ot.mass_density(sum_species))
287
+
288
+ # print("", *self.species_combinations, sep="\n")
289
+ # Check that plasma returns each ion species independently.
290
+ for s in self.species_combinations:
291
+ this_ion = ions_.loc[:, s[0] if len(s) == 1 else s]
292
+
293
+ # if print_inline_debug_info:
294
+ # print(s)
295
+ # print("<Ion>", type(this_ion))
296
+ # print(this_ion)
297
+ # print("<Plasma>", type(ot.mass_density(*s)))
298
+ # print(ot.mass_density(*s))
299
+
300
+ if isinstance(this_ion, pd.Series):
301
+ fcn = pdt.assert_series_equal
302
+ else:
303
+ fcn = pdt.assert_frame_equal
304
+
305
+ fcn(this_ion, ot.mass_density(*s))
306
+ fcn(this_ion, ot.rho(*s))
307
+ fcn(ot.rho(*s), ot.mass_density(*s))
308
+
309
+ if len(s) > 1:
310
+ this_ion = this_ion.sum(axis=1)
311
+ this_ion.name = "+".join(sorted(s))
312
+ pdt.assert_series_equal(this_ion, ot.mass_density("+".join(s)))
313
+ pdt.assert_series_equal(this_ion, ot.rho("+".join(s)))
314
+ pdt.assert_series_equal(
315
+ ot.mass_density("+".join(s)), ot.rho("+".join(s))
316
+ )
317
+
318
+ def test_thermal_speed(self):
319
+ ot = self.object_testing
320
+ ions_ = {s: ot.ions.loc[s].thermal_speed.data for s in self.stuple}
321
+ ions_ = pd.concat(ions_, axis=1, names=["S"], sort=True)
322
+ ions_ = ions_.reorder_levels(["C", "S"], axis=1).sort_index(axis=1)
323
+
324
+ for s in self.species_combinations:
325
+ if len(s) == 1:
326
+ this_ion = ions_.xs(s[0], axis=1, level="S")
327
+ pdt.assert_frame_equal(this_ion, ot.thermal_speed(*s))
328
+ pdt.assert_frame_equal(ot.thermal_speed(*s), ot.w(*s))
329
+ pdt.assert_frame_equal(ot.thermal_speed(s[0]), ot.w(*s))
330
+
331
+ # Test that the scalar thermal speed is as expected in plasma.
332
+ scalar = this_ion.loc[:, "scalar"].pow(2)
333
+ par = this_ion.loc[:, "par"].pow(2)
334
+ per = this_ion.loc[:, "per"].pow(2)
335
+ chk = per.multiply(2).add(par).multiply(1.0 / 3.0)
336
+ pdt.assert_series_equal(scalar, chk, check_names=False)
337
+
338
+ else:
339
+ these_ions = ions_.loc[:, pd.IndexSlice[:, s]]
340
+ pdt.assert_frame_equal(these_ions, ot.thermal_speed(*s))
341
+ pdt.assert_frame_equal(these_ions, ot.w(*s))
342
+ pdt.assert_frame_equal(ot.thermal_speed(*s), ot.w(*s))
343
+
344
+ msg = "The result of a total species thermal speed is physically ambiguous"
345
+ with self.assertRaisesRegex(NotImplementedError, msg):
346
+ ot.thermal_speed("+".join(s))
347
+ with self.assertRaisesRegex(NotImplementedError, msg):
348
+ ot.w("+".join(s))
349
+ with self.assertRaises(ValueError):
350
+ ot.thermal_speed(",".join(s))
351
+
352
+ def test_pth(self):
353
+ print_inline_debug_info = True
354
+ # Test that Plasma returns each Ion plasma independently.
355
+ ot = self.object_testing
356
+
357
+ ions_ = {s: ot.ions[s].pth for s in self.stuple}
358
+ ions_ = pd.concat(ions_, axis=1, names=["S"], sort=True)
359
+ ions_ = ions_.reorder_levels(["C", "S"], axis=1).sort_index(axis=1)
360
+ # print("<Ions>", ions, sep="\n")
361
+
362
+ # Check that plasma returns each ion species independently.
363
+ for s in self.species_combinations:
364
+ # print(s)
365
+
366
+ tk_species = pd.IndexSlice[:, s[0] if len(s) == 1 else s]
367
+ this_ion = ions_.loc[:, tk_species]
368
+ if len(s) == 1:
369
+ this_ion = this_ion.xs(s[0], axis=1, level="S")
370
+
371
+ if print_inline_debug_info:
372
+ print(s)
373
+ print(len(s))
374
+ print("<Ion>", type(this_ion))
375
+ print(this_ion)
376
+ print("<Plasma>", type(ot.pth(*s)))
377
+ print(self.object_testing.pth(*s))
378
+
379
+ pdt.assert_frame_equal(this_ion, ot.pth(*s))
380
+
381
+ # print(this_ion)
382
+
383
+ if len(s) > 1:
384
+ this_ion = this_ion.T.groupby(level="C").sum().T
385
+ # if print_inline_debug_info:
386
+ # print("<Summed Ion>", this_ion,
387
+ # "<Plasma sum>", self.object_testing.pth("+".join(s)),
388
+ # sep="\n")
389
+ pdt.assert_frame_equal(this_ion, ot.pth("+".join(s)))
390
+
391
+ # print(this_ion)
392
+
393
+ def test_temperature(self):
394
+ # print_inline_debug_info = False
395
+ # Test that Plasma returns each Ion plasma independently.
396
+ ions_ = {s: self.object_testing.ions[s].temperature for s in self.stuple}
397
+ ions_ = pd.concat(ions_, axis=1, names=["S"], sort=True)
398
+ ions_ = ions_.reorder_levels(["C", "S"], axis=1).sort_index(axis=1)
399
+ # print("<Ions>", ions, sep="\n")
400
+
401
+ # Check that plasma returns each ion species independently.
402
+ for s in self.species_combinations:
403
+ tk_species = pd.IndexSlice[:, s[0] if len(s) == 1 else s]
404
+ this_ion = ions_.loc[:, tk_species]
405
+ if len(s) == 1:
406
+ this_ion = this_ion.xs(s[0], axis=1, level="S")
407
+
408
+ # if print_inline_debug_info:
409
+ # print(s)
410
+ # print("<Ion>", type(this_ion))
411
+ # print(this_ion)
412
+ # print("<Plasma>", type(self.object_testing.temperature(*s)))
413
+ # print(self.object_testing.temperature(*s))
414
+
415
+ pdt.assert_frame_equal(this_ion, self.object_testing.temperature(*s))
416
+
417
+ if len(s) > 1:
418
+ this_ion = this_ion.T.groupby(level="C").sum().T
419
+ # if print_inline_debug_info:
420
+ # print("<Summed Ion>", this_ion,
421
+ # "<Plasma sum>",
422
+ # self.object_testing.temperature("+".join(s)),
423
+ # sep="\n")
424
+ pdt.assert_frame_equal(
425
+ this_ion, self.object_testing.temperature("+".join(s))
426
+ )
427
+
428
+ def test_beta(self):
429
+ pth = {s: self.object_testing.ions[s].pth for s in self.stuple}
430
+ pth = pd.concat(pth, axis=1, names=["S"], sort=True)
431
+ pth = pth.reorder_levels(["C", "S"], axis=1).sort_index(axis=1)
432
+
433
+ bsq = self.data.loc[:, pd.IndexSlice["b", ["x", "y", "z"], ""]]
434
+ bsq = bsq.pow(2).sum(axis=1)
435
+
436
+ ions_ = pth.divide(bsq, axis=0)
437
+
438
+ coeff = 2.0 * constants.mu_0 * 1e-12 / (1e-9**2.0)
439
+ ions_ *= coeff
440
+
441
+ # Check that plasma returns each ion species independently.
442
+ for s in self.species_combinations:
443
+ tk_species = pd.IndexSlice[:, s[0] if len(s) == 1 else s]
444
+ this_ion = ions_.loc[:, tk_species]
445
+ if len(s) == 1:
446
+ this_ion = this_ion.xs(s[0], axis=1, level="S")
447
+
448
+ # print_inline_debug_info = False
449
+ # if print_inline_debug_info:
450
+ # print("",
451
+ # "<Test>",
452
+ # s,
453
+ # "<pth>",
454
+ # pth.loc[:, tk_species],
455
+ # "<bsq>", bsq,
456
+ # "<Ion>",
457
+ # type(this_ion),
458
+ # this_ion,
459
+ # "<Plasma>",
460
+ # type(self.object_testing.beta(*s)),
461
+ # self.object_testing.beta(*s),
462
+ # sep="\n")
463
+
464
+ pdt.assert_frame_equal(this_ion, self.object_testing.beta(*s))
465
+
466
+ if len(s) > 1:
467
+ this_ion = this_ion.T.groupby(level="C").sum().T
468
+ # if print_inline_debug_info:
469
+ # print("<Summed Ion>", this_ion,
470
+ # "<Plasma sum>",
471
+ # self.object_testing.beta("+".join(s)),
472
+ # sep="\n")
473
+ pdt.assert_frame_equal(this_ion, self.object_testing.beta("+".join(s)))
474
+
475
+ def test_anisotropy(self):
476
+ ot = self.object_testing
477
+
478
+ # Test individual components. Should return RT values.
479
+ for s in self.stuple:
480
+ w = self.data.w.xs(s, axis=1, level="S")
481
+ ani = (w.per / w.par).pow(2)
482
+ ani.name = s
483
+ # print("", ani, sep="\n")
484
+ right = ot.anisotropy(s)
485
+ pdt.assert_series_equal(ani, right)
486
+ # print("", "<ani>", ani, sep="\n")
487
+
488
+ # Test list of and sums of sums of species.
489
+ for s in self.species_combinations:
490
+ if len(s) == 1:
491
+ continue
492
+ # w = self.data.w.xs(s[0], axis=1, level="S")
493
+ # ani = (w.per / w.par).pow(2)
494
+ # ani.name = s
495
+ # right = self.object_testing.anisotropy(s)
496
+ # pdt.assert_series_equal(ani, right)
497
+ else:
498
+ # Test list of and sums of sums of species.
499
+ pth = {sprime: ot.ions.loc[sprime].pth for sprime in s}
500
+ pth = pd.concat(pth, axis=1, names=["S"], sort=True)
501
+ pth = pth.drop("scalar", axis=1, level="C", errors="ignore")
502
+
503
+ coeff = pd.Series({"par": -1, "per": 1})
504
+
505
+ # Calculate anisotropy of each individual species.
506
+ ani_s = pth.pow(coeff, axis=1, level="C")
507
+ # ani_s = ani_s.product(axis=1, level="S")
508
+ ani_s = ani_s.T.groupby("S").prod().T
509
+
510
+ # Calculate total anisotropy.
511
+ ani_sum = (
512
+ pth.T.groupby(level="C")
513
+ .sum()
514
+ .T.pow(coeff, axis=1, level="C")
515
+ .product(axis=1)
516
+ )
517
+ ani_sum.name = "+".join(sorted(s))
518
+
519
+ # print_inline_debug_info = False
520
+ # if print_inline_debug_info:
521
+ # print("<By species>",
522
+ # ani_s,
523
+ # "<Total ani>",
524
+ # ani_s,
525
+ # "<Plasma>",
526
+ # "<by species>",
527
+ # self.object_testing.anisotropy(*s),
528
+ # "<summed>",
529
+ # self.object_testing.anisotropy("+".join(s)),
530
+ # sep="\n")
531
+
532
+ pdt.assert_frame_equal(ani_s, ot.anisotropy(*s))
533
+ pdt.assert_series_equal(ani_sum, ot.anisotropy("+".join(s)))
534
+
535
+ def test_velocity(self):
536
+ ot = self.object_testing
537
+ for s in self.species_combinations:
538
+ if len(s) == 1:
539
+ # Test the species
540
+ self.assertEqual(ot.ions.loc[s[0]].velocity, ot.velocity(*s))
541
+ self.assertEqual(ot.ions.loc[s[0]].velocity, ot.v(*s))
542
+ self.assertEqual(ot.velocity(*s), ot.v(*s))
543
+
544
+ # Test `project_m2q`.
545
+ v = ot.ions.loc[s[0]].velocity.data
546
+ m2q = np.sqrt(self.mass_in_mp[s[0]] / self.charge_states[s[0]])
547
+ v = v.multiply(m2q)
548
+ pdt.assert_frame_equal(v, ot.v(*s, project_m2q=True).data)
549
+ pdt.assert_frame_equal(v, ot.velocity(*s, project_m2q=True).data)
550
+ self.assertEqual(vector.Vector(v), ot.v(*s, project_m2q=True))
551
+ self.assertEqual(vector.Vector(v), ot.velocity(*s, project_m2q=True))
552
+ self.assertEqual(
553
+ ot.v(*s, project_m2q=True), ot.velocity(*s, project_m2q=True)
554
+ )
555
+
556
+ else:
557
+ # Test species = (s0, s1, ..., sn)
558
+ ions_ = ot.ions.loc[list(s)].apply(lambda x: x.v)
559
+
560
+ pdt.assert_series_equal(ions_, ot.velocity(*s))
561
+ pdt.assert_series_equal(ions_, ot.v(*s))
562
+ pdt.assert_series_equal(ot.velocity(*s), ot.v(*s))
563
+
564
+ # comma-separated species list fails
565
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
566
+ ot.velocity(",".join(s))
567
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
568
+ ot.v(",".join(s))
569
+
570
+ with self.assertRaisesRegex(
571
+ NotImplementedError, "A multi-species velocity is not valid"
572
+ ):
573
+ ot.velocity(*s, project_m2q=True)
574
+ with self.assertRaisesRegex(
575
+ NotImplementedError, "A multi-species velocity is not valid"
576
+ ):
577
+ ot.v(*s, project_m2q=True)
578
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
579
+ ot.velocity(",".join(s), project_m2q=True)
580
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
581
+ ot.v(",".join(s), project_m2q=True)
582
+
583
+ # Test species = "s0+s1+...+sn"
584
+ rhos = ot.rho(*s)
585
+ ions_ = pd.concat(
586
+ ions_.apply(lambda x: x.cartesian).to_dict(),
587
+ axis=1,
588
+ names=["S"],
589
+ sort=True,
590
+ )
591
+ # print("", "<Ions>", ions, "<rhos>", rhos, sep="\n")
592
+ ions_ = ions_.multiply(rhos, axis=1, level="S")
593
+ ions_ = (
594
+ ions_.T.groupby(level="C").sum().T.divide(rhos.sum(axis=1), axis=0)
595
+ )
596
+ # print("<vcom>", ions, sep="\n")
597
+ ions_ = vector.Vector(ions_)
598
+
599
+ self.assertEqual(ions_, ot.v("+".join(s)))
600
+ self.assertEqual(ions_, ot.velocity("+".join(s)))
601
+
602
+ with self.assertRaisesRegex(
603
+ NotImplementedError, "A multi-species velocity is not valid"
604
+ ):
605
+ ot.velocity("+".join(s), project_m2q=True)
606
+ with self.assertRaisesRegex(
607
+ NotImplementedError, "A multi-species velocity is not valid"
608
+ ):
609
+ ot.v("+".join(s), project_m2q=True)
610
+
611
+ def test_dv(self):
612
+ # print_inline_debug_info = False
613
+
614
+ msg = "identically zero"
615
+ for s in self.stuple:
616
+ with self.assertRaisesRegex(NotImplementedError, msg):
617
+ self.object_testing.dv(s, s)
618
+ with self.assertRaisesRegex(NotImplementedError, msg):
619
+ s = "+".join(self.stuple)
620
+ self.object_testing.dv(s, s)
621
+
622
+ if len(self.stuple) == 1:
623
+ # A multi-species plasma is necessary to calculate a dv.
624
+ return None
625
+
626
+ combos2 = [x for x in self.species_combinations if len(x) == 2]
627
+ # print(*combos2)
628
+
629
+ ot = self.object_testing
630
+ for combo in combos2:
631
+ # Calculate individual species dv.
632
+ sb, sc = combo
633
+ v0 = self.data.v.xs(sb, axis=1, level="S")
634
+ v1 = self.data.v.xs(sc, axis=1, level="S")
635
+ dv = v0.subtract(v1, axis=1)
636
+ pdt.assert_frame_equal(dv, ot.dv(sb, sc).data)
637
+ self.assertEqual(vector.Vector(dv), ot.dv(sb, sc))
638
+
639
+ # Test single species \sqrt{m/q} projection.
640
+ v0 = v0.multiply(np.sqrt(self.mass_in_mp[sb] / self.charge_states[sb]))
641
+ v1 = v1.multiply(np.sqrt(self.mass_in_mp[sc] / self.charge_states[sc]))
642
+ dv_projected = v0.subtract(v1, axis=1)
643
+ pdt.assert_frame_equal(dv_projected, ot.dv(sb, sc, project_m2q=True).data)
644
+ self.assertEqual(
645
+ vector.Vector(dv_projected), ot.dv(sb, sc, project_m2q=True)
646
+ )
647
+
648
+ # Calculate dv for v_s - v_com.
649
+ ssum = "+".join(combo)
650
+ scomma = ",".join(combo)
651
+ for s in combo:
652
+ tk = pd.IndexSlice[["x", "y", "z"], list(combo)]
653
+ vs = self.data.v.loc[:, tk]
654
+ ns = self.data.n.loc[:, ""].loc[:, list(combo)]
655
+ m = self.mass_in_mp.loc[list(combo)]
656
+ rhos = ns.multiply(m, axis=1, level="S")
657
+ rho_total = rhos.sum(axis=1)
658
+ vcom = (
659
+ vs.multiply(rhos, axis=1, level="S")
660
+ .T.groupby(level="C")
661
+ .sum()
662
+ .T.divide(rho_total, axis=0)
663
+ )
664
+
665
+ v = self.data.v.xs(s, axis=1, level="S")
666
+ dv = v.subtract(vcom, axis=1, level="C")
667
+
668
+ # if print_inline_debug_info:
669
+ # print(
670
+ # "<Test>",
671
+ # "<species>: %s, %s" % (combo, ssum),
672
+ # "<vs>", type(vs), vs,
673
+ # "<ns>", type(ns), ns,
674
+ # "<rhos>", type(rhos), rhos,
675
+ # "<sum(rhos)>", type(rho_total), rho_total,
676
+ # "<vcom>", type(vcom), vcom,
677
+ # "",
678
+ # sep="\n")
679
+
680
+ pdt.assert_frame_equal(dv, ot.dv(s, ssum).data)
681
+ self.assertEqual(vector.Vector(dv), ot.dv(s, ssum))
682
+
683
+ # Verify that we can't pass a sum or comma species with `project_m2q`
684
+ with self.assertRaisesRegex(
685
+ NotImplementedError, "A multi-species velocity is not valid"
686
+ ):
687
+ ot.dv(s, ssum, project_m2q=True)
688
+ with self.assertRaisesRegex(
689
+ NotImplementedError, "A multi-species velocity is not valid"
690
+ ):
691
+ ot.dv(ssum, s, project_m2q=True)
692
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
693
+ ot.dv(s, scomma, project_m2q=True)
694
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
695
+ ot.dv(scomma, s, project_m2q=True)
696
+
697
+ # Verify a combination of comma and sum fail.
698
+ with self.assertRaises((NotImplementedError, ValueError)):
699
+ ot.dv(ssum, scomma)
700
+ with self.assertRaises((NotImplementedError, ValueError)):
701
+ ot.dv(scomma, ssum)
702
+
703
+ # Verify a combination of comma and sum fail with `project_m2q`.
704
+ with self.assertRaises((NotImplementedError, ValueError)):
705
+ ot.dv(ssum, scomma, project_m2q=True)
706
+ with self.assertRaises((NotImplementedError, ValueError)):
707
+ ot.dv(scomma, ssum, project_m2q=True)
708
+
709
+ if len(self.stuple) > 2:
710
+ # Calculate dv for v_si - v_com for each s in stuple.
711
+ ssum = "+".join(self.stuple)
712
+ scomma = ",".join(self.stuple)
713
+
714
+ tk = pd.IndexSlice[["x", "y", "z"], list(self.stuple)]
715
+ vs = self.data.v.loc[:, tk]
716
+ ns = self.data.n.loc[:, ""].loc[:, list(self.stuple)]
717
+ m = self.mass_in_mp.loc[list(self.stuple)]
718
+ rhos = ns.multiply(m, axis=1, level="S")
719
+ rho_total = rhos.sum(axis=1)
720
+ vcom = (
721
+ vs.multiply(rhos, axis=1, level="S")
722
+ .T.groupby(level="C")
723
+ .sum()
724
+ .T.divide(rho_total, axis=0)
725
+ )
726
+
727
+ # if print_inline_debug_info:
728
+ # print(
729
+ # "",
730
+ # "<Test>",
731
+ # "<ssum>: %s" % ssum,
732
+ # "<vs>", type(vs), vs,
733
+ # "<ns>", type(ns), ns,
734
+ # "<rhos>", type(rhos), rhos,
735
+ # "<sum(rhos)>", type(rho_total), rho_total,
736
+ # "<vcom>", type(vcom), vcom,
737
+ # "",
738
+ # sep="\n")
739
+
740
+ for s in self.stuple:
741
+ # Calculate dv for v_si - v_com for each s in stuple.
742
+ v = self.data.v.xs(s, axis=1, level="S")
743
+ dv = v.subtract(vcom, axis=1, level="C")
744
+
745
+ # if print_inline_debug_info:
746
+ # print(
747
+ # "",
748
+ # "<species>: %s" % s,
749
+ # "<v>", type(v), v,
750
+ # "<dv>", type(dv), dv,
751
+ # "",
752
+ # sep="\n")
753
+
754
+ pdt.assert_frame_equal(dv, ot.dv(s, ssum).data)
755
+ self.assertEqual(vector.Vector(dv), ot.dv(s, ssum))
756
+
757
+ # Test comma-separated species failes.
758
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
759
+ ot.dv(s, scomma)
760
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
761
+ ot.dv(scomma, s)
762
+ # Test comma-separated species failes with `project_m2q`.
763
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
764
+ ot.dv(s, scomma, project_m2q=True)
765
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
766
+ ot.dv(scomma, s, project_m2q=True)
767
+
768
+ # Verify center-of-mass species fail with `project_m2q`.
769
+ with self.assertRaisesRegex(
770
+ NotImplementedError, "A multi-species velocity is not valid"
771
+ ):
772
+ ot.dv(ssum, s, project_m2q=True)
773
+ with self.assertRaisesRegex(
774
+ NotImplementedError, "A multi-species velocity is not valid"
775
+ ):
776
+ ot.dv(s, ssum, project_m2q=True)
777
+
778
+ # Verify a combination of comma and sum fail.
779
+ with self.assertRaises((NotImplementedError, ValueError)):
780
+ ot.dv(ssum, scomma)
781
+ with self.assertRaises((NotImplementedError, ValueError)):
782
+ ot.dv(scomma, ssum)
783
+
784
+ # Verify a combination of comma and sum fail with `project_m2q`.
785
+ with self.assertRaises((NotImplementedError, ValueError)):
786
+ ot.dv(ssum, scomma, project_m2q=True)
787
+ with self.assertRaises((NotImplementedError, ValueError)):
788
+ ot.dv(scomma, ssum, project_m2q=True)
789
+
790
+ for combo in combos2:
791
+ # Calculate dv for v_{s0+s1} - v_com for each s in stuple.
792
+ tk = pd.IndexSlice[["x", "y", "z"], list(combo)]
793
+ v_s0s1 = self.data.v.loc[:, tk]
794
+ n_s0s1 = self.data.n.loc[:, ""].loc[:, list(combo)]
795
+ m_s0s1 = self.mass_in_mp.loc[list(combo)]
796
+
797
+ rho_s0s1 = n_s0s1.multiply(m_s0s1, axis=1, level="S")
798
+ rho_total_s0s1 = rho_s0s1.sum(axis=1)
799
+ rv_s0s1 = (
800
+ v_s0s1.multiply(rho_s0s1, axis=1, level="S")
801
+ .T.groupby(level="C")
802
+ .sum()
803
+ .T
804
+ )
805
+ vcom_s0s1 = rv_s0s1.divide(rho_total_s0s1, axis=0)
806
+
807
+ dv_s0s1 = vcom_s0s1.subtract(vcom, axis=1, level="C")
808
+
809
+ # if print_inline_debug_info:
810
+ # print(
811
+ # "",
812
+ # "<species s0s1>: %s, %s" % (combo, "+".join(combo)),
813
+ # "<v s0s1>", type(v_s0s1), v_s0s1,
814
+ # "<ns s0s1>", type(n_s0s1), n_s0s1,
815
+ # "<rho s0s1>", type(rho_s0s1), rho_s0s1,
816
+ # "<sum(rho_s0s1)>",
817
+ # type(rho_total_s0s1), rho_total_s0s1,
818
+ # "<rv_s0s1>", type(rv_s0s1), rv_s0s1,
819
+ # "<vcom_s0s1>", type(vcom_s0s1), vcom_s0s1,
820
+ # "",
821
+ # "<dv_s0s1_com>", type(dv_s0s1), dv_s0s1,
822
+ # sep="\n")
823
+
824
+ right = self.object_testing.dv("+".join(combo), ssum)
825
+ pdt.assert_frame_equal(dv_s0s1, right.data)
826
+ self.assertEqual(vector.Vector(dv_s0s1), right)
827
+
828
+ # Test comma-separated species failes
829
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
830
+ ot.dv(s, scomma)
831
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
832
+ ot.dv(scomma, s)
833
+ # Test comma-separated species failes with `project_m2q`.
834
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
835
+ ot.dv(s, scomma, project_m2q=True)
836
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
837
+ ot.dv(scomma, s, project_m2q=True)
838
+
839
+ # Verify center-of-mass species fail with `project_m2q`.
840
+ with self.assertRaises(NotImplementedError):
841
+ ot.dv(ssum, s, project_m2q=True)
842
+ with self.assertRaises(NotImplementedError):
843
+ ot.dv(s, ssum, project_m2q=True)
844
+
845
+ # Verify a combination of comma and sum fail.
846
+ with self.assertRaises((NotImplementedError, ValueError)):
847
+ ot.dv(ssum, scomma)
848
+ with self.assertRaises((NotImplementedError, ValueError)):
849
+ ot.dv(scomma, ssum)
850
+
851
+ # Verify a combination of comma and sum fail with `project_m2q`.
852
+ with self.assertRaises((NotImplementedError, ValueError)):
853
+ ot.dv(ssum, scomma, project_m2q=True)
854
+ with self.assertRaises((NotImplementedError, ValueError)):
855
+ ot.dv(scomma, ssum, project_m2q=True)
856
+
857
+ def test_ca(self):
858
+ tk = ["x", "y", "z"]
859
+ b = self.data.b.loc[:, tk].pow(2).sum(axis=1).pipe(np.sqrt) * 1e-9
860
+ n = self.data.n.loc[:, ""].loc[:, self.stuple] * 1e6
861
+ m = self.mass
862
+ rho = n * m
863
+
864
+ combos = [x for x in self.species_combinations if len(x) > 1]
865
+ total_masses = pd.DataFrame(
866
+ {"+".join(s): rho.loc[:, s].sum(axis=1) for s in combos}
867
+ )
868
+ rho = pd.concat([rho, total_masses], axis=1, sort=True)
869
+
870
+ ions_ = (constants.mu_0 * rho).pow(-0.5).multiply(b, axis=0) / 1e3
871
+ ions_.columns.names = ["S"]
872
+ # print_inline_debug_info = False
873
+ # if print_inline_debug_info:
874
+ # print("",
875
+ # "<Test>",
876
+ # "<species>", self.stuple,
877
+ # "<b>", type(b), b,
878
+ # "<n>", type(n), n,
879
+ # "<m>", type(m), m,
880
+ # "<rho>", type(rho), rho,
881
+ # "<Ions>", type(ions), ions,
882
+ # "<Species combinations>",
883
+ # [x for x in self.species_combinations],
884
+ # sep="\n")
885
+
886
+ for s in self.species_combinations:
887
+ if len(s) == 1:
888
+ pdt.assert_series_equal(
889
+ ions_.xs(*s, axis=1), self.object_testing.ca(*s)
890
+ )
891
+ else:
892
+ # Check individual species.
893
+ pdt.assert_frame_equal(ions_.loc[:, s], self.object_testing.ca(*s))
894
+ # Check total plasma.
895
+ pdt.assert_series_equal(
896
+ ions_.loc[:, "+".join(s)], self.object_testing.ca("+".join(s))
897
+ )
898
+
899
+ # @pytest.mark.skip(reason="Not implemented")
900
+ def test_afsq(self):
901
+ # print_inline_debug_info = True
902
+
903
+ slist = list(self.stuple)
904
+ tk = pd.IndexSlice[["par", "per"], slist]
905
+
906
+ w = (
907
+ self.data.w.loc[:, tk].drop("scalar", axis=1, level="C", errors="ignore")
908
+ * 1e3
909
+ )
910
+ n = self.data.n.loc[:, ""].loc[:, slist] * 1e6
911
+ m = self.mass.loc[slist]
912
+ rho = n.multiply(m, axis=1, level="S")
913
+ pth = 0.5 * w.pow(2.0).multiply(rho, axis=1, level="S")
914
+
915
+ tk = pd.IndexSlice[["x", "y", "z"], ""]
916
+ bsq = (self.data.b.loc[:, tk] * 1e-9).pow(2.0).sum(axis=1)
917
+
918
+ # NOTE: Factor of 2 to get proper betas would go here.
919
+ beta = pth.divide(bsq, axis=0) * constants.mu_0 # * 2.0
920
+ dbeta = beta.per - beta.par
921
+ ions_ = dbeta + 1.0
922
+
923
+ # if print_inline_debug_info:
924
+ # print("",
925
+ # "<Test>",
926
+ # "<species>: {}".format(list(self.species_combinations)),
927
+ # "<tk>: {}".format(tk),
928
+ # "<w>", type(w), w,
929
+ # "<n>", type(n), n,
930
+ # "<m>", type(m), m,
931
+ # "<rho>", type(rho), rho,
932
+ # "<pth>", type(pth), pth,
933
+ # "<bsq>", type(bsq), bsq,
934
+ # "<''beta''>", type(beta), beta,
935
+ # "<''dbeta''>", type(dbeta), dbeta,
936
+ # "<Ions>", type(ions), ions,
937
+ # "",
938
+ # sep="\n")
939
+
940
+ msg = (
941
+ "Youngest beams analysis shows that dynamic pressure is "
942
+ "probably not useful."
943
+ )
944
+ # for s in self.stuple:
945
+ # pdt.assert_series_equal(ions.loc[:, s],
946
+ # self.object_testing.afsq(s, pdynamic=False))
947
+ # with self.assertRaisesRegex(NotImplementedError, msg):
948
+ # self.object_testing.afsq(s, pdynamic=True)
949
+
950
+ for combo in self.species_combinations:
951
+ with self.assertRaisesRegex(NotImplementedError, msg):
952
+ self.object_testing.afsq(*combo, pdynamic=True)
953
+ if len(combo) == 1:
954
+ # print("<Series>")
955
+ pdt.assert_series_equal(
956
+ ions_.loc[:, combo[0]],
957
+ self.object_testing.afsq(*combo, pdynamic=False),
958
+ )
959
+ else:
960
+ # if print_inline_debug_info:
961
+ # print("<Frame>", ions.loc[:, combo], "", sep="\n")
962
+ pdt.assert_frame_equal(
963
+ ions_.loc[:, combo], self.object_testing.afsq(*combo)
964
+ )
965
+
966
+ # So that we don't overcount the $1 +$ in AFSQ, we
967
+ # do the following before taking the sum.
968
+ left = 1 + (ions_.loc[:, combo] - 1).sum(axis=1)
969
+ left.name = "+".join(combo)
970
+ pdt.assert_series_equal(left, self.object_testing.afsq("+".join(combo)))
971
+
972
+ def test_caani(self):
973
+ # print_inline_debug_info = False
974
+
975
+ combos = [x for x in self.species_combinations]
976
+ masses = self.mass
977
+ n = self.data.n.xs("", axis=1, level="C") * 1e6
978
+ rhos = {
979
+ "+".join(x): n.loc[:, list(x)].multiply(masses.loc[list(x)]).sum(axis=1)
980
+ for x in combos
981
+ }
982
+ rhos = pd.concat(rhos, axis=1, names=["S"], sort=True)
983
+
984
+ tk = pd.IndexSlice[["x", "y", "z"], ""]
985
+ b = (self.data.b.loc[:, tk]).pow(2).sum(axis=1).pipe(np.sqrt) * 1e-9
986
+
987
+ ca = (rhos * constants.mu_0).pow(-0.5).multiply(b, axis=0) / 1e3
988
+
989
+ slist = list(self.stuple)
990
+ tk = pd.IndexSlice[["par", "per"], slist]
991
+ w = (
992
+ self.data.w.loc[:, tk].drop("scalar", axis=1, level="C", errors="ignore")
993
+ * 1e3
994
+ )
995
+ n = self.data.n.loc[:, ""].loc[:, slist] * 1e6
996
+ m = self.mass.loc[slist]
997
+ rho = n.multiply(m, axis=1, level="S")
998
+ pth = 0.5 * w.pow(2.0).multiply(rho, axis=1, level="S")
999
+ dp = pth.per - pth.par
1000
+
1001
+ beta_ish = dp.multiply(constants.mu_0 * b.pow(-2.0), axis=0)
1002
+
1003
+ # if print_inline_debug_info:
1004
+ # print("",
1005
+ # "<Test>",
1006
+ # "<combos>: {}".format(combos),
1007
+ # "<masses>", type(masses), masses,
1008
+ # "<n>", type(n), n,
1009
+ # "<rho>", type(rhos), rhos,
1010
+ # "<b>", type(b), b,
1011
+ # "<ca>", type(ca), ca,
1012
+ # "<p>", type(pth), pth,
1013
+ # "<dp>", type(dp), dp,
1014
+ # "<beta*>", type(beta_ish), beta_ish,
1015
+ # sep="\n")
1016
+
1017
+ regex_msg = (
1018
+ "Youngest beams analysis shows that "
1019
+ "dynamic pressure is probably not useful."
1020
+ )
1021
+ for combo in combos:
1022
+ this_ca = ca.loc[:, "+".join(combo)]
1023
+ afsq = 1.0 + beta_ish.loc[:, list(combo)].sum(axis=1)
1024
+ this_caani = this_ca.multiply(afsq.pow(0.5), axis=0)
1025
+ this_caani.name = "+".join(combo)
1026
+
1027
+ # if print_inline_debug_info:
1028
+ # print(
1029
+ # "<species>: {}".format(combo),
1030
+ # "<afsq>", type(afsq), afsq,
1031
+ # "<caani>", type(this_caani), this_caani,
1032
+ # sep="\n")
1033
+
1034
+ pdt.assert_series_equal(this_caani, self.object_testing.caani(*combo))
1035
+ pdt.assert_series_equal(
1036
+ this_caani, self.object_testing.caani("+".join(combo))
1037
+ )
1038
+ pdt.assert_series_equal(
1039
+ self.object_testing.caani(*combo),
1040
+ self.object_testing.caani("+".join(combo)),
1041
+ )
1042
+ with self.assertRaisesRegex(NotImplementedError, regex_msg):
1043
+ self.object_testing.caani(*combo, pdynamic=True)
1044
+ with self.assertRaisesRegex(NotImplementedError, regex_msg):
1045
+ self.object_testing.caani("+".join(combo), pdynamic=True)
1046
+
1047
+ def test_lnlambda(self):
1048
+ # print_inline_debug_info = True
1049
+
1050
+ ot = self.object_testing
1051
+ regex_msg = (
1052
+ "`lnlambda` can only calculate with individual s0 " "and s1 species."
1053
+ )
1054
+ invalid = "Invalid species"
1055
+
1056
+ kb_J = constants.physical_constants["Boltzmann constant"]
1057
+ kb_eV = constants.physical_constants["Boltzmann constant in eV/K"]
1058
+
1059
+ amu = self.m_amu
1060
+ charge_states = self.charge_states
1061
+ n_all = self.data.n.xs("", axis=1, level="C").loc[:, self.stuple] * 1e6
1062
+ m = self.mass
1063
+ # rho = n.multiply(m, axis=1, level="S")
1064
+ w = self.data.w.scalar.loc[:, self.stuple] * 1e3
1065
+
1066
+ Tkelvin = 0.5 * w.pow(2.0).multiply(m, axis=1, level="S") / kb_J[0]
1067
+ TeV = Tkelvin * kb_eV[0]
1068
+
1069
+ if len(self.stuple) == 1:
1070
+ s = self.stuple[0]
1071
+ z = charge_states.loc[s]
1072
+ n = n_all.loc[:, s]
1073
+ T = TeV.loc[:, s]
1074
+ lnlambda = 29.9 - np.log(
1075
+ np.sqrt(2) * z**3 * n.pipe(np.sqrt) * T.pow(-3.0 / 2.0)
1076
+ )
1077
+ lnlambda.name = f"{s},{s}"
1078
+
1079
+ pdt.assert_series_equal(lnlambda, ot.lnlambda(s, s))
1080
+ pdt.assert_series_equal(ot.lnlambda(s, s), ot.lnlambda(s, s))
1081
+
1082
+ s0s1 = f"{s}+{s}"
1083
+ with self.assertRaisesRegex(ValueError, regex_msg):
1084
+ ot.lnlambda(s, s0s1)
1085
+ with self.assertRaisesRegex(ValueError, regex_msg):
1086
+ ot.lnlambda(s0s1, s)
1087
+ with self.assertRaisesRegex(ValueError, regex_msg):
1088
+ ot.lnlambda(s0s1, s0s1)
1089
+
1090
+ combo = [s, s]
1091
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1092
+ ot.lnlambda("+".join(combo), list(combo))
1093
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1094
+ ot.lnlambda(list(combo), "+".join(combo))
1095
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1096
+ ot.lnlambda(",".join(combo), list(combo))
1097
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1098
+ ot.lnlambda(list(combo), ",".join(combo))
1099
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1100
+ ot.lnlambda(list(combo), list(combo))
1101
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1102
+ ot.lnlambda(list(combo), s)
1103
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1104
+ ot.lnlambda(list(combo), combo[1])
1105
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1106
+ ot.lnlambda(s, list(combo))
1107
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1108
+ ot.lnlambda(combo[1], list(combo))
1109
+
1110
+ else:
1111
+ nZsqOTeV = n_all.multiply(charge_states.pow(2), axis=1, level="S").divide(
1112
+ TeV, axis=1, level="S"
1113
+ )
1114
+
1115
+ # if print_inline_debug_info:
1116
+ # print("",
1117
+ # "<Test>",
1118
+ # "<species>: {}".format(self.stuple),
1119
+ # "<amu>", type(amu), amu,
1120
+ # "<charge_states>", type(charge_states), charge_states,
1121
+ # "<n>", type(n), n,
1122
+ # "<mass>", type(m), m,
1123
+ # # "<rho>", type(rho), rho,
1124
+ # "<w>", type(w), w,
1125
+ # "<kb_J>", kb_J,
1126
+ # "<kb_eV>", kb_eV,
1127
+ # "<T [K]>", type(Tkelvin), Tkelvin,
1128
+ # "<T [eV]>", type(TeV), TeV,
1129
+ # "<n Z^2 / T [eV]>", type(nZsqOTeV), nZsqOTeV,
1130
+ # "",
1131
+ # sep="\n")
1132
+
1133
+ combos2 = [x for x in self.species_combinations if len(x) == 2]
1134
+ for combo in combos2:
1135
+ si, sj = combo
1136
+ ai = amu.loc[si]
1137
+ aj = amu.loc[sj]
1138
+ zi = charge_states.loc[si]
1139
+ zj = charge_states.loc[sj]
1140
+ ti = TeV.loc[:, si]
1141
+ tj = TeV.loc[:, sj]
1142
+
1143
+ left = (zi * zj * (ai + aj)) / ((ai * tj) + (aj * ti))
1144
+ right = nZsqOTeV.loc[:, list(combo)].sum(axis=1).pipe(np.sqrt)
1145
+ ln = np.log(left * right)
1146
+ lnlambda = 29.9 - ln
1147
+ lnlambda.name = ",".join(sorted(combo))
1148
+ # ln = None
1149
+ # lnlambda = None
1150
+
1151
+ # if print_inline_debug_info:
1152
+ # print(
1153
+ # "<combo>: {}".format(combo),
1154
+ # "<sqrt( sum(n_i Z_i^s / T_i [eV]) )>",
1155
+ # type(right), right,
1156
+ # "<left>", type(left), left,
1157
+ # "<ln>", type(ln), ln,
1158
+ # "<lnlambda>", type(lnlambda), lnlambda,
1159
+ # "",
1160
+ # sep="\n")
1161
+
1162
+ pdt.assert_series_equal(lnlambda, ot.lnlambda(combo[0], combo[1]))
1163
+ pdt.assert_series_equal(
1164
+ lnlambda, ot.lnlambda(combo[1], combo[0]), check_names=False
1165
+ )
1166
+
1167
+ # NOTE: The following various Invalid Species tests are excessive
1168
+ # and should be reduced.
1169
+ s0s1 = "+".join(combo) # ("+".join(combo), combo):
1170
+ with self.assertRaisesRegex(ValueError, regex_msg):
1171
+ ot.lnlambda(combo[0], s0s1)
1172
+ with self.assertRaisesRegex(ValueError, regex_msg):
1173
+ ot.lnlambda(s0s1, combo[0])
1174
+ with self.assertRaisesRegex(ValueError, regex_msg):
1175
+ ot.lnlambda(combo[1], s0s1)
1176
+ with self.assertRaisesRegex(ValueError, regex_msg):
1177
+ ot.lnlambda(s0s1, combo[1])
1178
+ with self.assertRaisesRegex(ValueError, regex_msg):
1179
+ ot.lnlambda(s0s1, s0s1)
1180
+
1181
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1182
+ ot.lnlambda("+".join(combo), list(combo))
1183
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1184
+ ot.lnlambda(list(combo), "+".join(combo))
1185
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1186
+ ot.lnlambda(",".join(combo), list(combo))
1187
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1188
+ ot.lnlambda(list(combo), ",".join(combo))
1189
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1190
+ ot.lnlambda(list(combo), list(combo))
1191
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1192
+ ot.lnlambda(list(combo), combo[0])
1193
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1194
+ ot.lnlambda(list(combo), combo[1])
1195
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1196
+ ot.lnlambda(combo[0], list(combo))
1197
+ with self.assertRaisesRegex((TypeError, ValueError), invalid):
1198
+ ot.lnlambda(combo[1], list(combo))
1199
+
1200
+ def test_nuc(self):
1201
+ r"""We calculate the collision frequency for differential flow following
1202
+ Hernandez & Marsch (JGR 1985, doi:10.1029/JA090iA11p11062) Eq.
1203
+
1204
+ (18).
1205
+ """
1206
+ from scipy.special import erf
1207
+ from scipy import constants
1208
+
1209
+ # print_inline_debug_info = False
1210
+
1211
+ if len(self.stuple) == 1:
1212
+ # We only test plasmas w/ > 1 species.
1213
+ return None
1214
+
1215
+ slist = list(self.stuple)
1216
+ coeff = 4.0 * np.pi * constants.epsilon_0**2.0
1217
+ qsq = self.charges**2.0
1218
+ m = self.mass
1219
+ w = self.data.w.par.loc[:, slist] * 1e3
1220
+ wsq = w.pow(2.0)
1221
+ n = self.data.n.xs("", axis=1, level="C").loc[:, slist] * 1e6
1222
+ rho = n.multiply(m, axis=1)
1223
+ tk = pd.IndexSlice[["x", "y", "z"], slist]
1224
+ v = self.data.v.loc[:, tk] * 1e3
1225
+
1226
+ combos2 = [x for x in self.species_combinations if len(x) == 2]
1227
+
1228
+ # if print_inline_debug_info:
1229
+ # print("",
1230
+ # "<Test>",
1231
+ # "<species>: {}".format(self.stuple),
1232
+ # "<coeff>: %s" % coeff,
1233
+ # "<qsq>", type(qsq), qsq,
1234
+ # "<m>", type(m), m,
1235
+ # "<w>", type(w), w,
1236
+ # "<wsq>", type(wsq), wsq,
1237
+ # "<n>", type(n), n,
1238
+ # "<rho>", type(rho), rho,
1239
+ # "<v>", type(v), v,
1240
+ # "<combos len 2>", combos2,
1241
+ # "",
1242
+ # sep="\n")
1243
+
1244
+ for combo in combos2:
1245
+ sa, sb = combo
1246
+
1247
+ ma = m.loc[sa]
1248
+ mb = m.loc[sb]
1249
+ mu = (ma * mb) / (ma + mb)
1250
+ qabsq = qsq.loc[[sa, sb]].product()
1251
+ all_coeff = qabsq / (coeff * mu * ma)
1252
+
1253
+ nb = n.loc[:, sb]
1254
+ wab = wsq.loc[:, [sa, sb]].sum(axis=1).pipe(np.sqrt)
1255
+
1256
+ lnlambda = self.object_testing.lnlambda(sa, sb)
1257
+
1258
+ va = v.xs(sa, axis=1, level="S")
1259
+ vb = v.xs(sb, axis=1, level="S")
1260
+ dvvec = va - vb
1261
+ dv = dvvec.pow(2).sum(axis=1).pipe(np.sqrt)
1262
+ dvw = dv.divide(wab, axis=0)
1263
+
1264
+ gauss_coeff = dvw.multiply(2.0 / np.sqrt(np.pi))
1265
+ # ldr = longitudinal diffusion rate $\hat{\nu}_L$.
1266
+ erf_dvw = erf(dvw)
1267
+ gaussian_term = gauss_coeff * np.exp(-(dvw**2.0))
1268
+ ldr = dvw.pow(-3.0) * (erf_dvw - gaussian_term)
1269
+
1270
+ nuab = all_coeff * (nb * lnlambda / wab.pow(3.0)) * ldr / 1e-7
1271
+
1272
+ exp = pd.Series({sa: 1.0, sb: -1.0})
1273
+ rho_ratio = rho.loc[:, [sa, sb]].pow(exp, axis=1, level="S").product(axis=1)
1274
+ nuba = nuab.multiply(rho_ratio, axis=0)
1275
+ nuc = nuab.add(nuba, axis=0)
1276
+
1277
+ nuab.name = "%s-%s" % (sa, sb)
1278
+ nuba.name = "%s-%s" % (sb, sa)
1279
+ nuc.name = "%s+%s" % (sa, sb)
1280
+
1281
+ # if print_inline_debug_info:
1282
+ # print("",
1283
+ # "<combo>: {}".format(combo),
1284
+ # "<ma>", type(ma), ma,
1285
+ # "<mu>", type(mu), mu,
1286
+ # "<qab^2>", type(qabsq), qabsq,
1287
+ # "<qa^2 qb^2 / 4 pi e0^2 ma mu>", type(all_coeff), all_coeff,
1288
+ # "<nb>", type(nb), nb,
1289
+ # "<wab>", type(wab), wab,
1290
+ # "<lnlambda>", type(lnlambda), lnlambda,
1291
+ # "<dv vec>", type(dvvec), dvvec,
1292
+ # "<dv>", type(dv), dv,
1293
+ # "<dv/wab>", type(dvw), dvw,
1294
+ # "<erf(dv/wab)>", type(erf_dvw), erf_dvw,
1295
+ # "<(dv/wab) * 2/sqrt(pi) * exp(-(dv/wab)^2)>", type(gaussian_term), gaussian_term,
1296
+ # "<transverse diffusion rate>", type(ldr), ldr,
1297
+ # "<nuab>", type(nuab), nuab,
1298
+ # "<rho_a/rho_b>", type(rho_ratio), rho_ratio,
1299
+ # "<nuba>", type(nuba), nuba,
1300
+ # "<nuc>", type(nuc), nuc,
1301
+ # "",
1302
+ # sep="\n")
1303
+
1304
+ pdt.assert_series_equal(
1305
+ nuab, self.object_testing.nuc(sa, sb, both_species=False)
1306
+ )
1307
+ pdt.assert_series_equal(
1308
+ nuba, self.object_testing.nuc(sb, sa, both_species=False)
1309
+ )
1310
+ pdt.assert_series_equal(nuc, self.object_testing.nuc(sa, sb))
1311
+
1312
+ nuc.name = "%s+%s" % (sb, sa)
1313
+ pdt.assert_series_equal(nuc, self.object_testing.nuc(sb, sa))
1314
+
1315
+ pdt.assert_series_equal(
1316
+ self.object_testing.nuc(sa, sb),
1317
+ self.object_testing.nuc(sb, sa),
1318
+ check_names=False,
1319
+ )
1320
+
1321
+ def test_spacecraft_in_plasma(self):
1322
+ sc_data = base.TestData().spacecraft_data
1323
+
1324
+ Wind = pd.concat(
1325
+ {"pos": sc_data.xs("gse", axis=1, level="M")},
1326
+ axis=1,
1327
+ names=["M"],
1328
+ sort=True,
1329
+ )
1330
+ Wind = spacecraft.Spacecraft(Wind, "Wind", "GSE")
1331
+
1332
+ PSP = pd.concat(
1333
+ {
1334
+ "pos": sc_data.xs("pos_HCI", axis=1, level="M"),
1335
+ "v": sc_data.xs("v_HCI", axis=1, level="M"),
1336
+ "carr": sc_data.xs("Carr", axis=1, level="M"),
1337
+ },
1338
+ axis=1,
1339
+ names=["M"],
1340
+ sort=True,
1341
+ )
1342
+ PSP = spacecraft.Spacecraft(PSP, "PSP", "HCI")
1343
+
1344
+ ot = self.object_testing
1345
+ ot.set_spacecraft(None)
1346
+ self.assertIsNone(ot.spacecraft)
1347
+
1348
+ ot.set_spacecraft(Wind)
1349
+ self.assertEqual(ot.spacecraft, Wind)
1350
+ self.assertEqual(ot.spacecraft, ot.sc)
1351
+
1352
+ ot.set_spacecraft(PSP)
1353
+ self.assertEqual(ot.spacecraft, PSP)
1354
+ self.assertEqual(ot.spacecraft, ot.sc)
1355
+
1356
+ self.assertNotEqual(ot.spacecraft, Wind)
1357
+ self.assertNotEqual(ot.sc, Wind)
1358
+
1359
+ ot.set_spacecraft(None)
1360
+
1361
+ def test_nc_without_spacecraft(self):
1362
+ # if len(self.stuple) == 1:
1363
+ # # We only test Nc for plasmas with more than 1 species.
1364
+ # return None
1365
+
1366
+ ot = self.object_testing
1367
+ ot.set_spacecraft(None)
1368
+ combos2 = [x for x in self.species_combinations if len(x) == 2]
1369
+ for combo in combos2:
1370
+ sa, sb = combo
1371
+ # Assert failure to calculate Nc when no spacecraft set.
1372
+ with self.assertRaises(ValueError):
1373
+ ot.nc(sa, sb)
1374
+
1375
+ def test_nc_with_spacecraft(self):
1376
+
1377
+ if len(self.stuple) == 1:
1378
+ # We only test plasmas w/ > 1 species.
1379
+ return None
1380
+
1381
+ slist = list(self.stuple)
1382
+
1383
+ v = self.data.v
1384
+ v = pd.concat(
1385
+ {s: v.xs(s, axis=1, level="S") for s in slist},
1386
+ axis=1,
1387
+ names=["S"],
1388
+ sort=True,
1389
+ )
1390
+
1391
+ # Neither `n` nor `rho` units b/c Vcom divides out
1392
+ # the [rho].
1393
+ m = self.mass
1394
+ n = self.data.n.xs("", axis=1, level="C")
1395
+ n = pd.concat(
1396
+ {s: n.xs(s, axis=1) for s in slist}, axis=1, names=["S"], sort=True
1397
+ )
1398
+ rho = n.multiply(m, axis=1, level="S")
1399
+
1400
+ vcom = (
1401
+ v.multiply(rho, axis=1, level="S")
1402
+ .T.groupby(level="C")
1403
+ .sum()
1404
+ .T.divide(rho.sum(axis=1), axis=0)
1405
+ )
1406
+ vsw = vcom.pow(2.0).sum(axis=1).pipe(np.sqrt) * 1e3
1407
+
1408
+ sc_data = base.TestData().spacecraft_data
1409
+
1410
+ Wind = pd.concat(
1411
+ {"pos": sc_data.xs("gse", axis=1, level="M")},
1412
+ axis=1,
1413
+ names=["M"],
1414
+ sort=True,
1415
+ )
1416
+ Wind = spacecraft.Spacecraft(Wind, "Wind", "GSE")
1417
+ tau_exp_Wind = Wind.distance2sun.multiply(vsw.pow(-1.0), axis=0)
1418
+
1419
+ PSP = pd.concat(
1420
+ {
1421
+ "pos": sc_data.xs("pos_HCI", axis=1, level="M"),
1422
+ "v": sc_data.xs("v_HCI", axis=1, level="M"),
1423
+ "carr": sc_data.xs("Carr", axis=1, level="M"),
1424
+ },
1425
+ axis=1,
1426
+ names=["M"],
1427
+ sort=True,
1428
+ )
1429
+ PSP = spacecraft.Spacecraft(PSP, "PSP", "HCI")
1430
+ # Rs = 695.508e6 # [m]
1431
+ # r_Re = constants.au - (self.data.gse.x * Rs)
1432
+ tau_exp_PSP = PSP.distance2sun.multiply(vsw.pow(-1.0), axis=0)
1433
+
1434
+ # print("",
1435
+ # "<Test>",
1436
+ # "<species>: {}".format(self.stuple),
1437
+ # "<v>", type(v), v,
1438
+ # "<m>", type(m), m,
1439
+ # "<n>", type(n), n,
1440
+ # "<rho>", type(rho), rho,
1441
+ # "<vcom>", type(vcom), vcom,
1442
+ # "<vsw>", type(vsw), vsw,
1443
+ # "<r>", type(r), r,
1444
+ # "<tau_exp>", type(tau_exp), tau_exp,
1445
+ # sep="\n")
1446
+
1447
+ individual_msg = (
1448
+ "`nc` can only calculate with individual" " `sa` and `sb` species."
1449
+ )
1450
+ invalid_msg = "Invalid species"
1451
+
1452
+ combos2 = [x for x in self.species_combinations if len(x) == 2]
1453
+ ot = self.object_testing
1454
+ for combo in combos2:
1455
+ sa, sb = combo
1456
+
1457
+ for sc, tau_exp in zip((Wind, PSP), (tau_exp_Wind, tau_exp_PSP)):
1458
+
1459
+ ot.set_spacecraft(sc)
1460
+
1461
+ nuab = ot.nuc(sa, sb, both_species=False)
1462
+ nuba = ot.nuc(sb, sa, both_species=False)
1463
+ nuc = ot.nuc(sa, sb, both_species=True)
1464
+
1465
+ ncab = nuab.multiply(tau_exp, axis=0) * 1e-7
1466
+ ncab.name = "%s-%s" % (sa, sb)
1467
+
1468
+ ncba = nuba.multiply(tau_exp, axis=0) * 1e-7
1469
+ ncba.name = "%s-%s" % (sb, sa)
1470
+
1471
+ nc = nuc.multiply(tau_exp, axis=0) * 1e-7
1472
+ nc.name = "%s+%s" % combo
1473
+
1474
+ # print("",
1475
+ # "<nuab>", type(nuab), nuab,
1476
+ # "<ncab>", type(ncab), ncab,
1477
+ # "<nuba>", type(nuba), nuba,
1478
+ # "<ncba>", type(ncba), ncba,
1479
+ # "<nuc>", type(nuc), nuc,
1480
+ # "<nc>", type(nc), nc,
1481
+ # "",
1482
+ # sep="\n")
1483
+
1484
+ pdt.assert_series_equal(ncab, ot.nc(sa, sb, both_species=False))
1485
+ pdt.assert_series_equal(ncba, ot.nc(sb, sa, both_species=False))
1486
+ pdt.assert_series_equal(nc, ot.nc(sa, sb, both_species=True))
1487
+ pdt.assert_series_equal(
1488
+ nc, ot.nc(sb, sa, both_species=True), check_names=False
1489
+ )
1490
+ pdt.assert_series_equal(
1491
+ ot.nc(sa, sb, both_species=True),
1492
+ ot.nc(sb, sa, both_species=True),
1493
+ check_names=False,
1494
+ )
1495
+
1496
+ # Ensure spacecraft is None
1497
+ ot.set_spacecraft(None)
1498
+
1499
+ with self.assertRaisesRegex(ValueError, individual_msg):
1500
+ ot.nc("+".join(combo), sa)
1501
+ with self.assertRaisesRegex(ValueError, individual_msg):
1502
+ ot.nc(sa, "+".join(combo))
1503
+
1504
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1505
+ ot.nc(",".join(combo), sa)
1506
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1507
+ ot.nc(sa, ",".join(combo))
1508
+
1509
+ with self.assertRaisesRegex(TypeError, invalid_msg):
1510
+ ot.nc(combo, sa)
1511
+ with self.assertRaisesRegex(TypeError, invalid_msg):
1512
+ ot.nc(sa, combo)
1513
+
1514
+ def test_estimate_electrons(self):
1515
+ # print_inline_debug_info = True
1516
+
1517
+ stuple = self.stuple
1518
+
1519
+ if "p" not in self.stuple and "p1" not in self.stuple:
1520
+ with self.assertRaisesRegex(
1521
+ ValueError,
1522
+ # Match this sentence at start of string.
1523
+ r"^Plasma must contain \(core\) protons to estimate electrons.",
1524
+ ):
1525
+ self.object_testing.estimate_electrons()
1526
+
1527
+ else:
1528
+
1529
+ qi = self.charge_states
1530
+ ni = self.data.n.xs("", axis=1, level="C").loc[:, list(stuple)]
1531
+ niqi = ni.multiply(qi, axis=1, level="S")
1532
+ vi = self.data.v.loc[:, pd.IndexSlice[:, list(stuple)]]
1533
+ niqivi = vi.multiply(niqi, axis=1, level="S")
1534
+ # nqv = niqivi.sum(axis=1, level="C")
1535
+ nqv = niqivi.T.groupby(level="C").sum().T
1536
+ # Signs in -1 * niqi / qe cancel to give positive definite ne.
1537
+ ne = niqi.sum(axis=1)
1538
+ # Signs in -1 * niqivi / neqe cancel. The charge state of e- is -1.
1539
+ ve = nqv.divide(ne, axis=0)
1540
+
1541
+ if "p" in self.stuple:
1542
+ tkw = pd.IndexSlice["scalar", "p"]
1543
+ tkn = "p"
1544
+ exp = pd.Series({"p": 1.0, "e": -1.0})
1545
+ else:
1546
+ tkw = pd.IndexSlice["scalar", "p1"]
1547
+ tkn = "p1"
1548
+ exp = pd.Series({"p1": 1.0, "e": -1.0})
1549
+
1550
+ wp = self.data.w.loc[:, tkw]
1551
+ npne = self.data.n.xs("", axis=1, level="C").loc[:, tkn]
1552
+ npne = pd.concat([npne, ne], axis=1, keys=[tkn, "e"], sort=True)
1553
+ nrat = npne.pow(exp, axis=1, level="S").product(axis=1)
1554
+ mpme = physical_constants["electron-proton mass ratio"][0] ** -1.0
1555
+ we = (nrat * mpme).multiply(wp.pow(2), axis=0).pipe(np.sqrt)
1556
+
1557
+ tmp = pd.concat([we, we], axis=1, keys=["par", "per"], sort=True)
1558
+ ne.name = ""
1559
+ electrons = pd.concat(
1560
+ [ne, ve, tmp], axis=1, keys=["n", "v", "w"], names=["M", "C"], sort=True
1561
+ )
1562
+
1563
+ electrons = ions.Ion(electrons, "e")
1564
+
1565
+ # if print_inline_debug_info:
1566
+ # print("<Test>",
1567
+ # "<species>: {}".format(stuple),
1568
+ # "<charges>", type(qi), qi,
1569
+ # "<n>", type(ni), ni,
1570
+ # "<niqi>", type(niqi), niqi,
1571
+ # "<ne>", type(ne), ne,
1572
+ # "<vi>", type(vi), vi,
1573
+ # "<niqivi>", type(niqivi), niqivi,
1574
+ # "<nqv>", type(nqv), nqv,
1575
+ # "<ve>", type(ve), ve,
1576
+ # "<w proton>", type(wp), wp,
1577
+ # "<npne>", type(npne), npne,
1578
+ # "<nrat>", type(nrat), nrat,
1579
+ # "<mpme>: %s" % mpme,
1580
+ # "<we>", type(we), we,
1581
+ # "<electrons>", type(electrons), electrons, electrons.data,
1582
+ # "", sep="\n")
1583
+
1584
+ # Check electrons are properly calculated.
1585
+ ot = self.object_testing
1586
+ pdt.assert_frame_equal(electrons.data, ot.estimate_electrons().data)
1587
+
1588
+ # For some reason, this check failed (20250722).
1589
+ # self.assertEqual(electrons, ot.estimate_electrons())
1590
+
1591
+ # Check that electrons aren't stored in plasma.
1592
+ self.assertFalse("e" in ot.species)
1593
+ comp_data = (
1594
+ pd.concat(
1595
+ {"e": electrons.data}, axis=1, names=["S", "M", "C"], sort=True
1596
+ )
1597
+ .reorder_levels(["M", "C", "S"], axis=1)
1598
+ .sort_index(axis=1)
1599
+ )
1600
+
1601
+ self.assertFalse(
1602
+ comp_data.isin(ot.data).any().any(),
1603
+ "There should not be e- data in plasma data.",
1604
+ )
1605
+
1606
+ # # Check that electrons are still returned when `inplace=True`.
1607
+ # self.assertEqual(electrons,
1608
+ # self.object_testing.estimate_electrons(inplace=True))
1609
+ # # Check that electrons are in plasma after inplace calculation.
1610
+ # self.assertTrue("e" in object_testing.species)
1611
+ # self.assertTrue(electrons.data.isin(plasma.data).all().all(),
1612
+ # "There should be e- data in plasma data.")
1613
+ # self.assertEqual(electrons, self.object_testing.ions["e"])
1614
+ # # Check that electrons aren't duplicately stored after inplace calculation.
1615
+ # self.assertFalse(self.object_testing.data.columns.duplicated().any(),
1616
+ # "There should not be duplicated columns after inplace e- calculation.")
1617
+ # self.object_testing.estimate_electrons(inplace=True)
1618
+ # self.assertFalse(self.object_testing.data.columns.duplicated().any(),
1619
+ # "There should not be duplicated columns after inplace e- calculation.")
1620
+ #
1621
+ # # TODO: Do I need to drop the electrons and create a new plasma as tear down for this test?
1622
+ # self.object_testing.data.drop(electrons.data.columns, axis=1, inplace=True)
1623
+ # species = list(self.object_testing.species)
1624
+ # species.remove("e")
1625
+ # self.object_testing._Plasma__set_species(*species)
1626
+ # self.object_testing._Plasma__set_ions()
1627
+
1628
+ def test_pdynamic_without_m2q_projection(self):
1629
+ # print_inline_debug_info = False
1630
+
1631
+ slist = list(self.stuple)
1632
+
1633
+ if len(slist) == 1:
1634
+ msg = "Must have >1 species to calculate dynamic pressure."
1635
+ with self.assertRaisesRegex(ValueError, msg):
1636
+ self.object_testing.pdynamic(*slist)
1637
+ return None # Exit test.
1638
+
1639
+ v = self.data.v
1640
+ v = pd.concat(
1641
+ {s: v.xs(s, axis=1, level="S") for s in slist},
1642
+ axis=1,
1643
+ names=["S"],
1644
+ sort=True,
1645
+ )
1646
+
1647
+ # Neither `n` nor `rho` units b/c Vcom divides out
1648
+ # the [rho].
1649
+ m = self.mass_in_mp
1650
+ n = self.data.n.xs("", axis=1, level="C")
1651
+ n = pd.concat(
1652
+ {s: n.xs(s, axis=1) for s in slist}, axis=1, names=["S"], sort=True
1653
+ )
1654
+ rho = n.multiply(m, axis=1, level="S")
1655
+
1656
+ # if print_inline_debug_info:
1657
+ # print(
1658
+ # "",
1659
+ # "<Test>",
1660
+ # "<species>: {}".format(self.stuple),
1661
+ # "<v>",
1662
+ # type(v),
1663
+ # v,
1664
+ # "<m>",
1665
+ # type(m),
1666
+ # m,
1667
+ # "<n>",
1668
+ # type(n),
1669
+ # n,
1670
+ # "<rho>",
1671
+ # type(rho),
1672
+ # rho,
1673
+ # sep="\n",
1674
+ # )
1675
+
1676
+ ot = self.object_testing
1677
+ for combo in self.species_combinations:
1678
+
1679
+ if len(combo) == 1:
1680
+ msg = "Must have >1 species to calculate dynamic pressure."
1681
+ with self.assertRaisesRegex(ValueError, msg):
1682
+ ot.pdynamic(*combo)
1683
+ continue # Skip this test case.
1684
+
1685
+ scom = "+".join(combo)
1686
+
1687
+ rho_i = rho.loc[:, list(combo)]
1688
+ rho_t = rho_i.sum(axis=1)
1689
+ v_i = v.loc[:, list(combo)]
1690
+ vcom = (
1691
+ v_i.multiply(rho_i, axis=1, level="S")
1692
+ .T.groupby(level="C")
1693
+ .sum()
1694
+ .T.divide(rho_t, axis=0)
1695
+ )
1696
+ dv_i = v_i.subtract(vcom, axis=1, level="C")
1697
+ dvsq_i = dv_i.pow(2.0).T.groupby(level="S").sum().T
1698
+ dvsq_rho_i = dvsq_i.multiply(rho_i, axis=1, level="S")
1699
+ dvsq_rho = dvsq_rho_i.sum(axis=1)
1700
+
1701
+ const = (
1702
+ 0.5 * constants.m_p * 1e6 * 1e6 / 1e-12
1703
+ ) # [m_p] * [n] * [dv]**2 / [p]
1704
+ pdv = dvsq_rho.multiply(const)
1705
+
1706
+ # if print_inline_debug_info:
1707
+ # print(
1708
+ # "",
1709
+ # "<combo>: {}".format(combo),
1710
+ # "<scom>: %s" % scom,
1711
+ # "<rho_i>",
1712
+ # type(rho_i),
1713
+ # rho_i,
1714
+ # "<rho_t>",
1715
+ # type(rho_t),
1716
+ # rho_t,
1717
+ # "<v_i>",
1718
+ # type(v_i),
1719
+ # v_i,
1720
+ # "<vcom>",
1721
+ # type(vcom),
1722
+ # vcom,
1723
+ # "<dv_i>",
1724
+ # type(dv_i),
1725
+ # dv_i,
1726
+ # "<dvsq_i>",
1727
+ # type(dvsq_i),
1728
+ # dvsq_i,
1729
+ # "<dvsq_rho_i>",
1730
+ # type(dvsq_rho_i),
1731
+ # dvsq_rho_i,
1732
+ # "<dvsq_rho>",
1733
+ # type(dvsq_rho),
1734
+ # dvsq_rho,
1735
+ # "<const> %s" % const,
1736
+ # "<pdv>",
1737
+ # type(pdv),
1738
+ # pdv,
1739
+ # sep="\n",
1740
+ # end="\n\n",
1741
+ # )
1742
+
1743
+ pdv.name = "pdynamic"
1744
+ pdt.assert_series_equal(pdv, ot.pdynamic(*combo))
1745
+ pdt.assert_series_equal(pdv, ot.pdv(*combo))
1746
+ pdt.assert_series_equal(ot.pdynamic(*combo), ot.pdv(*combo))
1747
+ pdt.assert_series_equal(ot.pdv(*combo), ot.pdynamic(*combo))
1748
+ pdt.assert_series_equal(ot.pdynamic(*combo), pdv)
1749
+ pdt.assert_series_equal(ot.pdynamic(*combo), ot.pdynamic(*combo))
1750
+ pdt.assert_series_equal(ot.pdynamic(*combo), ot.pdynamic(*combo[::-1]))
1751
+
1752
+ invalid_msg = "Invalid species"
1753
+ # dynamic pressure shouldn't work with a comma separated list or sub-list.
1754
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1755
+ ot.pdynamic(",".join(combo))
1756
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1757
+ ot.pdynamic(",".join(combo), combo[0])
1758
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1759
+ ot.pdynamic(combo[0], ",".join(combo))
1760
+
1761
+ # dynamic pressure should work with sum of species, but not a sub-list
1762
+ # that includes a sum.
1763
+ pdt.assert_series_equal(pdv, self.object_testing.pdynamic(scom))
1764
+
1765
+ # print("<combo[0], scom>: {}, {}".format(combo[0], scom), end="\n\n")
1766
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1767
+ ot.pdynamic(combo[0], scom)
1768
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1769
+ ot.pdynamic(scom, combo[0])
1770
+
1771
+ # dynamic pressure should fail when each element is a sum or comma list.
1772
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1773
+ ot.pdynamic(scom, ",".join(combo))
1774
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1775
+ ot.pdynamic(",".join(combo), scom)
1776
+
1777
+ def test_pdynamic_with_m2q_projection(self):
1778
+
1779
+ slist = list(self.stuple)
1780
+
1781
+ if len(slist) == 1:
1782
+ msg = "Must have >1 species to calculate dynamic pressure."
1783
+ with self.assertRaisesRegex(ValueError, msg):
1784
+ self.object_testing.pdynamic(*slist)
1785
+ return None # Exit test.
1786
+
1787
+ # Neither `n` nor `rho` units b/c Vcom divides out
1788
+ # the [rho].
1789
+ m = self.mass_in_mp
1790
+ n = self.data.n.xs("", axis=1, level="C")
1791
+ n = pd.concat(
1792
+ {s: n.xs(s, axis=1) for s in slist}, axis=1, names=["S"], sort=True
1793
+ )
1794
+ rho = n.multiply(m, axis=1, level="S")
1795
+
1796
+ const = 0.5 * constants.m_p * 1e6 * 1e6 / 1e-12 # [m_p] * [n] * [dv]**2 / [p]
1797
+
1798
+ ot = self.object_testing
1799
+ invalid_msg = "Invalid species"
1800
+ for combo in self.species_combinations:
1801
+ scom = "+".join(combo)
1802
+ comma = ",".join(combo)
1803
+
1804
+ if len(combo) == 1:
1805
+ msg = "Must have >1 species to calculate dynamic pressure."
1806
+ with self.assertRaisesRegex(ValueError, msg):
1807
+ self.object_testing.pdynamic(*combo)
1808
+ continue # Skip this test case.
1809
+
1810
+ elif len(combo) == 2:
1811
+ dvsq = ot.dv(*combo, project_m2q=True).mag.pow(2)
1812
+ rho_s = rho.loc[:, combo]
1813
+ mu = rho_s.product(axis=1).divide(rho_s.sum(axis=1))
1814
+ pdv = dvsq.multiply(mu, axis=0).multiply(const)
1815
+ pdv.name = "pdynamic"
1816
+
1817
+ pdt.assert_series_equal(pdv, ot.pdynamic(*combo, project_m2q=True))
1818
+ pdt.assert_series_equal(pdv, ot.pdv(*combo, project_m2q=True))
1819
+ pdt.assert_series_equal(
1820
+ ot.pdv(*combo, project_m2q=True),
1821
+ ot.pdynamic(*combo, project_m2q=True),
1822
+ )
1823
+
1824
+ for s in combo:
1825
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1826
+ ot.pdynamic(scom, s, project_m2q=True)
1827
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1828
+ ot.pdynamic(s, scom, project_m2q=True)
1829
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1830
+ ot.pdynamic(comma, s, project_m2q=True)
1831
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1832
+ ot.pdynamic(s, comma, project_m2q=True)
1833
+
1834
+ elif len(combo) == 3:
1835
+ for s in combo:
1836
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1837
+ ot.pdynamic(scom, s, project_m2q=True)
1838
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1839
+ ot.pdynamic(s, scom, project_m2q=True)
1840
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1841
+ ot.pdynamic(comma, s, project_m2q=True)
1842
+ with self.assertRaisesRegex(ValueError, invalid_msg):
1843
+ ot.pdynamic(s, comma, project_m2q=True)
1844
+
1845
+ else:
1846
+ raise NotImplementedError("Unrecognized combo length: {}".format(combo))
1847
+
1848
+ for s in combo:
1849
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
1850
+ ot.pdynamic(comma, s, project_m2q=True)
1851
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
1852
+ ot.pdynamic(s, comma, project_m2q=True)
1853
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
1854
+ ot.pdynamic(scom, s, project_m2q=True)
1855
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
1856
+ ot.pdynamic(s, scom, project_m2q=True)
1857
+
1858
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
1859
+ ot.pdynamic(scom, comma, project_m2q=True)
1860
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
1861
+ ot.pdynamic(comma, scom, project_m2q=True)
1862
+
1863
+ def test_heatflux(self):
1864
+ print_inline_debug_info = False
1865
+
1866
+ # q = rho (dv^3 + (3/2) dv w^2)
1867
+ slist = list(self.stuple)
1868
+ scom = "+".join(slist)
1869
+
1870
+ if len(slist) == 1:
1871
+ msg = "Must have >1 species to calculate heatflux."
1872
+ with self.assertRaisesRegex(ValueError, msg):
1873
+ self.object_testing.heat_flux(*slist)
1874
+ return None # Exit test.
1875
+
1876
+ m = self.mass_in_mp
1877
+ n = self.data.n.loc[:, pd.IndexSlice["", slist]].xs("", axis=1, level="C")
1878
+ rho = n.multiply(m, axis=1, level="S")
1879
+ rho.columns.names = ["S"]
1880
+
1881
+ w = self.data.w.par.loc[:, slist]
1882
+
1883
+ b = self.data.b.xs("", axis=1, level="S").loc[:, ["x", "y", "z"]]
1884
+ bhat = b.divide(b.pow(2).sum(axis=1).pipe(np.sqrt), axis=0)
1885
+
1886
+ v = self.data.v.loc[:, pd.IndexSlice[:, slist]]
1887
+ vcom = (
1888
+ v.multiply(rho, axis=1, level="S")
1889
+ .T.groupby(level="C")
1890
+ .sum()
1891
+ .T.divide(rho.sum(axis=1), axis=0)
1892
+ )
1893
+ dv = v.subtract(vcom, axis=1, level="C")
1894
+
1895
+ dvpar = dv.multiply(bhat, axis=0).T.groupby(level="S").sum().T
1896
+ qa = dvpar.pow(3)
1897
+ qb = dvpar.multiply(w.pow(2), axis=1, level="S").multiply(3.0 / 2.0)
1898
+ qc = rho.multiply(qa.add(qb, axis=1, level="S"), axis=1, level="S")
1899
+
1900
+ coeff = constants.m_p * 1e6 * 1e9 / 1e-7 # [m_p] [n] [v]^3 / [q]
1901
+ q = qc.multiply(coeff)
1902
+ qtot = q.sum(axis=1)
1903
+ qtot.name = "+".join(slist)
1904
+
1905
+ if print_inline_debug_info:
1906
+ print(
1907
+ "",
1908
+ "<Test>",
1909
+ "<species>: {}".format(self.stuple),
1910
+ "<m>",
1911
+ type(m),
1912
+ m,
1913
+ "<n>",
1914
+ type(n),
1915
+ n,
1916
+ "<rho>",
1917
+ type(rho),
1918
+ rho,
1919
+ "<b>",
1920
+ type(b),
1921
+ b,
1922
+ "<bhat>",
1923
+ type(bhat),
1924
+ bhat,
1925
+ "<dv>",
1926
+ type(dv),
1927
+ dv,
1928
+ "<dvpar>",
1929
+ type(dvpar),
1930
+ dvpar,
1931
+ "<qa>",
1932
+ type(qa),
1933
+ qa,
1934
+ "<qb>",
1935
+ type(qb),
1936
+ qb,
1937
+ "<qc>",
1938
+ type(qc),
1939
+ qc,
1940
+ "<q>",
1941
+ type(q),
1942
+ q,
1943
+ "<qtot>",
1944
+ type(qtot),
1945
+ qtot,
1946
+ sep="\n",
1947
+ )
1948
+
1949
+ ot = self.object_testing
1950
+ pdt.assert_frame_equal(q, ot.heat_flux(*slist))
1951
+ pdt.assert_frame_equal(q, ot.qpar(*slist))
1952
+ pdt.assert_series_equal(qtot, ot.heat_flux(scom))
1953
+ pdt.assert_series_equal(qtot, ot.qpar(scom))
1954
+
1955
+ pdt.assert_frame_equal(ot.heat_flux(*slist), ot.qpar(*slist))
1956
+ pdt.assert_series_equal(ot.heat_flux(scom), ot.qpar(scom))
1957
+
1958
+ # for s in self.species_combinations:
1959
+ # if len(s) == 1:
1960
+ # # Only single species case.
1961
+ # qi = q.loc[:, s[0]]
1962
+ # qi.name = s[0]
1963
+ # else:
1964
+ # qi = q.loc[:, list(s)]
1965
+ #
1966
+ # if print_inline_debug_info:
1967
+ # print("<qi>", type(qi), qi, sep="\n")
1968
+ #
1969
+ # # Check the list input case first.
1970
+ # pdt.assert_frame_equal(qi, self.object_testing.heat_flux(*s))
1971
+ # pdt.assert_frame_equal(qi, self.object_testing.qpar(*s))
1972
+ # pdt.assert_frame_equal(
1973
+ # self.object_testing.heat_flux(*s), self.object_testing.qpar(*s)
1974
+ # )
1975
+ # # Then sum `qi` to check the sum case.
1976
+ # qi = qi.sum(axis=1)
1977
+ # qi.name = "+".join(sorted(list(s)))
1978
+ #
1979
+ # if print_inline_debug_info:
1980
+ # print("<qi>", type(qi), qi, sep="\n")
1981
+ #
1982
+ # # Check the sum case.
1983
+ # pdt.assert_series_equal(qi, self.object_testing.heat_flux("+".join(s)))
1984
+ # pdt.assert_series_equal(qi, self.object_testing.qpar("+".join(s)))
1985
+ # pdt.assert_series_equal(
1986
+ # self.object_testing.heat_flux("+".join(s)),
1987
+ # self.object_testing.qpar("+".join(s)),
1988
+ # )
1989
+
1990
+ def test_set_auxiliary_data(self):
1991
+ ot = self.object_testing
1992
+ data = base.TestData().combined_data
1993
+ drop = data.columns.isin(ot.data.columns)
1994
+ aux = data.loc[:, ~drop]
1995
+ ot.set_auxiliary_data(aux)
1996
+ pdt.assert_frame_equal(aux, ot.auxiliary_data)
1997
+ pdt.assert_frame_equal(ot.auxiliary_data, ot.aux)
1998
+
1999
+ ot.set_auxiliary_data(None)
2000
+ self.assertIsNone(ot.auxiliary_data)
2001
+ self.assertIsNone(ot.aux)
2002
+
2003
+ with self.assertRaises(ValueError):
2004
+ ot.set_auxiliary_data(ot.data)
2005
+
2006
+ def test_epoch(self):
2007
+ epoch = self.data.index
2008
+ self.assertIsInstance(epoch, pd.DatetimeIndex)
2009
+
2010
+ ot = self.object_testing
2011
+
2012
+ pdt.assert_index_equal(epoch, ot.data.index)
2013
+ # pdt.assert_index_equal(epoch, ot.index)
2014
+ # pdt.assert_index_equal(epoch, ot.data.epoch)
2015
+ # pdt.assert_index_equal(ot.data.epoch, ot.data.index)
2016
+
2017
+ def test_build_alfvenic_turbulence(self):
2018
+ species = self.species
2019
+ slist = species.split("+")
2020
+ ns = len(slist)
2021
+ data = self.data
2022
+
2023
+ tkc = ["x", "y", "z"]
2024
+ v = data.loc[:, "v"].loc[:, pd.IndexSlice[tkc, slist]]
2025
+ b = data.loc[:, "b"].xs("", axis=1, level="S").loc[:, tkc]
2026
+ n = data.loc[:, "n"].xs("", axis=1, level="C").loc[:, slist]
2027
+ r = n.multiply(self.mass_in_mp.loc[slist], axis=1)
2028
+ rtot = r.sum(axis=1)
2029
+
2030
+ bat = self.object_testing.build_alfvenic_turbulence
2031
+ AlfvenicTurbulence = alfvenic_turbulence.AlfvenicTurbulence
2032
+
2033
+ test_window = "365d"
2034
+ test_periods = 1
2035
+ if ns == 1:
2036
+ v = v.xs(species, axis=1, level="S")
2037
+ alf_turb = AlfvenicTurbulence(
2038
+ v, b, rtot, species, window=test_window, min_periods=test_periods
2039
+ )
2040
+ built = bat(species, window=test_window, min_periods=test_periods)
2041
+ self.assertEqual(alf_turb, built)
2042
+
2043
+ elif ns == 2:
2044
+
2045
+ # Check CoM velocity case.
2046
+ vcom = (
2047
+ v.multiply(r, axis=1, level="S")
2048
+ .T.groupby(level="C")
2049
+ .sum()
2050
+ .T.divide(rtot, axis=0)
2051
+ )
2052
+ alf_turb = AlfvenicTurbulence(
2053
+ vcom, b, rtot, species, window=test_window, min_periods=test_periods
2054
+ )
2055
+ built = bat(species, window=test_window, min_periods=test_periods)
2056
+ self.assertEqual(alf_turb, built)
2057
+
2058
+ s0, s1 = species.split("+")
2059
+ v0 = v.xs(s0, axis=1, level="S")
2060
+ v1 = v.xs(s1, axis=1, level="S")
2061
+
2062
+ # Check dv s0,s1 case.
2063
+ dv = v0.subtract(v1, axis=1, level="C")
2064
+ s0s1 = ",".join([s0, s1])
2065
+ r1 = r.xs(s1, axis=1)
2066
+ alf_turb = AlfvenicTurbulence(
2067
+ dv, b, r1, s0s1, window=test_window, min_periods=test_periods
2068
+ )
2069
+ built = bat(s0s1, window=test_window, min_periods=test_periods)
2070
+ self.assertEqual(alf_turb, built)
2071
+
2072
+ # Check dv s1,s0 case.
2073
+ dv = v1.subtract(v0, axis=1, level="C")
2074
+ s0s1 = ",".join([s1, s0])
2075
+ r0 = r.xs(s0, axis=1)
2076
+ alf_turb = AlfvenicTurbulence(
2077
+ dv, b, r0, s0s1, window=test_window, min_periods=test_periods
2078
+ )
2079
+ built = bat(s0s1, window=test_window, min_periods=test_periods)
2080
+ self.assertEqual(alf_turb, built)
2081
+
2082
+ # Check dv s0,s0+s1 case.
2083
+ dv = v0.subtract(vcom, axis=1)
2084
+ s0s1 = ",".join([s0, species])
2085
+ alf_turb = AlfvenicTurbulence(
2086
+ dv, b, rtot, s0s1, window=test_window, min_periods=test_periods
2087
+ )
2088
+ built = bat(s0s1, window=test_window, min_periods=test_periods)
2089
+ self.assertEqual(alf_turb, built)
2090
+
2091
+ # Check dv s1,s0+s1 case.
2092
+ dv = v1.subtract(vcom, axis=1)
2093
+ s0s1 = ",".join([s1, species])
2094
+ alf_turb = AlfvenicTurbulence(
2095
+ dv, b, rtot, s0s1, window=test_window, min_periods=test_periods
2096
+ )
2097
+ built = bat(s0s1, window=test_window, min_periods=test_periods)
2098
+ self.assertEqual(alf_turb, built)
2099
+
2100
+ elif ns == 3:
2101
+
2102
+ # Check CoM velocity case.
2103
+ vcom = (
2104
+ v.multiply(r, axis=1, level="S")
2105
+ .T.groupby(level="C")
2106
+ .sum()
2107
+ .T.divide(rtot, axis=0)
2108
+ )
2109
+ alf_turb = AlfvenicTurbulence(
2110
+ vcom, b, rtot, species, window=test_window, min_periods=test_periods
2111
+ )
2112
+ built = bat(species, window=test_window, min_periods=test_periods)
2113
+ self.assertEqual(alf_turb, built)
2114
+
2115
+ s0, s1, s2 = species.split("+")
2116
+ v0 = v.xs(s0, axis=1, level="S")
2117
+ v1 = v.xs(s1, axis=1, level="S")
2118
+ v2 = v.xs(s2, axis=1, level="S")
2119
+
2120
+ # Check dv s0,stot case.
2121
+ dv = v0.subtract(vcom, axis=1)
2122
+ s0s1 = ",".join([s0, species])
2123
+ alf_turb = AlfvenicTurbulence(
2124
+ dv, b, rtot, s0s1, window=test_window, min_periods=test_periods
2125
+ )
2126
+ built = bat(s0s1, window=test_window, min_periods=test_periods)
2127
+ self.assertEqual(alf_turb, built)
2128
+
2129
+ # Check dv s1,stot case.
2130
+ dv = v1.subtract(vcom, axis=1)
2131
+ s0s1 = ",".join([s1, species])
2132
+ alf_turb = AlfvenicTurbulence(
2133
+ dv, b, rtot, s0s1, window=test_window, min_periods=test_periods
2134
+ )
2135
+ built = bat(s0s1, window=test_window, min_periods=test_periods)
2136
+ self.assertEqual(alf_turb, built)
2137
+
2138
+ # Check dv s2,stot case.
2139
+ dv = v2.subtract(vcom, axis=1)
2140
+ s0s1 = ",".join([s2, species])
2141
+ alf_turb = AlfvenicTurbulence(
2142
+ dv, b, rtot, s0s1, window=test_window, min_periods=test_periods
2143
+ )
2144
+ built = bat(s0s1, window=test_window, min_periods=test_periods)
2145
+ self.assertEqual(alf_turb, built)
2146
+
2147
+ # Check dv s0+s1,stot case.
2148
+ tks = [s0, s1]
2149
+ r0r1 = r.loc[:, tks]
2150
+ v0v1 = (
2151
+ v.loc[:, pd.IndexSlice[tkc, tks]]
2152
+ .multiply(r0r1, axis=1)
2153
+ .T.groupby(level="C")
2154
+ .sum()
2155
+ .T.divide(r0r1.sum(axis=1), axis=0)
2156
+ )
2157
+ dv = v0v1.subtract(vcom, axis=1)
2158
+ s0s1 = ",".join(["{}+{}".format(*tks), species])
2159
+ alf_turb = AlfvenicTurbulence(
2160
+ dv, b, rtot, s0s1, window=test_window, min_periods=test_periods
2161
+ )
2162
+ built = bat(s0s1, window=test_window, min_periods=test_periods)
2163
+ self.assertEqual(alf_turb, built)
2164
+
2165
+ # Check dv s1+s2,stot case.
2166
+ tks = [s1, s2]
2167
+ r0r1 = r.loc[:, tks]
2168
+ v0v1 = (
2169
+ v.loc[:, pd.IndexSlice[tkc, tks]]
2170
+ .multiply(r0r1, axis=1)
2171
+ .T.groupby(level="C")
2172
+ .sum()
2173
+ .T.divide(r0r1.sum(axis=1), axis=0)
2174
+ )
2175
+ dv = v0v1.subtract(vcom, axis=1)
2176
+ s0s1 = ",".join(["{}+{}".format(*tks), species])
2177
+ alf_turb = AlfvenicTurbulence(
2178
+ dv, b, rtot, s0s1, window=test_window, min_periods=test_periods
2179
+ )
2180
+ built = bat(s0s1, window=test_window, min_periods=test_periods)
2181
+ self.assertEqual(alf_turb, built)
2182
+
2183
+ # Check dv s0+s2,stot case.
2184
+ tks = [s0, s2]
2185
+ r0r1 = r.loc[:, tks]
2186
+ v0v1 = (
2187
+ v.loc[:, pd.IndexSlice[tkc, tks]]
2188
+ .multiply(r0r1, axis=1)
2189
+ .T.groupby(level="C")
2190
+ .sum()
2191
+ .T.divide(r0r1.sum(axis=1), axis=0)
2192
+ )
2193
+ dv = v0v1.subtract(vcom, axis=1)
2194
+ s0s1 = ",".join(["{}+{}".format(*tks), species])
2195
+ alf_turb = AlfvenicTurbulence(
2196
+ dv, b, rtot, s0s1, window=test_window, min_periods=test_periods
2197
+ )
2198
+ built = bat(s0s1, window=test_window, min_periods=test_periods)
2199
+ self.assertEqual(alf_turb, built)
2200
+
2201
+ # Check bad species
2202
+ for bad_species in ("a,p1,p2", "a+p1,p1+p2,a+p2"):
2203
+ with self.assertRaises(ValueError):
2204
+ bat(bad_species)
2205
+ else:
2206
+ msg = "Unexpected number of species in test case\nslist: %s"
2207
+ raise NotImplementedError(msg % (slist))
2208
+
2209
+ def test_drop_species(self):
2210
+ print_inline_debug_info = True # noqa: F841
2211
+
2212
+ ot = self.object_testing
2213
+ slist = list(self.stuple)
2214
+ if len(slist) == 1:
2215
+ msg = "Must have >1 species. Can't have empty plasma."
2216
+ with self.assertRaisesRegex(ValueError, msg):
2217
+ ot.drop_species(*slist)
2218
+ return None
2219
+
2220
+ combos = []
2221
+ for i in np.arange(1, len(slist) + 1):
2222
+ combos += list(itertools.combinations(slist, i))
2223
+ combos.sort(key=len)
2224
+
2225
+ for c in combos:
2226
+ if len(c) == len(slist):
2227
+ msg = "Must have >1 species. Can't have empty plasma."
2228
+ with self.assertRaisesRegex(ValueError, msg):
2229
+ ot.drop_species(*c)
2230
+ continue
2231
+
2232
+ dropped = set(c)
2233
+ remaining = tuple(sorted(set(slist) - dropped))
2234
+
2235
+ result = ot.drop_species(*c)
2236
+
2237
+ self.assertIsInstance(result, plasma.Plasma)
2238
+ self.assertEqual(result.species, remaining)
2239
+
2240
+ keep_mask = (
2241
+ ot.data.columns.get_level_values("S") == ""
2242
+ ) | ot.data.columns.get_level_values("S").isin(remaining)
2243
+ expected_data = ot.data.loc[:, keep_mask]
2244
+
2245
+ pdt.assert_frame_equal(result.data, expected_data)
2246
+
2247
+ pdt.assert_index_equal(result.ions.index, pd.Index(remaining))
2248
+
2249
+ # Original object should remain unchanged
2250
+ self.assertEqual(ot.species, tuple(slist))
2251
+
2252
+ def test_VDFratio(self):
2253
+ # print_inline_debug_info = True
2254
+
2255
+ ot = self.object_testing
2256
+ slist = [s for s in self.species_combinations if len(s) == 2]
2257
+ sother = [sisj[::-1] for sisj in slist]
2258
+ slist = slist + sother
2259
+
2260
+ for sisj in slist:
2261
+ si, sj = sisj
2262
+ ni = self.data.loc[:, ("n", "", si)]
2263
+ nj = self.data.loc[:, ("n", "", sj)]
2264
+
2265
+ wi = (
2266
+ self.data.loc[:, "w"]
2267
+ .xs(si, axis=1, level="S")
2268
+ .drop("scalar", axis=1, errors="ignore")
2269
+ )
2270
+ wi_par = wi.loc[:, "par"]
2271
+ wi_per = wi.loc[:, "per"]
2272
+
2273
+ wj = (
2274
+ self.data.loc[:, "w"]
2275
+ .xs(sj, axis=1, level="S")
2276
+ .drop("scalar", axis=1, errors="ignore")
2277
+ )
2278
+ wj_par = wj.loc[:, "par"]
2279
+ wj_per = wj.loc[:, "per"]
2280
+
2281
+ nbar = ni.divide(nj)
2282
+ par = wj_par.divide(wi_par)
2283
+ per = wj_per.divide(wi_per).pow(2)
2284
+ wbar = par.multiply(per, axis=0)
2285
+ coef = nbar.multiply(wbar, axis=0).apply(np.log)
2286
+
2287
+ vi = self.data.loc[:, "v"].xs(si, axis=1, level="S")
2288
+ vj = self.data.loc[:, "v"].xs(sj, axis=1, level="S")
2289
+ if si == "a":
2290
+ vi = vi.multiply(np.sqrt(self.mass_in_mp[si] / self.charge_states[si]))
2291
+ elif sj == "a":
2292
+ vj = vj.multiply(np.sqrt(self.mass_in_mp[sj] / self.charge_states[sj]))
2293
+ # vi = vi.multiply(np.sqrt(self.mass_in_mp[si] / self.charge_states[si]))
2294
+ # vj = vj.multiply(np.sqrt(self.mass_in_mp[sj] / self.charge_states[sj]))
2295
+
2296
+ dv = vi.subtract(vj)
2297
+ dv = vector.Vector(dv).project(ot.b)
2298
+ # dv = ot.dv(si, sj).project(ot.b)
2299
+ dvw = dv.divide(wj, axis=1).pow(2).sum(axis=1)
2300
+
2301
+ f2f1 = coef.add(dvw, axis=0)
2302
+ f2f1.name = "{}/{}".format(si, sj)
2303
+
2304
+ # if print_inline_debug_info:
2305
+ # print("",
2306
+ # "<Test>",
2307
+ # "<species>: {},{}".format(si, sj),
2308
+ # "<ni>", type(ni), ni,
2309
+ # "<nj>", type(nj), nj,
2310
+ # "<nbar>", type(nbar), nbar,
2311
+ # "<wi>", type(wi), wi,
2312
+ # "<wj>", type(wj), wj,
2313
+ # "<wbar>", type(wbar), wbar,
2314
+ # "<coef>", type(coef), coef,
2315
+ # "<dvw>", type(dvw), dvw,
2316
+ # "<f2f1>", type(f2f1), f2f1,
2317
+ # "",
2318
+ # sep="\n",
2319
+ # )
2320
+
2321
+ pdt.assert_series_equal(f2f1, ot.vdf_ratio(si, sj))
2322
+
2323
+ # Test catching multi-species strings.
2324
+ ssum = "+".join(sisj)
2325
+ scomma = ",".join(sisj)
2326
+ msg0 = "Invalid species"
2327
+ msg1 = "VDFs are evaluated on a species-by-species basis."
2328
+ with self.assertRaisesRegex(ValueError, msg1):
2329
+ ot.vdf_ratio(ssum, si)
2330
+ with self.assertRaisesRegex(ValueError, msg1):
2331
+ ot.vdf_ratio(ssum, sj)
2332
+ with self.assertRaisesRegex(ValueError, msg0):
2333
+ ot.vdf_ratio(scomma, si)
2334
+ with self.assertRaisesRegex(ValueError, msg0):
2335
+ ot.vdf_ratio(scomma, sj)
2336
+ with self.assertRaisesRegex(ValueError, msg1):
2337
+ ot.vdf_ratio(si, ssum)
2338
+ with self.assertRaisesRegex(ValueError, msg1):
2339
+ ot.vdf_ratio(sj, ssum)
2340
+ with self.assertRaisesRegex(ValueError, msg0):
2341
+ ot.vdf_ratio(si, scomma)
2342
+ with self.assertRaisesRegex(ValueError, msg0):
2343
+ ot.vdf_ratio(sj, scomma)
2344
+ with self.assertRaisesRegex(ValueError, msg0):
2345
+ ot.vdf_ratio(ssum, scomma)
2346
+ with self.assertRaisesRegex(ValueError, msg0):
2347
+ ot.vdf_ratio(scomma, ssum)
2348
+
2349
+ def test_specific_entropy(self):
2350
+ # print_inline_debug_info = False
2351
+ ot = self.object_testing
2352
+
2353
+ gamma = 5.0 / 3.0
2354
+ units = 1e4 / constants.e
2355
+ for s in self.species_combinations:
2356
+ multi_species = len(s) > 1
2357
+ pth = ot.pth(*s).xs("scalar", axis=1, level="C" if multi_species else None)
2358
+ rho = ot.rho(*s)
2359
+
2360
+ pth *= 1e-12
2361
+ rho *= 1e6 * constants.m_p
2362
+
2363
+ by_species = (
2364
+ pth.multiply(
2365
+ rho.pow(-gamma),
2366
+ axis=1 if multi_species else 0,
2367
+ level="S" if multi_species else None,
2368
+ )
2369
+ / units
2370
+ )
2371
+ by_species.name = "S"
2372
+
2373
+ test_fcn = (
2374
+ pdt.assert_frame_equal if multi_species else pdt.assert_series_equal
2375
+ )
2376
+ test_fcn(by_species, ot.specific_entropy(*s))
2377
+ test_fcn(ot.specific_entropy(*s), ot.S(*s))
2378
+
2379
+ if multi_species:
2380
+ stotal = "+".join(s)
2381
+ pth_total = pth.sum(axis=1)
2382
+ rho_total = rho.sum(axis=1)
2383
+ total = pth_total.multiply(rho_total.pow(-gamma), axis=0) / units
2384
+ total.name = "S"
2385
+
2386
+ pdt.assert_series_equal(total, ot.specific_entropy(stotal))
2387
+ pdt.assert_series_equal(ot.specific_entropy(stotal), ot.S(stotal))
2388
+ pdt.assert_series_equal(ot.specific_entropy(stotal), ot.S(stotal))
2389
+
2390
+ # comma-separated species list fails
2391
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
2392
+ ot.specific_entropy(",".join(s))
2393
+ with self.assertRaisesRegex(ValueError, "Invalid species"):
2394
+ ot.S(",".join(s))
2395
+
2396
+
2397
+ #####
2398
+ # Tests
2399
+ #####
2400
+ class TestPlasmaAlpha(base.AlphaTest, PlasmaTestBase, base.SWEData):
2401
+ def test_chk_species_fail(self):
2402
+ r"""
2403
+ The code will look something like:
2404
+ for s in bad_species:
2405
+ with self.assertRaisesRegex(ValueError,
2406
+ "Requested species unavailable."):
2407
+ self.object_testing._chk_species(*s)
2408
+ """
2409
+ bad_species = [
2410
+ "a+p1",
2411
+ "p1",
2412
+ "p2",
2413
+ ("a", "p1"),
2414
+ ("a", "p1", "p2"),
2415
+ ("p1", "p2"),
2416
+ "a+p1+p2",
2417
+ "p1+p2",
2418
+ ]
2419
+ for s in bad_species:
2420
+ with self.assertRaisesRegex(ValueError, "Requested species unavailable."):
2421
+ if isinstance(s, str):
2422
+ s = [s]
2423
+ self.object_testing._chk_species(*s)
2424
+
2425
+
2426
+ class TestPlasmaP1(base.P1Test, PlasmaTestBase, base.SWEData):
2427
+ def test_chk_species_fail(self):
2428
+ r"""
2429
+ The code will look something like:
2430
+ for s in bad_species:
2431
+ with self.assertRaisesRegex(ValueError,
2432
+ "Requested species unavailable."):
2433
+ self.object_testing._chk_species(*s)
2434
+ """
2435
+ bad_species = [
2436
+ "a+p1",
2437
+ "a",
2438
+ "p2",
2439
+ ("a", "p1"),
2440
+ ("a", "p1", "p2"),
2441
+ ("p1", "p2"),
2442
+ "a+p1+p2",
2443
+ "p1+p2",
2444
+ ]
2445
+ for s in bad_species:
2446
+ with self.assertRaisesRegex(ValueError, "Requested species unavailable."):
2447
+ if isinstance(s, str):
2448
+ s = [s]
2449
+ self.object_testing._chk_species(*s)
2450
+
2451
+
2452
+ class TestPlasmaP2(base.P2Test, PlasmaTestBase, base.SWEData):
2453
+ def test_chk_species_fail(self):
2454
+ r"""
2455
+ The code will look something like:
2456
+ for s in bad_species:
2457
+ with self.assertRaisesRegex(ValueError,
2458
+ "Requested species unavailable."):
2459
+ self.object_testing._chk_species(*s)
2460
+ """
2461
+ bad_species = [
2462
+ "a+p1",
2463
+ "a",
2464
+ "p1",
2465
+ ("a", "p2"),
2466
+ ("a", "p1", "p2"),
2467
+ ("p1", "p2"),
2468
+ "a+p1+p2",
2469
+ "p1+p2",
2470
+ ]
2471
+ for s in bad_species:
2472
+ with self.assertRaisesRegex(ValueError, "Requested species unavailable."):
2473
+ if isinstance(s, str):
2474
+ s = [s]
2475
+ self.object_testing._chk_species(*s)
2476
+
2477
+
2478
+ class TestPlasmaAlphaP1(base.AlphaP1Test, PlasmaTestBase, base.SWEData):
2479
+ def test_chk_species_fail(self):
2480
+ r"""
2481
+ The code will look something like:
2482
+ for s in bad_species:
2483
+ with self.assertRaisesRegex(ValueError,
2484
+ "Requested species unavailable."):
2485
+ self.object_testing._chk_species(*s)
2486
+ """
2487
+ bad_species = [
2488
+ ("a", "p2"),
2489
+ ("a", "e"),
2490
+ ("p2", "e"),
2491
+ ("a", "p1", "p2"),
2492
+ ("p1", "p2"),
2493
+ "a+p1+p2",
2494
+ "p1+p2",
2495
+ "a+e+p1+p2",
2496
+ "e+p1+p2",
2497
+ ]
2498
+ for s in bad_species:
2499
+ with self.assertRaisesRegex(ValueError, "Requested species unavailable."):
2500
+ if isinstance(s, str):
2501
+ s = [s]
2502
+ self.object_testing._chk_species(*s)
2503
+
2504
+
2505
+ class TestPlasmaAlphaP2(base.AlphaP2Test, PlasmaTestBase, base.SWEData):
2506
+ def test_chk_species_fail(self):
2507
+ r"""
2508
+ The code will look something like:
2509
+ for s in bad_species:
2510
+ with self.assertRaisesRegex(ValueError,
2511
+ "Requested species unavailable."):
2512
+ self.object_testing._chk_species(*s)
2513
+ """
2514
+ bad_species = [
2515
+ ("a", "p1"),
2516
+ ("a", "e"),
2517
+ ("p2", "e"),
2518
+ ("a", "p1", "p2"),
2519
+ ("p1", "p2"),
2520
+ "a+p1+p2",
2521
+ "p1+p2",
2522
+ "a+e+p1+p2",
2523
+ "e+p1+p2",
2524
+ ]
2525
+ for s in bad_species:
2526
+ with self.assertRaisesRegex(ValueError, "Requested species unavailable."):
2527
+ if isinstance(s, str):
2528
+ s = [s]
2529
+ self.object_testing._chk_species(*s)
2530
+
2531
+
2532
+ class TestPlasmaP1P2(base.P1P2Test, PlasmaTestBase, base.SWEData):
2533
+ def test_chk_species_fail(self):
2534
+ r"""
2535
+ The code will look something like:
2536
+ for s in bad_species:
2537
+ with self.assertRaisesRegex(ValueError,
2538
+ "Requested species unavailable."):
2539
+ self.object_testing._chk_species(*s)
2540
+ """
2541
+ bad_species = [
2542
+ "a",
2543
+ ("a", "p2"),
2544
+ ("a", "e"),
2545
+ ("p2", "e"),
2546
+ ("a", "p1", "p2"),
2547
+ "a+p1+p2",
2548
+ "a+e+p1+p2",
2549
+ "e+p1+p2",
2550
+ ]
2551
+ for s in bad_species:
2552
+ with self.assertRaisesRegex(ValueError, "Requested species unavailable."):
2553
+ if isinstance(s, str):
2554
+ s = [s]
2555
+ self.object_testing._chk_species(*s)
2556
+
2557
+
2558
+ class TestPlasmaAlphaP1P2(base.AlphaP1P2Test, PlasmaTestBase, base.SWEData):
2559
+ def test_chk_species_fail(self):
2560
+ r"""
2561
+ The code will look something like:
2562
+ for s in bad_species:
2563
+ with self.assertRaisesRegex(ValueError,
2564
+ "Requested species unavailable."):
2565
+ self.object_testing._chk_species(*s)
2566
+ """
2567
+ bad_species = [
2568
+ "a+e",
2569
+ "a+e",
2570
+ "e",
2571
+ ("e", "p1"),
2572
+ ("a", "p1", "p2", "e"),
2573
+ ("p1", "p2", "e"),
2574
+ "a+e+p1+p2",
2575
+ "e+p1+p2",
2576
+ ]
2577
+ for s in bad_species:
2578
+ with self.assertRaisesRegex(ValueError, "Requested species unavailable."):
2579
+ if isinstance(s, str):
2580
+ s = [s]
2581
+ self.object_testing._chk_species(*s)