visualdynamics 0.1.0a74__tar.gz → 0.1.0a76__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. {visualdynamics-0.1.0a74/src/visualdynamics.egg-info → visualdynamics-0.1.0a76}/PKG-INFO +3 -2
  2. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/pyproject.toml +13 -2
  3. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/__init__.py +1 -1
  4. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/compatibility.py +2 -2
  5. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/author.py +22 -11
  6. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/channel_table.py +44 -1
  7. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/classical_pulse.py +11 -25
  8. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/compliance.py +197 -31
  9. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/data.py +63 -17
  10. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/drive_prediction.py +131 -114
  11. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/half_power.py +9 -2
  12. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/kurtosis.py +10 -2
  13. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/matrix_checks.py +55 -28
  14. visualdynamics-0.1.0a76/src/visualdynamics/core/matrix_comparison.py +1181 -0
  15. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/modal_response.py +6 -52
  16. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/overload.py +13 -0
  17. visualdynamics-0.1.0a76/src/visualdynamics/core/prediction.py +998 -0
  18. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/replication_segments.py +5 -5
  19. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/report.py +175 -48
  20. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/resample.py +2 -1
  21. visualdynamics-0.1.0a76/src/visualdynamics/core/sensor_catalog.py +166 -0
  22. visualdynamics-0.1.0a76/src/visualdynamics/core/sensors.py +242 -0
  23. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shock_synthesis.py +3 -8
  24. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sine_distortion.py +5 -0
  25. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sine_tracking.py +5 -0
  26. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/solids.py +65 -3
  27. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/srs.py +41 -22
  28. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/stroke.py +20 -5
  29. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sweep_rate.py +1 -11
  30. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/time_statistics.py +3 -3
  31. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/transform.py +3 -2
  32. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/icons.py +36 -1
  33. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/main_window.py +484 -37
  34. visualdynamics-0.1.0a76/src/visualdynamics/gui/matrix_comparison_panel.py +125 -0
  35. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/modal_response_panel.py +3 -3
  36. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/object_tables.py +29 -2
  37. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/opengl.py +54 -1
  38. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/panes.py +50 -7
  39. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/report_editor.py +55 -2
  40. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/resample_panel.py +15 -5
  41. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/sine_panel.py +1 -8
  42. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/tables.py +8 -1
  43. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/native.py +73 -5
  44. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/__init__.py +71 -0
  45. visualdynamics-0.1.0a76/src/visualdynamics/plot/matrix_comparison.py +333 -0
  46. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/mixed_mode.py +3 -8
  47. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/project.py +310 -34
  48. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/report/__init__.py +106 -14
  49. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/report/features.py +10 -7
  50. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/report/plot_conventions.py +25 -4
  51. visualdynamics-0.1.0a76/src/visualdynamics/report/prediction.py +291 -0
  52. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/report/skeleton.py +15 -6
  53. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/animate.py +83 -12
  54. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/geometry.py +15 -6
  55. visualdynamics-0.1.0a76/src/visualdynamics/viz/sensors.py +413 -0
  56. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76/src/visualdynamics.egg-info}/PKG-INFO +3 -2
  57. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics.egg-info/SOURCES.txt +8 -0
  58. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics.egg-info/requires.txt +1 -1
  59. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/LICENSE +0 -0
  60. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/README.md +0 -0
  61. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/setup.cfg +0 -0
  62. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/__main__.py +0 -0
  63. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/__init__.py +0 -0
  64. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/amplitude_spectrum.py +0 -0
  65. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/averaging.py +0 -0
  66. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/breakpoints.py +0 -0
  67. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/calibration.py +0 -0
  68. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/chance.py +0 -0
  69. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/channel_health.py +0 -0
  70. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/correlate.py +0 -0
  71. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/detect.py +0 -0
  72. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/dwell.py +0 -0
  73. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/entities.py +0 -0
  74. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/fem.py +0 -0
  75. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/filters.py +0 -0
  76. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/geometry.py +0 -0
  77. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/hexmesh.py +0 -0
  78. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/instrumentation.py +0 -0
  79. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/matches.py +0 -0
  80. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/mates.py +0 -0
  81. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/merge.py +0 -0
  82. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/mesh.py +0 -0
  83. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/mixed_mode.py +0 -0
  84. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/modal_fit.py +0 -0
  85. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/octave.py +0 -0
  86. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/photos.py +0 -0
  87. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/progress.py +0 -0
  88. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/replication.py +0 -0
  89. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/replication_fit.py +0 -0
  90. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/report_caption.py +0 -0
  91. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/rigid.py +0 -0
  92. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/rms_check.py +0 -0
  93. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shaker.py +0 -0
  94. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shapes.py +0 -0
  95. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shells.py +0 -0
  96. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shock_check.py +0 -0
  97. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shocks.py +0 -0
  98. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/signals.py +0 -0
  99. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/signature.py +0 -0
  100. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sine.py +0 -0
  101. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sine_frequency.py +0 -0
  102. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sine_profile.py +0 -0
  103. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/snr.py +0 -0
  104. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sparse_solve.py +0 -0
  105. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/tables.py +0 -0
  106. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/tetmesh.py +0 -0
  107. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/tone_readout.py +0 -0
  108. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/transmissibility.py +0 -0
  109. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/truncate.py +0 -0
  110. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/unit_choices.py +0 -0
  111. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/validate.py +0 -0
  112. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/core/wavelet.py +0 -0
  113. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/decimate.py +0 -0
  114. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/deform.py +0 -0
  115. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/__init__.py +0 -0
  116. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/barc.py +0 -0
  117. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/drone.py +0 -0
  118. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/frame_wing.py +0 -0
  119. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/plate.py +0 -0
  120. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/shapes.py +0 -0
  121. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/__init__.py +0 -0
  122. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/amplitude_panel.py +0 -0
  123. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/app_icon.py +0 -0
  124. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/ask.py +0 -0
  125. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/author_panel.py +0 -0
  126. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/averaging_panel.py +0 -0
  127. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/chance_panel.py +0 -0
  128. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/confidence_panel.py +0 -0
  129. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/console.py +0 -0
  130. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/disclaimer.py +0 -0
  131. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/distortion_panel.py +0 -0
  132. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/drawer_tab.py +0 -0
  133. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/drive_prediction_panel.py +0 -0
  134. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/dwell_panel.py +0 -0
  135. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/editors.py +0 -0
  136. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/filter_panel.py +0 -0
  137. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/frequency_panel.py +0 -0
  138. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/glyphs.py +0 -0
  139. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/icons/drawer-tab-shape.svg +0 -0
  140. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/icons/drawer-tab.svg +0 -0
  141. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/instrumentation_panel.py +0 -0
  142. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/levels_readout.py +0 -0
  143. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/mate_panel.py +0 -0
  144. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/matrix_check_panel.py +0 -0
  145. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/merge_panel.py +0 -0
  146. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/mesh_panel.py +0 -0
  147. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/mesh_solids_panel.py +0 -0
  148. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/message_bar.py +0 -0
  149. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/mixed_panel.py +0 -0
  150. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/modes_panel.py +0 -0
  151. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/movie.py +0 -0
  152. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/octave_panel.py +0 -0
  153. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/overload_panel.py +0 -0
  154. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/pattern_panel.py +0 -0
  155. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/preferences.py +0 -0
  156. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/profile_panel.py +0 -0
  157. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/project_tree.py +0 -0
  158. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/pulse_panel.py +0 -0
  159. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/record_grid.py +0 -0
  160. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/replication_fit_panel.py +0 -0
  161. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/report_features_panel.py +0 -0
  162. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/rms_check_panel.py +0 -0
  163. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/run_reports.py +0 -0
  164. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/segments_panel.py +0 -0
  165. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/settings_panel.py +0 -0
  166. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/shock_panel.py +0 -0
  167. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/signal_panel.py +0 -0
  168. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/signature_panel.py +0 -0
  169. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/srs_tolerance_panel.py +0 -0
  170. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/stage_drag.py +0 -0
  171. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/statistics_panel.py +0 -0
  172. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/stream_window.py +0 -0
  173. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/stroke_panel.py +0 -0
  174. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/sweep_rate_panel.py +0 -0
  175. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/text_import.py +0 -0
  176. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/tone_panel.py +0 -0
  177. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/toolbars.py +0 -0
  178. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/tooltips.py +0 -0
  179. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/transmissibility_panel.py +0 -0
  180. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/truncate_panel.py +0 -0
  181. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/updater.py +0 -0
  182. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/wavelet_panel.py +0 -0
  183. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/windows.py +0 -0
  184. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/__init__.py +0 -0
  185. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/adf.py +0 -0
  186. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/delimited.py +0 -0
  187. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf.py +0 -0
  188. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_objects.py +0 -0
  189. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/LICENSE +0 -0
  190. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/activity_result.txt +0 -0
  191. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/channel_table.txt +0 -0
  192. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/data.txt +0 -0
  193. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/data_physics_random_control_parameters.txt +0 -0
  194. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/data_physics_shock_response_synthesis_control_parameters.txt +0 -0
  195. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/data_physics_sine_control_parameters.txt +0 -0
  196. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/geometry.txt +0 -0
  197. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/global_analysis_attributes.txt +0 -0
  198. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/global_test_attributes.txt +0 -0
  199. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/matrix.txt +0 -0
  200. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/mechanical_control_parameters.txt +0 -0
  201. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/modal_test_parameters.txt +0 -0
  202. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/mode.txt +0 -0
  203. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/parameter_set.txt +0 -0
  204. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/point_cloud.txt +0 -0
  205. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/rattlesnake_random_control_parameters.txt +0 -0
  206. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/rattlesnake_sysid_parameters.txt +0 -0
  207. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/rattlesnake_transient_control_parameters.txt +0 -0
  208. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/response_spectrum.txt +0 -0
  209. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/scalar.txt +0 -0
  210. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/unknown.txt +0 -0
  211. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/vector.txt +0 -0
  212. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/vibration_channel_table.txt +0 -0
  213. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/excel.py +0 -0
  214. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/exodus.py +0 -0
  215. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/exporters.py +0 -0
  216. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/femap.py +0 -0
  217. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/matlab.py +0 -0
  218. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/nastran.py +0 -0
  219. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/notes.py +0 -0
  220. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/photo_files.py +0 -0
  221. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/punch.py +0 -0
  222. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/rattlesnake.py +0 -0
  223. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/rattlesnake_sine.py +0 -0
  224. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/rattlesnake_specification.py +0 -0
  225. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/report_template.py +0 -0
  226. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/sdynpy_data.py +0 -0
  227. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/sdynpy_npz.py +0 -0
  228. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/sdynpy_shapes.py +0 -0
  229. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/sep005.py +0 -0
  230. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/sniffing.py +0 -0
  231. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/stated_units.py +0 -0
  232. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/step.py +0 -0
  233. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/stl.py +0 -0
  234. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/threemf.py +0 -0
  235. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/io/unv.py +0 -0
  236. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/names.py +0 -0
  237. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/amplitude_spectrum.py +0 -0
  238. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/averaging.py +0 -0
  239. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/bands.py +0 -0
  240. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/bars.py +0 -0
  241. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/breakpoints.py +0 -0
  242. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/chance.py +0 -0
  243. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/classical_pulse.py +0 -0
  244. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/drive_prediction.py +0 -0
  245. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/dwell.py +0 -0
  246. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/filtering.py +0 -0
  247. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/half_power.py +0 -0
  248. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/log_axis.py +0 -0
  249. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/matrix_checks.py +0 -0
  250. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/overload.py +0 -0
  251. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/replication_fit.py +0 -0
  252. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/replication_segments.py +0 -0
  253. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/report_caption.py +0 -0
  254. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/resample.py +0 -0
  255. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/rms_check.py +0 -0
  256. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/scalogram.py +0 -0
  257. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/shock_check.py +0 -0
  258. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/shock_synthesis.py +0 -0
  259. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/shocks.py +0 -0
  260. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/signature.py +0 -0
  261. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/sine_distortion.py +0 -0
  262. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/sine_frequency.py +0 -0
  263. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/sine_profile.py +0 -0
  264. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/sine_tracking.py +0 -0
  265. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/srs_tolerance.py +0 -0
  266. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/stroke.py +0 -0
  267. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/sweep_rate.py +0 -0
  268. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/text_import.py +0 -0
  269. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/time_statistics.py +0 -0
  270. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/tone_readout.py +0 -0
  271. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/tracking_filter.py +0 -0
  272. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/transmissibility.py +0 -0
  273. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/truncation.py +0 -0
  274. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/py.typed +0 -0
  275. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/report/calibration.py +0 -0
  276. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/report/markdown.py +0 -0
  277. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/report/page.py +0 -0
  278. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/rotate.py +0 -0
  279. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/theme.py +0 -0
  280. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/units.py +0 -0
  281. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/update.py +0 -0
  282. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/__init__.py +0 -0
  283. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/_quiet.py +0 -0
  284. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/banded.py +0 -0
  285. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/mac_bars.py +0 -0
  286. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/marks.py +0 -0
  287. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/paired.py +0 -0
  288. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/pick.py +0 -0
  289. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/rigid.py +0 -0
  290. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/scalogram.py +0 -0
  291. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/sinespec.py +0 -0
  292. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/waterfall.py +0 -0
  293. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics.egg-info/dependency_links.txt +0 -0
  294. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics.egg-info/entry_points.txt +0 -0
  295. {visualdynamics-0.1.0a74 → visualdynamics-0.1.0a76}/src/visualdynamics.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: visualdynamics
