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,1209 @@
1
+ #!/usr/bin/env python
2
+ """Tests for solarwindpy.plotting.select_data_from_figure module.
3
+
4
+ This module provides comprehensive test coverage for the SelectFromPlot2D class used for
5
+ interactive data selection from plotted figures.
6
+ """
7
+
8
+ import pytest
9
+ import logging
10
+ import pandas as pd
11
+ import numpy as np
12
+ from pathlib import Path
13
+ from unittest.mock import patch, MagicMock, call, Mock
14
+ from collections import namedtuple
15
+
16
+ import matplotlib
17
+
18
+ matplotlib.use("Agg") # Use non-interactive backend
19
+ from matplotlib import pyplot as plt
20
+ from matplotlib.widgets import RectangleSelector
21
+ from matplotlib.patches import Rectangle
22
+ from matplotlib.text import Text
23
+
24
+ import solarwindpy.plotting.select_data_from_figure as select_module
25
+ from solarwindpy.plotting.select_data_from_figure import SelectFromPlot2D, DateAxes
26
+
27
+
28
+ class TestSelectDataFromFigureModuleStructure:
29
+ """Test select_data_from_figure module structure and imports."""
30
+
31
+ def test_module_imports(self):
32
+ """Test that all required imports are accessible."""
33
+ # Test basic imports
34
+ assert hasattr(select_module, "logging")
35
+ assert hasattr(select_module, "np")
36
+ assert hasattr(select_module, "pd")
37
+ assert hasattr(select_module, "mpl")
38
+ assert hasattr(select_module, "namedtuple")
39
+
40
+ def test_module_all_export(self):
41
+ """Test that __all__ exports are correct."""
42
+ assert hasattr(select_module, "__all__")
43
+ assert "SelectFromPlot2D" in select_module.__all__
44
+ assert len(select_module.__all__) == 1
45
+
46
+ def test_date_axes_namedtuple(self):
47
+ """Test DateAxes namedtuple structure."""
48
+ assert hasattr(select_module, "DateAxes")
49
+ assert DateAxes._fields == ("x", "y")
50
+
51
+ # Test creation
52
+ date_axes = DateAxes(True, False)
53
+ assert date_axes.x is True
54
+ assert date_axes.y is False
55
+
56
+ def test_select_from_plot_2d_class_available(self):
57
+ """Test that SelectFromPlot2D class is accessible from module."""
58
+ assert hasattr(select_module, "SelectFromPlot2D")
59
+ assert callable(select_module.SelectFromPlot2D)
60
+
61
+
62
+ class TestSelectFromPlot2DInitialization:
63
+ """Test SelectFromPlot2D class initialization and basic properties."""
64
+
65
+ @pytest.fixture
66
+ def mock_plotter(self):
67
+ """Create a mock plotter object."""
68
+ plotter = Mock()
69
+ plotter.data = pd.DataFrame(
70
+ {
71
+ "x": np.random.randn(100),
72
+ "y": np.random.randn(100),
73
+ "z": np.random.randn(100),
74
+ }
75
+ )
76
+ plotter.log = Mock()
77
+ plotter.log.x = False
78
+ plotter.log.y = False
79
+ return plotter
80
+
81
+ @pytest.fixture
82
+ def mock_ax(self):
83
+ """Create a mock axes object."""
84
+ ax = Mock()
85
+ ax.figure = Mock()
86
+ ax.figure.axes = [ax] # Single panel by default
87
+ ax.patches = []
88
+ ax.transAxes = Mock()
89
+ ax.text = Mock(return_value=Mock())
90
+ ax.xaxis = Mock()
91
+ ax.xaxis.get_label = Mock()
92
+ ax.xaxis.get_label.return_value = Mock()
93
+ ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
94
+ ax.yaxis = Mock()
95
+ ax.yaxis.get_label = Mock()
96
+ ax.yaxis.get_label.return_value = Mock()
97
+ ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
98
+ ax.figure.canvas = Mock()
99
+ ax.figure.canvas.draw_idle = Mock()
100
+ ax.add_patch = Mock()
101
+ ax.scatter = Mock()
102
+ ax.get_xlim = Mock(return_value=(0, 1))
103
+ ax.get_ylim = Mock(return_value=(0, 1))
104
+ ax.set_xlim = Mock()
105
+ ax.set_ylim = Mock()
106
+ return ax
107
+
108
+ @patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
109
+ def test_init_basic(self, mock_rect_selector, mock_plotter, mock_ax):
110
+ """Test basic initialization."""
111
+ mock_selector = Mock()
112
+ mock_rect_selector.return_value = mock_selector
113
+
114
+ selector = SelectFromPlot2D(mock_plotter, mock_ax)
115
+
116
+ # Test basic attributes
117
+ assert selector._plotter is mock_plotter
118
+ assert selector._ax is mock_ax
119
+ assert selector._corners == tuple()
120
+ assert hasattr(selector, "_date_axes")
121
+ assert hasattr(selector, "_text")
122
+ assert hasattr(selector, "_selector")
123
+
124
+ # Test initialization calls
125
+ mock_rect_selector.assert_called_once()
126
+ mock_ax.text.assert_called_once()
127
+
128
+ @patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
129
+ def test_init_with_colorbar(self, mock_rect_selector, mock_plotter, mock_ax):
130
+ """Test initialization with colorbar consideration."""
131
+ # Mock multi-panel with colorbar
132
+ mock_ax.figure.axes = [mock_ax, Mock(), Mock()] # 3 axes (2 + colorbar)
133
+ mock_selector = Mock()
134
+ mock_rect_selector.return_value = mock_selector
135
+
136
+ selector = SelectFromPlot2D(mock_plotter, mock_ax, has_colorbar=True)
137
+
138
+ # With colorbar=True and 3 axes: (3 - 1) = 2 > 1, so is_multipanel=True
139
+ assert selector.is_multipanel is True
140
+
141
+ @patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
142
+ def test_init_date_axes(self, mock_rect_selector, mock_plotter, mock_ax):
143
+ """Test initialization with date axes."""
144
+ mock_selector = Mock()
145
+ mock_rect_selector.return_value = mock_selector
146
+
147
+ selector = SelectFromPlot2D(mock_plotter, mock_ax, xdate=True, ydate=False)
148
+
149
+ assert selector.date_axes.x is True
150
+ assert selector.date_axes.y is False
151
+
152
+ @patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
153
+ def test_init_text_kwargs(self, mock_rect_selector, mock_plotter, mock_ax):
154
+ """Test initialization with custom text kwargs."""
155
+ mock_selector = Mock()
156
+ mock_rect_selector.return_value = mock_selector
157
+ text_kwargs = {"fontsize": 12, "color": "red"}
158
+
159
+ selector = SelectFromPlot2D(mock_plotter, mock_ax, text_kwargs=text_kwargs)
160
+
161
+ # Should initialize without error
162
+ assert hasattr(selector, "_text")
163
+
164
+ @patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
165
+ def test_invalid_plotter_type(self, mock_rect_selector, mock_ax):
166
+ """Test that invalid plotter type raises AttributeError when accessing plotter
167
+ attributes."""
168
+ mock_selector = Mock()
169
+ mock_rect_selector.return_value = mock_selector
170
+
171
+ # Initialization should work (plotter attributes not accessed yet)
172
+ selector = SelectFromPlot2D("invalid_plotter", mock_ax)
173
+
174
+ # But accessing plotter.data should fail
175
+ with pytest.raises(AttributeError):
176
+ selector._add_corners((0, 1, 0, 1))
177
+ selector.sample_data(n=3)
178
+
179
+
180
+ class TestSelectFromPlot2DProperties:
181
+ """Test SelectFromPlot2D properties."""
182
+
183
+ @pytest.fixture
184
+ def selector_setup(self):
185
+ """Set up a SelectFromPlot2D instance for testing."""
186
+ with patch(
187
+ "solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
188
+ ):
189
+ plotter = Mock()
190
+ plotter.data = pd.DataFrame(
191
+ {
192
+ "x": np.random.randn(100),
193
+ "y": np.random.randn(100),
194
+ "z": np.random.randn(100),
195
+ }
196
+ )
197
+ plotter.log = Mock()
198
+ plotter.log.x = False
199
+ plotter.log.y = False
200
+
201
+ ax = Mock()
202
+ ax.figure = Mock()
203
+ ax.figure.axes = [ax]
204
+ ax.patches = []
205
+ ax.transAxes = Mock()
206
+ ax.text = Mock(return_value=Mock())
207
+ ax.xaxis = Mock()
208
+ ax.xaxis.get_label = Mock()
209
+ ax.xaxis.get_label.return_value = Mock()
210
+ ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
211
+ ax.yaxis = Mock()
212
+ ax.yaxis.get_label = Mock()
213
+ ax.yaxis.get_label.return_value = Mock()
214
+ ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
215
+ ax.figure.canvas = Mock()
216
+ ax.figure.canvas.draw_idle = Mock()
217
+
218
+ selector = SelectFromPlot2D(plotter, ax)
219
+ return selector, plotter, ax
220
+
221
+ def test_ax_property(self, selector_setup):
222
+ """Test ax property."""
223
+ selector, plotter, ax = selector_setup
224
+ assert selector.ax is ax
225
+
226
+ def test_corners_property(self, selector_setup):
227
+ """Test corners property."""
228
+ selector, plotter, ax = selector_setup
229
+ assert selector.corners == tuple()
230
+
231
+ # Test after adding corners
232
+ selector._add_corners((0, 1, 0, 1))
233
+ assert len(selector.corners) == 1
234
+ assert selector.corners[0] == (0, 1, 0, 1)
235
+
236
+ def test_date_axes_property(self, selector_setup):
237
+ """Test date_axes property."""
238
+ selector, plotter, ax = selector_setup
239
+ assert hasattr(selector.date_axes, "x")
240
+ assert hasattr(selector.date_axes, "y")
241
+
242
+ def test_is_multipanel_property(self, selector_setup):
243
+ """Test is_multipanel property."""
244
+ selector, plotter, ax = selector_setup
245
+ assert selector.is_multipanel is False # Single panel by default
246
+
247
+ def test_plotter_property(self, selector_setup):
248
+ """Test plotter property."""
249
+ selector, plotter, ax = selector_setup
250
+ assert selector.plotter is plotter
251
+
252
+ def test_selector_property(self, selector_setup):
253
+ """Test selector property."""
254
+ selector, plotter, ax = selector_setup
255
+ assert hasattr(selector, "selector")
256
+ assert selector.selector is not None
257
+
258
+ def test_text_property(self, selector_setup):
259
+ """Test text property."""
260
+ selector, plotter, ax = selector_setup
261
+ assert hasattr(selector, "text")
262
+ assert selector.text is not None
263
+
264
+ def test_num_initial_patches_property(self, selector_setup):
265
+ """Test num_initial_patches property."""
266
+ selector, plotter, ax = selector_setup
267
+ assert hasattr(selector, "num_initial_patches")
268
+ assert isinstance(selector.num_initial_patches, int)
269
+
270
+ def test_num_selection_patches_property(self, selector_setup):
271
+ """Test num_selection_patches property."""
272
+ selector, plotter, ax = selector_setup
273
+ # Initially should be 0
274
+ assert selector.num_selection_patches == 0
275
+
276
+ def test_logger_property(self, selector_setup):
277
+ """Test logger property."""
278
+ selector, plotter, ax = selector_setup
279
+ logger = selector.logger
280
+ assert isinstance(logger, logging.Logger)
281
+ assert "analysis" in logger.name
282
+
283
+
284
+ class TestSelectFromPlot2DCornerManagement:
285
+ """Test corner management methods."""
286
+
287
+ @pytest.fixture
288
+ def selector_setup(self):
289
+ """Set up a SelectFromPlot2D instance for testing."""
290
+ with patch(
291
+ "solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
292
+ ):
293
+ plotter = Mock()
294
+ plotter.data = pd.DataFrame(
295
+ {
296
+ "x": np.random.randn(100),
297
+ "y": np.random.randn(100),
298
+ "z": np.random.randn(100),
299
+ }
300
+ )
301
+ plotter.log = Mock()
302
+ plotter.log.x = False
303
+ plotter.log.y = False
304
+
305
+ ax = Mock()
306
+ ax.figure = Mock()
307
+ ax.figure.axes = [ax]
308
+ ax.patches = []
309
+ ax.transAxes = Mock()
310
+ ax.text = Mock(return_value=Mock())
311
+ ax.xaxis = Mock()
312
+ ax.xaxis.get_label = Mock()
313
+ ax.xaxis.get_label.return_value = Mock()
314
+ ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
315
+ ax.yaxis = Mock()
316
+ ax.yaxis.get_label = Mock()
317
+ ax.yaxis.get_label.return_value = Mock()
318
+ ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
319
+ ax.figure.canvas = Mock()
320
+ ax.figure.canvas.draw_idle = Mock()
321
+
322
+ selector = SelectFromPlot2D(plotter, ax)
323
+ return selector, plotter, ax
324
+
325
+ def test_init_corners(self, selector_setup):
326
+ """Test _init_corners method."""
327
+ selector, plotter, ax = selector_setup
328
+
329
+ # Corners should be initialized as empty tuple
330
+ assert selector.corners == tuple()
331
+
332
+ def test_add_corners(self, selector_setup):
333
+ """Test _add_corners method."""
334
+ selector, plotter, ax = selector_setup
335
+
336
+ # Add first corner
337
+ corner1 = (0, 1, 0, 1)
338
+ selector._add_corners(corner1)
339
+ assert len(selector.corners) == 1
340
+ assert selector.corners[0] == corner1
341
+
342
+ # Add second corner
343
+ corner2 = (2, 3, 2, 3)
344
+ selector._add_corners(corner2)
345
+ assert len(selector.corners) == 2
346
+ assert selector.corners[0] == corner1
347
+ assert selector.corners[1] == corner2
348
+
349
+
350
+ class TestSelectFromPlot2DTextManagement:
351
+ """Test text management methods."""
352
+
353
+ @pytest.fixture
354
+ def selector_setup(self):
355
+ """Set up a SelectFromPlot2D instance for testing."""
356
+ with patch(
357
+ "solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
358
+ ):
359
+ plotter = Mock()
360
+ plotter.data = pd.DataFrame(
361
+ {
362
+ "x": np.random.randn(100),
363
+ "y": np.random.randn(100),
364
+ "z": np.random.randn(100),
365
+ }
366
+ )
367
+ plotter.log = Mock()
368
+ plotter.log.x = False
369
+ plotter.log.y = False
370
+
371
+ ax = Mock()
372
+ ax.figure = Mock()
373
+ ax.figure.axes = [ax]
374
+ ax.patches = []
375
+ ax.transAxes = Mock()
376
+ ax.text = Mock(return_value=Mock())
377
+ ax.xaxis = Mock()
378
+ ax.xaxis.get_label = Mock()
379
+ ax.xaxis.get_label.return_value = Mock()
380
+ ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
381
+ ax.yaxis = Mock()
382
+ ax.yaxis.get_label = Mock()
383
+ ax.yaxis.get_label.return_value = Mock()
384
+ ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
385
+ ax.figure.canvas = Mock()
386
+ ax.figure.canvas.draw_idle = Mock()
387
+
388
+ selector = SelectFromPlot2D(plotter, ax)
389
+ # Mock required attributes for text methods
390
+ selector._sampled_indices = pd.Index([1, 2, 3])
391
+ selector._failed_samples = []
392
+ selector._sampled_per_patch = 3
393
+
394
+ return selector, plotter, ax
395
+
396
+ def test_finalize_text_single_panel(self, selector_setup):
397
+ """Test _finalize_text method for single panel."""
398
+ selector, plotter, ax = selector_setup
399
+ selector._is_multipanel = False
400
+
401
+ # Mock the text object
402
+ mock_text = Mock()
403
+ selector._text = mock_text
404
+
405
+ selector._finalize_text()
406
+
407
+ # Should call set_text with formatted string
408
+ mock_text.set_text.assert_called_once()
409
+ call_args = mock_text.set_text.call_args[0][0]
410
+ assert "Patches" in call_args
411
+ assert "Spectra" in call_args
412
+ assert " - " in call_args # Single panel format
413
+
414
+ def test_finalize_text_multi_panel(self, selector_setup):
415
+ """Test _finalize_text method for multi panel."""
416
+ selector, plotter, ax = selector_setup
417
+ selector._is_multipanel = True
418
+
419
+ # Mock the text object
420
+ mock_text = Mock()
421
+ selector._text = mock_text
422
+
423
+ selector._finalize_text()
424
+
425
+ # Should call set_text with formatted string
426
+ mock_text.set_text.assert_called_once()
427
+ call_args = mock_text.set_text.call_args[0][0]
428
+ assert "Patches" in call_args
429
+ assert "Spectra" in call_args
430
+ assert "\n" in call_args # Multi panel format
431
+
432
+ def test_update_text_no_dates(self, selector_setup):
433
+ """Test _update_text method without date axes."""
434
+ selector, plotter, ax = selector_setup
435
+
436
+ # Mock selector with extents
437
+ mock_selector = Mock()
438
+ mock_selector.extents = (0.1, 0.9, 0.2, 0.8)
439
+ selector._selector = mock_selector
440
+
441
+ # Mock text object
442
+ mock_text = Mock()
443
+ selector._text = mock_text
444
+
445
+ selector._update_text()
446
+
447
+ mock_text.set_text.assert_called_once()
448
+ call_args = mock_text.set_text.call_args[0][0]
449
+ assert "Patch" in call_args
450
+ assert "Lower Left" in call_args
451
+ assert "Upper Right" in call_args
452
+
453
+ @patch("solarwindpy.plotting.select_data_from_figure.mpl.dates.num2date")
454
+ def test_update_text_with_dates(self, mock_num2date, selector_setup):
455
+ """Test _update_text method with date axes."""
456
+ selector, plotter, ax = selector_setup
457
+
458
+ # Set date axes
459
+ selector._date_axes = DateAxes(True, True)
460
+
461
+ # Mock date conversion
462
+ mock_date = Mock()
463
+ mock_date.strftime = Mock(return_value="2020-01-01 12:00:00")
464
+ mock_num2date.return_value = [mock_date, mock_date]
465
+
466
+ # Mock selector with extents
467
+ mock_selector = Mock()
468
+ mock_selector.extents = (737425.5, 737426.5, 737425.5, 737426.5) # date numbers
469
+ selector._selector = mock_selector
470
+
471
+ # Mock text object
472
+ mock_text = Mock()
473
+ selector._text = mock_text
474
+
475
+ selector._update_text()
476
+
477
+ mock_text.set_text.assert_called_once()
478
+ mock_num2date.assert_called()
479
+
480
+
481
+ class TestSelectFromPlot2DSelection:
482
+ """Test selection-related methods."""
483
+
484
+ @pytest.fixture
485
+ def selector_setup(self):
486
+ """Set up a SelectFromPlot2D instance for testing."""
487
+ with patch(
488
+ "solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
489
+ ):
490
+ plotter = Mock()
491
+ plotter.data = pd.DataFrame(
492
+ {
493
+ "x": np.random.randn(100),
494
+ "y": np.random.randn(100),
495
+ "z": np.random.randn(100),
496
+ }
497
+ )
498
+ plotter.log = Mock()
499
+ plotter.log.x = False
500
+ plotter.log.y = False
501
+
502
+ ax = Mock()
503
+ ax.figure = Mock()
504
+ ax.figure.axes = [ax]
505
+ ax.patches = []
506
+ ax.transAxes = Mock()
507
+ ax.text = Mock(return_value=Mock())
508
+ ax.xaxis = Mock()
509
+ ax.xaxis.get_label = Mock()
510
+ ax.xaxis.get_label.return_value = Mock()
511
+ ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
512
+ ax.yaxis = Mock()
513
+ ax.yaxis.get_label = Mock()
514
+ ax.yaxis.get_label.return_value = Mock()
515
+ ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
516
+ ax.figure.canvas = Mock()
517
+ ax.figure.canvas.draw_idle = Mock()
518
+ ax.add_patch = Mock()
519
+ ax.scatter = Mock()
520
+ ax.get_xlim = Mock(return_value=(0, 1))
521
+ ax.get_ylim = Mock(return_value=(0, 1))
522
+ ax.set_xlim = Mock()
523
+ ax.set_ylim = Mock()
524
+
525
+ selector = SelectFromPlot2D(plotter, ax)
526
+ return selector, plotter, ax
527
+
528
+ @patch("solarwindpy.plotting.select_data_from_figure.mpl.patches.Rectangle")
529
+ def test_onselect(self, mock_rectangle, selector_setup):
530
+ """Test onselect method."""
531
+ selector, plotter, ax = selector_setup
532
+
533
+ # Mock selector rect_bbox
534
+ mock_selector = Mock()
535
+ mock_selector._rect_bbox = [0, 0, 1, 1] # x, y, w, h
536
+ mock_selector.extents = (0, 1, 0, 1)
537
+ selector._selector = mock_selector
538
+
539
+ # Mock text object
540
+ mock_text = Mock()
541
+ selector._text = mock_text
542
+
543
+ # Mock rectangle patch
544
+ mock_rect = Mock()
545
+ mock_rectangle.return_value = mock_rect
546
+
547
+ selector.onselect("press", "release")
548
+
549
+ # Should create rectangle and add to axis
550
+ mock_rectangle.assert_called_once()
551
+ ax.add_patch.assert_called_once_with(mock_rect)
552
+
553
+ # Should update corners and text
554
+ assert len(selector.corners) == 1
555
+ mock_text.set_text.assert_called()
556
+ ax.figure.canvas.draw_idle.assert_called()
557
+
558
+ def test_set_ax(self, selector_setup):
559
+ """Test set_ax method."""
560
+ selector, plotter, ax = selector_setup
561
+
562
+ # Test single panel
563
+ new_ax = Mock()
564
+ new_ax.figure = Mock()
565
+ new_ax.figure.axes = [new_ax]
566
+
567
+ selector.set_ax(new_ax, has_colorbar=False)
568
+
569
+ assert selector._ax is new_ax
570
+ assert selector._is_multipanel is False
571
+
572
+ # Test multi panel with colorbar
573
+ new_ax.figure.axes = [new_ax, Mock(), Mock()] # 3 axes
574
+ selector.set_ax(new_ax, has_colorbar=True)
575
+ assert selector._is_multipanel is True
576
+
577
+ def test_start_text_single_panel(self, selector_setup):
578
+ """Test start_text method for single panel."""
579
+ selector, plotter, ax = selector_setup
580
+ selector._is_multipanel = False
581
+
582
+ # Mock text creation
583
+ mock_text = Mock()
584
+ ax.text = Mock(return_value=mock_text)
585
+
586
+ selector.start_text()
587
+
588
+ # Should call ax.text with appropriate parameters
589
+ ax.text.assert_called()
590
+ call_kwargs = ax.text.call_args[1]
591
+ assert call_kwargs["va"] == "bottom" # Single panel
592
+
593
+ assert selector._text is mock_text
594
+
595
+ def test_start_text_multi_panel(self, selector_setup):
596
+ """Test start_text method for multi panel."""
597
+ selector, plotter, ax = selector_setup
598
+ selector._is_multipanel = True
599
+
600
+ # Mock text creation
601
+ mock_text = Mock()
602
+ ax.text = Mock(return_value=mock_text)
603
+
604
+ selector.start_text()
605
+
606
+ # Should call ax.text with appropriate parameters
607
+ ax.text.assert_called()
608
+ call_kwargs = ax.text.call_args[1]
609
+ assert call_kwargs["va"] == "top" # Multi panel
610
+
611
+ assert selector._text is mock_text
612
+
613
+ def test_start_text_custom_kwargs(self, selector_setup):
614
+ """Test start_text method with custom kwargs."""
615
+ selector, plotter, ax = selector_setup
616
+
617
+ # Mock text creation
618
+ mock_text = Mock()
619
+ ax.text = Mock(return_value=mock_text)
620
+
621
+ custom_kwargs = {"fontsize": 14, "color": "blue"}
622
+ selector.start_text(**custom_kwargs)
623
+
624
+ ax.text.assert_called()
625
+ assert selector._text is mock_text
626
+
627
+ def test_start_selector(self, selector_setup):
628
+ """Test start_selector method."""
629
+ selector, plotter, ax = selector_setup
630
+
631
+ # Should have initialized selector and tracked initial patches
632
+ assert hasattr(selector, "_selector")
633
+ assert hasattr(selector, "_num_initial_patches")
634
+ assert isinstance(selector._num_initial_patches, int)
635
+
636
+
637
+ class TestSelectFromPlot2DSampleData:
638
+ """Test data sampling methods."""
639
+
640
+ @pytest.fixture
641
+ def selector_setup(self):
642
+ """Set up a SelectFromPlot2D instance for testing."""
643
+ with patch(
644
+ "solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
645
+ ):
646
+ # Create realistic test data
647
+ np.random.seed(42)
648
+ data = pd.DataFrame(
649
+ {
650
+ "x": np.random.uniform(0, 10, 100),
651
+ "y": np.random.uniform(0, 10, 100),
652
+ "z": np.random.uniform(0, 1, 100),
653
+ },
654
+ index=pd.RangeIndex(100),
655
+ )
656
+
657
+ plotter = Mock()
658
+ plotter.data = data
659
+ plotter.log = Mock()
660
+ plotter.log.x = False
661
+ plotter.log.y = False
662
+
663
+ ax = Mock()
664
+ ax.figure = Mock()
665
+ ax.figure.axes = [ax]
666
+ ax.patches = []
667
+ ax.transAxes = Mock()
668
+ ax.text = Mock(return_value=Mock())
669
+ ax.xaxis = Mock()
670
+ ax.xaxis.get_label = Mock()
671
+ ax.xaxis.get_label.return_value = Mock()
672
+ ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
673
+ ax.yaxis = Mock()
674
+ ax.yaxis.get_label = Mock()
675
+ ax.yaxis.get_label.return_value = Mock()
676
+ ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
677
+ ax.figure.canvas = Mock()
678
+ ax.figure.canvas.draw_idle = Mock()
679
+ ax.add_patch = Mock()
680
+ ax.scatter = Mock()
681
+ ax.get_xlim = Mock(return_value=(0, 1))
682
+ ax.get_ylim = Mock(return_value=(0, 1))
683
+ ax.set_xlim = Mock()
684
+ ax.set_ylim = Mock()
685
+
686
+ selector = SelectFromPlot2D(plotter, ax)
687
+ return selector, plotter, ax
688
+
689
+ def test_sample_data_basic(self, selector_setup):
690
+ """Test basic sample_data functionality."""
691
+ selector, plotter, ax = selector_setup
692
+
693
+ # Add a corner that should contain some data
694
+ selector._add_corners((2, 8, 2, 8))
695
+
696
+ selector.sample_data(n=3, random_state=42)
697
+
698
+ # Should have sampled indices
699
+ assert hasattr(selector, "_sampled_indices")
700
+ assert hasattr(selector, "_failed_samples")
701
+ assert hasattr(selector, "_sampled_per_patch")
702
+ assert selector._sampled_per_patch == 3
703
+ assert isinstance(selector._sampled_indices, pd.Index)
704
+ assert len(selector._failed_samples) >= 0
705
+
706
+ def test_sample_data_frac_not_implemented(self, selector_setup):
707
+ """Test that sample_data with frac raises NotImplementedError."""
708
+ selector, plotter, ax = selector_setup
709
+
710
+ with pytest.raises(NotImplementedError, match="Please use the `n` kwarg"):
711
+ selector.sample_data(frac=0.1)
712
+
713
+ def test_sample_data_with_log_axes(self, selector_setup):
714
+ """Test sample_data with logarithmic axes."""
715
+ selector, plotter, ax = selector_setup
716
+
717
+ # Set log axes
718
+ plotter.log.x = True
719
+ plotter.log.y = True
720
+
721
+ # Add corner
722
+ selector._add_corners((1, 10, 1, 10)) # Will be log10 transformed
723
+
724
+ selector.sample_data(n=2, random_state=42)
725
+
726
+ # Should complete without error
727
+ assert hasattr(selector, "_sampled_indices")
728
+
729
+ def test_sample_data_empty_region(self, selector_setup):
730
+ """Test sample_data with region containing no data."""
731
+ selector, plotter, ax = selector_setup
732
+
733
+ # Add corner outside data range
734
+ selector._add_corners((100, 110, 100, 110))
735
+
736
+ selector.sample_data(n=3, random_state=42)
737
+
738
+ # Should have failed samples
739
+ assert len(selector._failed_samples) == 1
740
+ assert selector._failed_samples[0] == (100, 110, 100, 110)
741
+
742
+ def test_sample_data_with_other_selector(self, selector_setup):
743
+ """Test sample_data with other_SelectFromPlot2D."""
744
+ selector, plotter, ax = selector_setup
745
+
746
+ # Create another selector with already sampled indices
747
+ other_selector = Mock()
748
+ other_selector.sampled_indices = pd.Index([0, 1, 2])
749
+
750
+ # Add corner
751
+ selector._add_corners((0, 10, 0, 10))
752
+
753
+ selector.sample_data(
754
+ n=3, random_state=42, other_SelectFromPlot2D=other_selector
755
+ )
756
+
757
+ # Should complete without error
758
+ assert hasattr(selector, "_sampled_indices")
759
+
760
+ def test_sample_data_with_other_selector_list(self, selector_setup):
761
+ """Test sample_data with list of other selectors."""
762
+ selector, plotter, ax = selector_setup
763
+
764
+ # Create other selectors
765
+ other1 = Mock()
766
+ other1.sampled_indices = pd.Index([0, 1])
767
+ other2 = Mock()
768
+ other2.sampled_indices = pd.Index([2, 3])
769
+
770
+ # Add corner
771
+ selector._add_corners((0, 10, 0, 10))
772
+
773
+ selector.sample_data(
774
+ n=3, random_state=42, other_SelectFromPlot2D=[other1, other2]
775
+ )
776
+
777
+ # Should complete without error
778
+ assert hasattr(selector, "_sampled_indices")
779
+
780
+ def test_sample_data_insufficient_samples(self, selector_setup):
781
+ """Test sample_data when not enough samples available without replacement."""
782
+ selector, plotter, ax = selector_setup
783
+
784
+ # Create data with only 2 points in range but request 5
785
+ selector._plotter.data = pd.DataFrame(
786
+ {"x": [5.0, 5.1], "y": [5.0, 5.1], "z": [0.5, 0.6]}, index=[0, 1]
787
+ )
788
+
789
+ # Add corner that contains both points
790
+ selector._add_corners((4.9, 5.2, 4.9, 5.2))
791
+
792
+ # Mock logger module to capture warning
793
+ with patch(
794
+ "solarwindpy.plotting.select_data_from_figure.logging.getLogger"
795
+ ) as mock_get_logger:
796
+ mock_logger = Mock()
797
+ mock_get_logger.return_value = mock_logger
798
+
799
+ selector.sample_data(n=5, random_state=42)
800
+
801
+ # Should log warning about sampling with replacement
802
+ mock_logger.warning.assert_called()
803
+ assert "Sample failed without replacement" in str(
804
+ mock_logger.warning.call_args
805
+ )
806
+
807
+
808
+ class TestSelectFromPlot2DDisconnectAndPlotting:
809
+ """Test disconnect and plotting methods."""
810
+
811
+ @pytest.fixture
812
+ def selector_setup(self):
813
+ """Set up a SelectFromPlot2D instance for testing."""
814
+ with patch(
815
+ "solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
816
+ ):
817
+ # Create realistic test data
818
+ np.random.seed(42)
819
+ data = pd.DataFrame(
820
+ {
821
+ "x": np.random.uniform(0, 10, 100),
822
+ "y": np.random.uniform(0, 10, 100),
823
+ "z": np.random.uniform(0, 1, 100),
824
+ },
825
+ index=pd.RangeIndex(100),
826
+ )
827
+
828
+ plotter = Mock()
829
+ plotter.data = data
830
+ plotter.log = Mock()
831
+ plotter.log.x = False
832
+ plotter.log.y = False
833
+
834
+ ax = Mock()
835
+ ax.figure = Mock()
836
+ ax.figure.axes = [ax]
837
+ ax.patches = []
838
+ ax.transAxes = Mock()
839
+ ax.text = Mock(return_value=Mock())
840
+ ax.xaxis = Mock()
841
+ ax.xaxis.get_label = Mock()
842
+ ax.xaxis.get_label.return_value = Mock()
843
+ ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
844
+ ax.yaxis = Mock()
845
+ ax.yaxis.get_label = Mock()
846
+ ax.yaxis.get_label.return_value = Mock()
847
+ ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
848
+ ax.figure.canvas = Mock()
849
+ ax.figure.canvas.draw_idle = Mock()
850
+ ax.add_patch = Mock()
851
+ ax.scatter = Mock()
852
+ ax.get_xlim = Mock(return_value=(0, 10))
853
+ ax.get_ylim = Mock(return_value=(0, 10))
854
+ ax.set_xlim = Mock()
855
+ ax.set_ylim = Mock()
856
+
857
+ selector = SelectFromPlot2D(plotter, ax)
858
+
859
+ # Set up some sampled data
860
+ selector._add_corners((2, 8, 2, 8))
861
+ selector.sample_data(n=3, random_state=42)
862
+
863
+ return selector, plotter, ax
864
+
865
+ def test_disconnect(self, selector_setup):
866
+ """Test disconnect method."""
867
+ selector, plotter, ax = selector_setup
868
+
869
+ # Mock selector disconnect
870
+ mock_selector = Mock()
871
+ mock_selector.disconnect_events = Mock()
872
+ selector._selector = mock_selector
873
+
874
+ # Mock text object
875
+ mock_text = Mock()
876
+ selector._text = mock_text
877
+
878
+ selector.disconnect()
879
+
880
+ # Should call all required methods
881
+ mock_selector.disconnect_events.assert_called_once()
882
+ mock_text.set_text.assert_called()
883
+
884
+ def test_disconnect_with_other_selector(self, selector_setup):
885
+ """Test disconnect with other SelectFromPlot2D."""
886
+ selector, plotter, ax = selector_setup
887
+
888
+ # Create other selector
889
+ other_selector = Mock()
890
+ other_selector.sampled_indices = pd.Index([10, 11, 12])
891
+
892
+ # Mock selector disconnect
893
+ mock_selector = Mock()
894
+ mock_selector.disconnect_events = Mock()
895
+ selector._selector = mock_selector
896
+
897
+ # Mock text object
898
+ mock_text = Mock()
899
+ selector._text = mock_text
900
+
901
+ selector.disconnect(other_SelectFromPlot2D=other_selector)
902
+
903
+ # Should call disconnect_events
904
+ mock_selector.disconnect_events.assert_called_once()
905
+
906
+ def test_disconnect_with_scatter_kwargs(self, selector_setup):
907
+ """Test disconnect with custom scatter kwargs."""
908
+ selector, plotter, ax = selector_setup
909
+
910
+ # Mock selector disconnect
911
+ mock_selector = Mock()
912
+ mock_selector.disconnect_events = Mock()
913
+ selector._selector = mock_selector
914
+
915
+ # Mock text object
916
+ mock_text = Mock()
917
+ selector._text = mock_text
918
+
919
+ scatter_kwargs = {"color": "red", "s": 50}
920
+ selector.disconnect(scatter_kwargs=scatter_kwargs)
921
+
922
+ # Should call disconnect_events
923
+ mock_selector.disconnect_events.assert_called_once()
924
+
925
+ def test_scatter_sample(self, selector_setup):
926
+ """Test scatter_sample method."""
927
+ selector, plotter, ax = selector_setup
928
+
929
+ selector.scatter_sample()
930
+
931
+ # Should call ax.scatter
932
+ ax.scatter.assert_called_once()
933
+
934
+ # Should preserve axis limits
935
+ ax.set_xlim.assert_called_once_with(0, 10)
936
+ ax.set_ylim.assert_called_once_with(0, 10)
937
+
938
+ def test_scatter_sample_with_log_axes(self, selector_setup):
939
+ """Test scatter_sample with logarithmic axes."""
940
+ selector, plotter, ax = selector_setup
941
+
942
+ # Set log axes
943
+ plotter.log.x = True
944
+ plotter.log.y = True
945
+
946
+ selector.scatter_sample()
947
+
948
+ # Should call ax.scatter (data will be transformed)
949
+ ax.scatter.assert_called_once()
950
+
951
+ def test_scatter_sample_custom_kwargs(self, selector_setup):
952
+ """Test scatter_sample with custom kwargs."""
953
+ selector, plotter, ax = selector_setup
954
+
955
+ custom_kwargs = {"c": "blue", "s": 100, "marker": "x"}
956
+ selector.scatter_sample(**custom_kwargs)
957
+
958
+ # Should call ax.scatter
959
+ ax.scatter.assert_called_once()
960
+ call_kwargs = ax.scatter.call_args[1]
961
+ assert call_kwargs["c"] == "blue"
962
+ assert call_kwargs["s"] == 100
963
+ assert call_kwargs["marker"] == "x"
964
+
965
+ def test_plot_failed_samples(self, selector_setup):
966
+ """Test plot_failed_samples method."""
967
+ selector, plotter, ax = selector_setup
968
+
969
+ # Add some failed samples
970
+ selector._failed_samples = [(100, 110, 100, 110), (120, 130, 120, 130)]
971
+
972
+ selector.plot_failed_samples()
973
+
974
+ # Should add patches for failed samples
975
+ assert ax.add_patch.call_count == 2
976
+ ax.figure.canvas.draw_idle.assert_called_once()
977
+
978
+ def test_plot_failed_samples_empty(self, selector_setup):
979
+ """Test plot_failed_samples with no failed samples."""
980
+ selector, plotter, ax = selector_setup
981
+
982
+ # No failed samples
983
+ selector._failed_samples = []
984
+
985
+ selector.plot_failed_samples()
986
+
987
+ # Should still call draw_idle but no patches added
988
+ ax.figure.canvas.draw_idle.assert_called_once()
989
+ # ax.add_patch should not be called
990
+ ax.add_patch.assert_not_called()
991
+
992
+
993
+ class TestSelectFromPlot2DDateAxes:
994
+ """Test date axes functionality."""
995
+
996
+ @pytest.fixture
997
+ def selector_setup(self):
998
+ """Set up a SelectFromPlot2D instance for testing."""
999
+ with patch(
1000
+ "solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
1001
+ ):
1002
+ plotter = Mock()
1003
+ plotter.data = pd.DataFrame(
1004
+ {
1005
+ "x": np.random.randn(100),
1006
+ "y": np.random.randn(100),
1007
+ "z": np.random.randn(100),
1008
+ }
1009
+ )
1010
+ plotter.log = Mock()
1011
+ plotter.log.x = False
1012
+ plotter.log.y = False
1013
+
1014
+ ax = Mock()
1015
+ ax.figure = Mock()
1016
+ ax.figure.axes = [ax]
1017
+ ax.patches = []
1018
+ ax.transAxes = Mock()
1019
+ ax.text = Mock(return_value=Mock())
1020
+ ax.xaxis = Mock()
1021
+ ax.xaxis.get_label = Mock()
1022
+ ax.xaxis.get_label.return_value = Mock()
1023
+ ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
1024
+ ax.yaxis = Mock()
1025
+ ax.yaxis.get_label = Mock()
1026
+ ax.yaxis.get_label.return_value = Mock()
1027
+ ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
1028
+ ax.figure.canvas = Mock()
1029
+ ax.figure.canvas.draw_idle = Mock()
1030
+
1031
+ selector = SelectFromPlot2D(plotter, ax)
1032
+ return selector, plotter, ax
1033
+
1034
+ def test_set_date_axes(self, selector_setup):
1035
+ """Test set_date_axes method."""
1036
+ selector, plotter, ax = selector_setup
1037
+
1038
+ selector.set_date_axes(True, False)
1039
+
1040
+ assert selector.date_axes.x is True
1041
+ assert selector.date_axes.y is False
1042
+
1043
+ selector.set_date_axes(False, True)
1044
+
1045
+ assert selector.date_axes.x is False
1046
+ assert selector.date_axes.y is True
1047
+
1048
+
1049
+ class TestSelectFromPlot2DEdgeCases:
1050
+ """Test edge cases and error handling."""
1051
+
1052
+ @pytest.fixture
1053
+ def selector_setup(self):
1054
+ """Set up a SelectFromPlot2D instance for testing."""
1055
+ with patch(
1056
+ "solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector"
1057
+ ):
1058
+ plotter = Mock()
1059
+ plotter.data = pd.DataFrame(
1060
+ {
1061
+ "x": np.random.randn(100),
1062
+ "y": np.random.randn(100),
1063
+ "z": np.random.randn(100),
1064
+ }
1065
+ )
1066
+ plotter.log = Mock()
1067
+ plotter.log.x = False
1068
+ plotter.log.y = False
1069
+
1070
+ ax = Mock()
1071
+ ax.figure = Mock()
1072
+ ax.figure.axes = [ax]
1073
+ ax.patches = []
1074
+ ax.transAxes = Mock()
1075
+ ax.text = Mock(return_value=Mock())
1076
+ ax.xaxis = Mock()
1077
+ ax.xaxis.get_label = Mock()
1078
+ ax.xaxis.get_label.return_value = Mock()
1079
+ ax.xaxis.get_label.return_value.get_text = Mock(return_value="x-axis")
1080
+ ax.yaxis = Mock()
1081
+ ax.yaxis.get_label = Mock()
1082
+ ax.yaxis.get_label.return_value = Mock()
1083
+ ax.yaxis.get_label.return_value.get_text = Mock(return_value="y-axis")
1084
+ ax.figure.canvas = Mock()
1085
+ ax.figure.canvas.draw_idle = Mock()
1086
+
1087
+ selector = SelectFromPlot2D(plotter, ax)
1088
+ return selector, plotter, ax
1089
+
1090
+ def test_sample_data_value_error_reraise(self, selector_setup):
1091
+ """Test that ValueError other than sample size is re-raised."""
1092
+ selector, plotter, ax = selector_setup
1093
+
1094
+ # Mock pandas Series.sample to raise different ValueError
1095
+ with patch("pandas.Series.sample") as mock_sample:
1096
+ mock_sample.side_effect = ValueError("Different error message")
1097
+
1098
+ selector._add_corners((0, 10, 0, 10))
1099
+
1100
+ with pytest.raises(ValueError, match="Different error message"):
1101
+ selector.sample_data(n=3, random_state=42)
1102
+
1103
+ def test_sample_data_missing_already_selected(self, selector_setup):
1104
+ """Test sample_data when already_selected indices are missing."""
1105
+ selector, plotter, ax = selector_setup
1106
+
1107
+ # Create other selector with indices not in current data
1108
+ other_selector = Mock()
1109
+ other_selector.sampled_indices = pd.Index(
1110
+ [200, 201, 202]
1111
+ ) # Outside current data range
1112
+
1113
+ # Add corner
1114
+ selector._add_corners((0, 10, 0, 10))
1115
+
1116
+ # Mock logger module to capture warning
1117
+ with patch(
1118
+ "solarwindpy.plotting.select_data_from_figure.logging.getLogger"
1119
+ ) as mock_get_logger:
1120
+ mock_logger = Mock()
1121
+ mock_get_logger.return_value = mock_logger
1122
+
1123
+ selector.sample_data(
1124
+ n=3, random_state=42, other_SelectFromPlot2D=other_selector
1125
+ )
1126
+
1127
+ # Should log warning about missing indices
1128
+ mock_logger.warning.assert_called()
1129
+ assert "None of `already_selected` found" in str(
1130
+ mock_logger.warning.call_args
1131
+ )
1132
+
1133
+ def test_sample_data_other_selector_no_sampled_indices(self, selector_setup):
1134
+ """Test sample_data with other selector that has no sampled_indices
1135
+ attribute."""
1136
+ selector, plotter, ax = selector_setup
1137
+
1138
+ # Create other selector without sampled_indices
1139
+ other_selector = Mock()
1140
+ del other_selector.sampled_indices # Remove attribute
1141
+
1142
+ # Add corner
1143
+ selector._add_corners((0, 10, 0, 10))
1144
+
1145
+ # Should handle gracefully
1146
+ selector.sample_data(
1147
+ n=3, random_state=42, other_SelectFromPlot2D=other_selector
1148
+ )
1149
+
1150
+ # Should complete without error
1151
+ assert hasattr(selector, "_sampled_indices")
1152
+
1153
+
1154
+ class TestSelectFromPlot2DIntegration:
1155
+ """Integration tests for SelectFromPlot2D."""
1156
+
1157
+ @patch("solarwindpy.plotting.select_data_from_figure.mpl.widgets.RectangleSelector")
1158
+ def test_full_workflow(self, mock_rect_selector):
1159
+ """Test complete selection workflow."""
1160
+ # Set up realistic scenario
1161
+ np.random.seed(42)
1162
+ data = pd.DataFrame(
1163
+ {
1164
+ "x": np.random.uniform(0, 10, 50),
1165
+ "y": np.random.uniform(0, 10, 50),
1166
+ "z": np.random.uniform(0, 1, 50),
1167
+ },
1168
+ index=pd.RangeIndex(50),
1169
+ )
1170
+
1171
+ plotter = Mock()
1172
+ plotter.data = data
1173
+ plotter.log = Mock()
1174
+ plotter.log.x = False
1175
+ plotter.log.y = False
1176
+
1177
+ # Create figure and axis
1178
+ fig, ax = plt.subplots()
1179
+
1180
+ # Mock selector
1181
+ mock_selector = Mock()
1182
+ mock_selector.extents = (2, 8, 2, 8)
1183
+ mock_selector._rect_bbox = [2, 2, 6, 6]
1184
+ mock_selector.disconnect_events = Mock()
1185
+ mock_rect_selector.return_value = mock_selector
1186
+
1187
+ # Create selector
1188
+ selector = SelectFromPlot2D(plotter, ax)
1189
+
1190
+ # Simulate selection
1191
+ selector.onselect("press", "release")
1192
+
1193
+ # Should have added corner
1194
+ assert len(selector.corners) == 1
1195
+
1196
+ # Sample data
1197
+ selector.sample_data(n=3, random_state=42)
1198
+
1199
+ # Should have sampled indices
1200
+ assert hasattr(selector, "_sampled_indices")
1201
+ assert len(selector._sampled_indices) > 0
1202
+
1203
+ # Disconnect
1204
+ selector.disconnect()
1205
+
1206
+ # Should have called disconnect_events
1207
+ mock_selector.disconnect_events.assert_called_once()
1208
+
1209
+ plt.close(fig)