3
- Version: 0.1.0a74
3
+ Version: 0.1.0a76
4
4
  Summary: Structural Dynamics Visualization: a units-aware structural dynamics analysis toolset
5
5
  Author-email: Brandon Zwink <bzwink@gmail.com>
6
6
  License-Expression: MIT
@@ -16,6 +16,7 @@ Classifier: Topic :: Scientific/Engineering :: Visualization
16
16
  Classifier: Programming Language :: Python :: 3
17
17
  Classifier: Programming Language :: Python :: 3.12
18
18
  Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
19
20
  Classifier: Operating System :: OS Independent
20
21
  Classifier: Environment :: X11 Applications :: Qt
21
22
  Requires-Python: >=3.12
@@ -29,7 +30,7 @@ Requires-Dist: h5py
29
30
  Requires-Dist: netCDF4
30
31
  Requires-Dist: pint
31
32
  Requires-Dist: pyvista
32
- Requires-Dist: pyvistaqt
33
+ Requires-Dist: pyvistaqt>=0.13
33
34
  Requires-Dist: vtk
34
35
  Requires-Dist: PySide6
35
36
  Requires-Dist: shiboken6
@@ -33,6 +33,7 @@ classifiers = [
33
33
  "Programming Language :: Python :: 3",
34
34
  "Programming Language :: Python :: 3.12",
35
35
  "Programming Language :: Python :: 3.13",
36
+ "Programming Language :: Python :: 3.14",
36
37
  "Operating System :: OS Independent",
37
38
  "Environment :: X11 Applications :: Qt",
38
39
  ]
@@ -70,7 +71,13 @@ dependencies = [
70
71
  "netCDF4",
71
72
  "pint",
72
73
  "pyvista",
73
- "pyvistaqt",
74
+ # 0.13 made the 3-D view a QOpenGLWidget that Qt composes with the
75
+ # rest of the window, and the window is built for that: Qt Quick
76
+ # draws with OpenGL and every context has the views' format from
77
+ # the start (gui.opengl.one_graphics_system). A Mac still on 0.11.4
78
+ # crashed generating a report under that setup (2026-10-27); the
79
+ # floor has pip upgrade such an environment rather than leave it.
80
+ "pyvistaqt>=0.13",
74
81
  # BSD-3-Clause. pyvista brings it, but this package imports
75
82
  # `vtkmodules` directly — the stage's marks, the drag picking, the
76
83
  # scene's axes — and what is imported is declared here rather than
@@ -189,10 +196,14 @@ line-length = 100
189
196
  # — forty-six seconds, which was most of the suite's wall clock and none
190
197
  # of it about the package. The rules that *are* about the package are
191
198
  # stated on a three-quad strip in test_fem.py instead. Run the demo's own
192
- # suite with `pytest -m slow` (CI does, in a step of its own).
199
+ # suite with `pytest -m slow` (CI does, in a job of its own).
200
+ # `guide` marks the slow tests that are minutes of meshing each, the
201
+ # guide's tetrahedra table: CI runs them on main and weekly, not on a
202
+ # pull request (2026-10-28), and `-m slow` here still runs them.
193
203
  addopts = '-n auto -m "not slow"'
194
204
  markers = [
195
205
  "slow: the demonstration model, built for the website and the docs — run on demand rather than on every edit",
206
+ "guide: the guide's mesh table, minutes of meshing a shape — checked on main and weekly, not on every pull request",
196
207
  ]
197
208
  filterwarnings = [
198
209
  # netCDF4's published wheel is built against an older numpy C API; the
@@ -97,7 +97,7 @@ from .units import (
97
97
  #: alpha, said in the version itself so the dialog, the About text, the
98
98
  #: installers and latest.json all say the same thing, and the first
99
99
  #: non-alpha release is offered as an upgrade (Brandon, 2026-09-11)
100
- __version__ = '0.1.0a74'
100
+ __version__ = '0.1.0a76'
101
101
 
102
102
 
103
103
  def launch_gui(*paths: str, in_process: bool | None = None) -> Any:
@@ -22,8 +22,8 @@ A data object whose DOFs are all modal coordinates (`M1` ... `Mn`, see
22
22
  the one it was transformed or computed through, and fits wherever there
23
23
  is a set with at least as many modes as it names: a set in its object
24
24
  group when it is in one, any set in the project otherwise. A time
25
- history can hold both kinds of record, forces with the modal responses
26
- computed from them appended: its physical records answer to the
25
+ history can hold both kinds of record, forces merged with the modal
26
+ responses computed from them: its physical records answer to the
27
27
  geometry and its modal records to the shape sets, each by its own rule.
28
28
 
29
29
  Not checked here:
@@ -1907,11 +1907,19 @@ def read_at_lines(old_f: np.ndarray, values: np.ndarray,
1907
1907
  """A specification record read at new lines.
1908
1908
 
1909
1909
  At its own lines the record keeps its own values. Between them it
1910
- is interpolated the way a specification is read, the magnitude
1911
- log-log and the phase linearly, so a line the sheet added does not
1912
- change what a channel the sheet does not hold means. Outside its
1913
- range it takes its end values.
1910
+ is interpolated the way a specification is read, so a line the
1911
+ sheet added does not change what a channel the sheet does not hold
1912
+ means: a cross term by `compliance.cross_term_at` (the magnitude
1913
+ log-log between points above zero, straight through a zero, the
1914
+ phase linearly), a real record by its magnitude log-log over the
1915
+ points above zero. Outside its range it takes its end values.
1914
1916
  """
1917
+ # A cross term was read here by its own rule, bridging a zero at a
1918
+ # breakpoint as a gap, and the drive prediction read the sheet's
1919
+ # rule while the matrix comparison read the specification's: the
1920
+ # same cross term gave two coherences (2026-10-28). One reader now.
1921
+ from .compliance import cross_term_at
1922
+
1915
1923
  values = np.asarray(values)
1916
1924
  exact = np.isin(lines, old_f)
1917
1925
  if exact.all():
@@ -1923,16 +1931,19 @@ def read_at_lines(old_f: np.ndarray, values: np.ndarray,
1923
1931
  out = np.full(len(lines), np.nan, dtype=values.dtype)
1924
1932
  out[exact] = values[np.searchsorted(old_f, lines[exact])]
1925
1933
  new = ~exact
1934
+ if np.iscomplexobj(values):
1935
+ written = np.isfinite(values)
1936
+ read = cross_term_at(old_f[written], values[written], lines[new])
1937
+ # the end values outside the range, as the real branch holds them
1938
+ below, above = lines[new] < old_f[0], lines[new] > old_f[-1]
1939
+ read[below], read[above] = values[0], values[-1]
1940
+ out[new] = read
1941
+ return out
1926
1942
  if positive.sum() >= 2:
1927
1943
  size = np.exp(np.interp(log_new[new], log_old[positive],
1928
1944
  np.log(magnitude[positive])))
1929
1945
  else:
1930
1946
  size = np.interp(lines[new], old_f, np.nan_to_num(magnitude))
1931
- if np.iscomplexobj(values):
1932
- phase = np.interp(lines[new], old_f,
1933
- np.unwrap(np.angle(values)))
1934
- out[new] = size * np.exp(1j * phase)
1935
- else:
1936
- out[new] = size * np.sign(np.interp(lines[new], old_f,
1937
- np.nan_to_num(values)))
1947
+ out[new] = size * np.sign(np.interp(lines[new], old_f,
1948
+ np.nan_to_num(values)))
1938
1949
  return out
@@ -484,7 +484,13 @@ class ChannelTable:
484
484
 
485
485
  A unit that is not of the channel's declared type is refused;
486
486
  change the type first. Changing the type clears a unit of another
487
- kind.
487
+ kind. A model in the sensor catalog (`core.sensor_catalog`) fills
488
+ a blank Make with its maker; a make already written is kept. A
489
+ triaxial accelerometer's model or serial number is written once:
490
+ the other acceleration rows at its node, its other legs, get its
491
+ Make, Model and Serial Number where theirs are blank (and their
492
+ model is blank or the same). A row of another type at that node,
493
+ a force gauge, is never filled.
488
494
 
489
495
  Parameters
490
496
  ----------
@@ -537,6 +543,43 @@ class ChannelTable:
537
543
  from ..units import dimension_of
538
544
  if dimension_of(unit) != coerced:
539
545
  self.frame.loc[row, 'unit'] = ''
546
+ if name == 'model' and coerced:
547
+ # picking a model names its maker too (Brandon, 2026-10-26)
548
+ from .sensor_catalog import lookup
549
+
550
+ entry = lookup(str(coerced))
551
+ if entry is not None and not str(self.frame.loc[row, 'make']).strip():
552
+ self.frame.loc[row, 'make'] = entry.make
553
+ if name in ('model', 'serial_number') and coerced:
554
+ self._fill_triax(row)
555
+
556
+ def _fill_triax(self, row: int) -> None:
557
+ """Copy a triax row's Make, Model and Serial Number into the blank
558
+ cells of its other legs: the other acceleration rows at its node
559
+ whose model is blank or the same. Only a model the catalog knows
560
+ is a triax is spread, and only into acceleration rows."""
561
+ # Brandon, 2026-10-27: a triax's identity typed three times was
562
+ # one too many; legs wired one or two at a time are why the cells
563
+ # are filled rather than merged, and a force gauge at the node is
564
+ # why the type has to match.
565
+ from .sensor_catalog import lookup
566
+
567
+ frame = self.frame
568
+ entry = lookup(str(frame.loc[row, 'model']))
569
+ node = str(frame.loc[row, 'node']).strip()
570
+ if (entry is None or entry.kind != 'triax' or not node
571
+ or str(frame.loc[row, 'channel_type']) != 'acceleration'):
572
+ return
573
+ for other in range(self.num_channels):
574
+ if (other == row or str(frame.loc[other, 'node']).strip() != node
575
+ or str(frame.loc[other, 'channel_type']) != 'acceleration'):
576
+ continue
577
+ model = str(frame.loc[other, 'model']).strip()
578
+ if model and lookup(model) is not entry:
579
+ continue
580
+ for column in ('make', 'model', 'serial_number'):
581
+ if not str(frame.loc[other, column]).strip():
582
+ frame.loc[other, column] = frame.loc[row, column]
540
583
 
541
584
  def _agrees_with_type(self, row: int, unit: str) -> None:
542
585
  """A declared type and a unit have to describe one channel."""
@@ -906,28 +906,14 @@ def check_pulse(history: TimeHistory, pulse: ClassicalPulse,
906
906
  def pulse_channel(history: TimeHistory,
907
907
  channel: int | str | None = None) -> int:
908
908
  """The row of `history` a pulse is read from: `channel` by index or
909
- DOF, or the first acceleration channel; refused when it is not an
910
- acceleration, since a pulse's velocity change is the area under an
911
- acceleration and nothing else."""
912
- rows = range(history.num_records)
913
- if channel is None:
914
- row = next((i for i in rows
915
- if history.known_dim(i) == 'acceleration'), None)
916
- if row is None:
917
- raise ValueError('a pulse is read from an acceleration, and '
918
- 'this record holds none')
919
- return row
920
- if isinstance(channel, str):
921
- dofs = [str(dof) for dof in history.response_dof]
922
- if channel not in dofs:
923
- raise ValueError(f'the record has no channel {channel}')
924
- row = dofs.index(channel)
925
- else:
926
- row = int(channel)
927
- if not 0 <= row < history.num_records:
928
- raise ValueError(f'the record has {history.num_records} '
929
- f'channels; there is no channel {row}')
930
- if history.known_dim(row) != 'acceleration':
931
- raise ValueError(f'{history.response_dof[row]} is not an '
932
- 'acceleration, and a pulse is read from one')
933
- return row
909
+ DOF, or the first acceleration channel whose unit is declared
910
+ (`core.shaker.acceleration_channel`, the one rule for the stroke
911
+ and the replication fit too); refused otherwise, since a pulse's
912
+ velocity change is the area under an acceleration in a known scale
913
+ and nothing else."""
914
+ # It read a channel by `known_dim` (2026-10-28), so a record whose
915
+ # unit was only hinted was judged raw against an SI nominal: a 50 g
916
+ # half-sine stored in g passed as 5.1 g with every point inside.
917
+ from .shaker import acceleration_channel
918
+
919
+ return acceleration_channel(history, channel)
@@ -88,6 +88,116 @@ def log_interpolate(frequencies: ArrayLike, spec_frequencies: ArrayLike,
88
88
  return out
89
89
 
90
90
 
91
+ def cross_term_at(frequencies: ArrayLike, values: ArrayLike,
92
+ lines: ArrayLike) -> np.ndarray:
93
+ """A cross term written at breakpoints, read at `lines` the way a
94
+ specification is drawn: the magnitude log-log between two points
95
+ that are both above zero and straight between two that are not (a
96
+ coherence of zero, two channels stated to be independent, is a
97
+ value and not a gap), the phase linearly. Not a number outside the
98
+ points, and at lines at or below zero hertz.
99
+
100
+ Parameters
101
+ ----------
102
+ frequencies : array_like
103
+ The breakpoints' frequencies, in Hz, in any order.
104
+ values : array_like
105
+ The complex cross term at each breakpoint.
106
+ lines : array_like
107
+ Where to read it.
108
+
109
+ Returns
110
+ -------
111
+ numpy.ndarray
112
+ Complex, one value per line.
113
+ """
114
+ # Two readers of this existed, one in the matrix comparison and one
115
+ # in the drive prediction through the authoring sheet, and they
116
+ # disagreed on a zero at a breakpoint: the sheet's bridged it as a
117
+ # gap, so one specification read 0.81 coherence in the prediction
118
+ # and 0.32 in the comparison at the same line (2026-10-28). This is
119
+ # the one reader, and the zero is a statement.
120
+ frequencies = np.asarray(frequencies, dtype=float)
121
+ values = np.asarray(values)
122
+ lines = np.asarray(lines, dtype=float)
123
+ usable = np.isfinite(frequencies) & (frequencies > 0.0)
124
+ order = np.argsort(frequencies[usable])
125
+ f = frequencies[usable][order]
126
+ v = values[usable][order].astype(complex)
127
+ out = np.full(lines.shape, np.nan, dtype=complex)
128
+ if f.size < 2:
129
+ return out
130
+ inside = (lines >= f[0]) & (lines <= f[-1])
131
+ at = lines[inside]
132
+ k = np.clip(np.searchsorted(f, at, side='right') - 1, 0, f.size - 2)
133
+ m1, m2 = np.abs(v[k]), np.abs(v[k + 1])
134
+ part = (at - f[k]) / (f[k + 1] - f[k])
135
+ with np.errstate(divide='ignore', invalid='ignore'):
136
+ logs = np.exp(np.log(m1) + (np.log(m2) - np.log(m1))
137
+ * np.log(at / f[k]) / np.log(f[k + 1] / f[k]))
138
+ magnitude = np.where((m1 > 0.0) & (m2 > 0.0), logs,
139
+ m1 + (m2 - m1) * part)
140
+ phase = np.interp(at, f, np.unwrap(np.angle(v)))
141
+ out[inside] = magnitude * np.exp(1j * phase)
142
+ return out
143
+
144
+
145
+ def record_at(data: Any, record: int, lines: ArrayLike) -> np.ndarray:
146
+ """One record of a specification read at `lines`, the way it is
147
+ drawn, and not a number where it is not written.
148
+
149
+ An autospectrum is written where it is above zero, and between
150
+ breakpoints ('log_log') is read log-log with its gaps
151
+ (`log_interpolate`). A cross term is written wherever it is a
152
+ number, zero included, and is read between breakpoints by
153
+ `cross_term_at`; whether its coherence is defined is for its two
154
+ autospectra to say. On lines or bands ('bin') a record takes the
155
+ value of the bin the line falls in. The matrix comparison and the
156
+ drive prediction both read a specification here.
157
+
158
+ Parameters
159
+ ----------
160
+ data : Psd or Specification
161
+ The specification.
162
+ record : int
163
+ Which record.
164
+ lines : array_like
165
+ Where to read it, in Hz.
166
+
167
+ Returns
168
+ -------
169
+ numpy.ndarray
170
+ Real for an autospectrum, complex for a cross term.
171
+ """
172
+ from .data import autospectrum
173
+
174
+ lines = np.asarray(lines, dtype=float)
175
+ frequencies = np.asarray(data.abscissa, dtype=float)
176
+ values = np.asarray(data.ordinate)[record]
177
+ auto = autospectrum(data, record)
178
+ written = np.isfinite(frequencies) & (
179
+ data.written(record) if auto else np.isfinite(values))
180
+ if getattr(data, 'interpolation', 'bin') == 'log_log':
181
+ if auto:
182
+ return log_interpolate(lines, frequencies, np.real(values))
183
+ return cross_term_at(frequencies[written], values[written], lines)
184
+ left, right = data.bin_bounds()
185
+ order = np.argsort(left)
186
+ left, right = left[order], right[order]
187
+ out = np.full(lines.shape, np.nan, dtype=float if auto else complex)
188
+ if not left.size:
189
+ return out
190
+ # the last bin starting at or below each line, if that bin reaches it
191
+ k = np.searchsorted(left, lines, side='right') - 1
192
+ found = k >= 0
193
+ k = np.maximum(k, 0)
194
+ found &= (lines >= left[k]) & (lines < right[k])
195
+ source = order[k]
196
+ found &= written[source]
197
+ out[found] = (np.real(values) if auto else values)[source[found]]
198
+ return out
199
+
200
+
91
201
  def log_log_areas(frequencies: ArrayLike, values: ArrayLike,
92
202
  lows: ArrayLike, highs: ArrayLike) -> np.ndarray:
93
203
  """The exact area under the log-log curve through these points, over
@@ -423,6 +533,24 @@ def cells(specification: Specification, measured: DataArray,
423
533
  return out
424
534
 
425
535
 
536
+ def cell_lines(cell: dict[str, Any], left: np.ndarray,
537
+ right: np.ndarray) -> slice | None:
538
+ """The measured lines one cell of `cells` reaches, as a slice of
539
+ the bins whose edges are `left` and `right` (sorted), or None when
540
+ it reaches none.
541
+
542
+ Found by where the cell's ends fall among the bins: a bin that ends
543
+ where the cell begins shares an edge and no width, and is not in
544
+ it. `judge` marks the lines of an out cell by this, and a reading
545
+ that spreads a cell's result over its lines uses the same rule.
546
+ """
547
+ first = int(np.searchsorted(right, cell['low'] * (1.0 + 1e-12),
548
+ side='right'))
549
+ last = int(np.searchsorted(left, cell['high'] * (1.0 - 1e-12),
550
+ side='left'))
551
+ return slice(first, last) if last > first else None
552
+
553
+
426
554
  def judge(specification: Specification, measured: DataArray,
427
555
  spec_record: int = 0, measured_record: int = 0,
428
556
  limit: str | None = 'abort_upper', over: bool = True,
@@ -494,16 +622,11 @@ def judge(specification: Specification, measured: DataArray,
494
622
  lines = np.zeros(left.shape, dtype=bool)
495
623
  level = np.full(left.shape, np.nan)
496
624
  start, stop = left.copy(), right.copy()
497
- # the lines each out cell reaches, found by where the cell's ends
498
- # fall among the sorted bins: a bin that ends where the cell
499
- # begins shares an edge and no width, and is not in it
500
625
  for k in np.flatnonzero(out):
501
626
  cell = found[k]
502
- first = int(np.searchsorted(right, cell['low'] * (1.0 + 1e-12), side='right'))
503
- last = int(np.searchsorted(left, cell['high'] * (1.0 - 1e-12), side='left'))
504
- if last <= first:
627
+ inside = cell_lines(cell, left, right)
628
+ if inside is None:
505
629
  continue
506
- inside = slice(first, last)
507
630
  lines[inside] = True
508
631
  level[inside] = asked[k] / cell['width']
509
632
  start[inside] = np.maximum(left[inside], cell['low'])
@@ -816,6 +939,7 @@ def detect_scale_db(specification: Specification, measured: DataArray,
816
939
  # the same bands. `significant_band` is there because a waveform's
817
940
  # spectrum ending at 2 kHz left half a transient's bands 40 dB apart,
818
941
  # saying nothing about level.
942
+ from .data import autospectrum
819
943
  from .octave import PER_OCTAVE
820
944
 
821
945
  if comparable(specification, measured) is not None:
@@ -833,6 +957,10 @@ def detect_scale_db(specification: Specification, measured: DataArray,
833
957
  if wanted is not None and label not in wanted and str(
834
958
  specification.response_dof[spec_index]) not in wanted:
835
959
  continue
960
+ # a cross term has no level to read a scale from: its real part
961
+ # moves with its phase
962
+ if not autospectrum(specification, spec_index):
963
+ continue
836
964
  # a pair the comparison refuses has no level to read a scale from
837
965
  if units_check(specification.ordinate_dim[spec_index],
838
966
  measured.ordinate_dim[measured_index])[0] is not None:
@@ -973,6 +1101,16 @@ def exceedances(specification: Specification, measured: DataArray,
973
1101
  return marks[0], marks[1]
974
1102
 
975
1103
 
1104
+ #: why `compare` does not read a cross term. A cross spectrum is complex,
1105
+ #: and its real part is not the power of either channel: read as a
1106
+ #: level, a cross term held at its specified magnitude but 60 degrees
1107
+ #: off its phase came out 3 dB low, and one 120 degrees off had no
1108
+ #: level at all. Its coherence and phase are what is judged
1109
+ #: (`core.matrix_comparison`).
1110
+ CROSS_TERM_REFUSAL = ('a cross term is judged by its coherence and phase '
1111
+ '(the matrix comparison), not by its level')
1112
+
1113
+
976
1114
  def compare(specification: Specification, measured: DataArray,
977
1115
  spec_record: int = 0,
978
1116
  measured_record: int = 0,
@@ -992,17 +1130,20 @@ def compare(specification: Specification, measured: DataArray,
992
1130
  fraction of its bin that was judged, so a band at the top of the
993
1131
  range counts the same as one at the bottom.
994
1132
 
995
- A pair of objects that cannot be compared (`comparable`), or a pair
1133
+ A pair of objects that cannot be compared (`comparable`), a pair
996
1134
  of records of different quantities or with only one side's units
997
- declared (`units_check`), returns {'lines': 0, 'refused': reason},
998
- and one with nothing to compare returns {'lines': 0}. With neither
999
- side's units declared the pair is compared and the result carries
1000
- 'note' (`RAW_NOTE`).
1135
+ declared (`units_check`), or a specification record that is a cross
1136
+ term (`CROSS_TERM_REFUSAL`), returns {'lines': 0, 'refused':
1137
+ reason}, and one with nothing to compare returns {'lines': 0}. With
1138
+ neither side's units declared the pair is compared and the result
1139
+ carries 'note' (`RAW_NOTE`).
1001
1140
 
1002
1141
  Every number is of the scaled measurement: `scale_db` is resolved
1003
1142
  through `comparison_scale_db` unless given, and is returned so a
1004
1143
  table can say what was compared.
1005
1144
  """
1145
+ from .data import autospectrum
1146
+
1006
1147
  if scale_db is None:
1007
1148
  scale_db = comparison_scale_db(specification, measured)
1008
1149
  # the level, cell by cell: both integrated over the very same
@@ -1013,6 +1154,8 @@ def compare(specification: Specification, measured: DataArray,
1013
1154
  refused = comparable(specification, measured)
1014
1155
  if refused is not None:
1015
1156
  return {'lines': 0, 'refused': refused}
1157
+ if not autospectrum(specification, spec_record):
1158
+ return {'lines': 0, 'refused': CROSS_TERM_REFUSAL}
1016
1159
  refused, note = units_check(specification.ordinate_dim[spec_record],
1017
1160
  measured.ordinate_dim[measured_record],
1018
1161
  wanted_name='the specification')
@@ -1284,10 +1427,11 @@ def compare_all(specification: Specification, measured: DataArray,
1284
1427
  result from `compare`.
1285
1428
 
1286
1429
  A specification bounds the control channels, and a measurement
1287
- usually holds those and many more. Channels are paired by DOF, the
1288
- response and the reference where there is one (`matched_records`),
1289
- so a cross spectrum is compared with the specification's cross term
1290
- if it has one and is left out if it does not.
1430
+ usually holds those and many more. Channels are paired by DOF and
1431
+ quantity (`matched_records`). Only the specification's autospectra
1432
+ are compared here: a cross term is not a level, and its coherence
1433
+ and phase are judged by `core.matrix_comparison.compare_matrix`,
1434
+ which compares the autospectra through this same function.
1291
1435
 
1292
1436
  Rows are in the specification's order, the order a control room
1293
1437
  reads its channels in. The scale is resolved once for the whole set,
@@ -1301,24 +1445,46 @@ def compare_all(specification: Specification, measured: DataArray,
1301
1445
  'scale_db', but `channel_errors` and the charts built from it do
1302
1446
  not. Pass `scale_db=0` to compare as measured.
1303
1447
  """
1304
- # the warning is there because a whole campaign was once scored on
1305
- # shifted numbers without a word (2026-09-26)
1306
- if scale_db is None:
1307
- scale_db = comparison_scale_db(specification, measured,
1308
- spec_records, measured_records)
1309
- if scale_db and getattr(measured, 'scale_db', None) is None:
1310
- import warnings
1311
-
1312
- warnings.warn(
1313
- f'the measurement was scaled by {scale_db:+g} dB, detected '
1314
- f'from the data as a run at {-scale_db:+g} dB; pass '
1315
- 'scale_db=0 to compare it as measured, or set '
1316
- 'measured.scale_db to the level it was run at',
1317
- ScaleWarning, stacklevel=2)
1448
+ from .data import autospectrum
1449
+
1450
+ scale_db = resolved_scale_db(specification, measured, spec_records,
1451
+ measured_records, scale_db, stacklevel=3)
1452
+ # cross terms out since 2026-10-24: `compare` read one by its real
1453
+ # part, which moves with its phase
1318
1454
  return [(label, compare(specification, measured, spec_index,
1319
1455
  measured_index, scale_db=scale_db))
1320
1456
  for label, spec_index, measured_index in matched_records(
1321
- specification, measured, spec_records, measured_records)]
1457
+ specification, measured, spec_records, measured_records)
1458
+ if autospectrum(specification, spec_index)]
1459
+
1460
+
1461
+ def resolved_scale_db(specification: Specification, measured: DataArray,
1462
+ spec_records: Sequence[int] | None = None,
1463
+ measured_records: Sequence[int] | None = None,
1464
+ scale_db: float | None = None,
1465
+ stacklevel: int = 2) -> float:
1466
+ """The one scale a whole set of comparisons is made at: `scale_db`
1467
+ when given, else `comparison_scale_db`'s, with a `ScaleWarning`
1468
+ when that scale was detected from the data rather than set on the
1469
+ measurement. `compare_all` and the matrix comparison resolve their
1470
+ scale here, so both warn alike. `stacklevel` points the warning at
1471
+ the caller's caller."""
1472
+ # the warning is there because a whole campaign was once scored on
1473
+ # shifted numbers without a word (2026-09-26)
1474
+ if scale_db is not None:
1475
+ return scale_db
1476
+ scale_db = comparison_scale_db(specification, measured,
1477
+ spec_records, measured_records)
1478
+ if scale_db and getattr(measured, 'scale_db', None) is None:
1479
+ import warnings
1480
+
1481
+ warnings.warn(
1482
+ f'the measurement was scaled by {scale_db:+g} dB, detected '
1483
+ f'from the data as a run at {-scale_db:+g} dB; pass '
1484
+ 'scale_db=0 to compare it as measured, or set '
1485
+ 'measured.scale_db to the level it was run at',
1486
+ ScaleWarning, stacklevel=stacklevel)
1487
+ return scale_db
1322
1488
 
1323
1489
 
1324
1490
  #: The RMS dB deviation an SRS comparison is shaded past, either side.