visualdynamics 0.1.0a75__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.
- {visualdynamics-0.1.0a75/src/visualdynamics.egg-info → visualdynamics-0.1.0a76}/PKG-INFO +1 -1
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/__init__.py +1 -1
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/author.py +22 -11
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/classical_pulse.py +11 -25
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/compliance.py +110 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/data.py +5 -5
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/drive_prediction.py +131 -114
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/half_power.py +9 -2
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/kurtosis.py +10 -2
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/matrix_checks.py +55 -28
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/matrix_comparison.py +9 -90
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/overload.py +13 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/prediction.py +26 -3
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/replication_segments.py +5 -5
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/resample.py +2 -1
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shock_synthesis.py +3 -8
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sine_distortion.py +5 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sine_tracking.py +5 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/srs.py +41 -22
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/stroke.py +20 -5
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sweep_rate.py +1 -11
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/time_statistics.py +3 -3
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/main_window.py +114 -14
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/panes.py +5 -5
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/resample_panel.py +15 -5
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/sine_panel.py +1 -8
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/mixed_mode.py +3 -8
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/project.py +48 -1
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/report/__init__.py +62 -22
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/report/features.py +1 -1
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/report/plot_conventions.py +19 -4
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/report/prediction.py +9 -1
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/report/skeleton.py +15 -9
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76/src/visualdynamics.egg-info}/PKG-INFO +1 -1
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/LICENSE +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/README.md +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/pyproject.toml +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/setup.cfg +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/__main__.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/compatibility.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/__init__.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/amplitude_spectrum.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/averaging.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/breakpoints.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/calibration.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/chance.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/channel_health.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/channel_table.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/correlate.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/detect.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/dwell.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/entities.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/fem.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/filters.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/geometry.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/hexmesh.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/instrumentation.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/matches.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/mates.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/merge.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/mesh.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/mixed_mode.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/modal_fit.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/modal_response.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/octave.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/photos.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/progress.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/replication.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/replication_fit.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/report.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/report_caption.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/rigid.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/rms_check.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sensor_catalog.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sensors.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shaker.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shapes.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shells.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shock_check.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/shocks.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/signals.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/signature.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sine.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sine_frequency.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sine_profile.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/snr.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/solids.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/sparse_solve.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/tables.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/tetmesh.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/tone_readout.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/transform.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/transmissibility.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/truncate.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/unit_choices.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/validate.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/wavelet.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/decimate.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/deform.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/__init__.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/barc.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/drone.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/frame_wing.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/plate.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/demo/shapes.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/__init__.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/amplitude_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/app_icon.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/ask.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/author_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/averaging_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/chance_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/confidence_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/console.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/disclaimer.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/distortion_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/drawer_tab.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/drive_prediction_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/dwell_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/editors.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/filter_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/frequency_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/glyphs.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/icons/drawer-tab-shape.svg +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/icons/drawer-tab.svg +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/icons.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/instrumentation_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/levels_readout.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/mate_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/matrix_check_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/matrix_comparison_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/merge_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/mesh_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/mesh_solids_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/message_bar.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/mixed_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/modal_response_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/modes_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/movie.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/object_tables.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/octave_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/opengl.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/overload_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/pattern_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/preferences.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/profile_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/project_tree.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/pulse_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/record_grid.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/replication_fit_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/report_editor.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/report_features_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/rms_check_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/run_reports.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/segments_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/settings_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/shock_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/signal_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/signature_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/srs_tolerance_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/stage_drag.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/statistics_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/stream_window.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/stroke_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/sweep_rate_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/tables.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/text_import.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/tone_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/toolbars.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/tooltips.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/transmissibility_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/truncate_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/updater.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/wavelet_panel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/gui/windows.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/__init__.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/adf.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/delimited.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_objects.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/LICENSE +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/activity_result.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/channel_table.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/data.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/data_physics_random_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/data_physics_shock_response_synthesis_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/data_physics_sine_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/geometry.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/global_analysis_attributes.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/global_test_attributes.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/matrix.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/mechanical_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/modal_test_parameters.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/mode.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/parameter_set.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/point_cloud.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/rattlesnake_random_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/rattlesnake_sysid_parameters.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/rattlesnake_transient_control_parameters.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/response_spectrum.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/scalar.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/unknown.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/vector.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/escdf_specifications/vibration_channel_table.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/excel.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/exodus.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/exporters.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/femap.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/matlab.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/nastran.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/native.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/notes.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/photo_files.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/punch.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/rattlesnake.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/rattlesnake_sine.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/rattlesnake_specification.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/report_template.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/sdynpy_data.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/sdynpy_npz.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/sdynpy_shapes.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/sep005.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/sniffing.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/stated_units.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/step.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/stl.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/threemf.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/io/unv.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/names.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/__init__.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/amplitude_spectrum.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/averaging.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/bands.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/bars.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/breakpoints.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/chance.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/classical_pulse.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/drive_prediction.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/dwell.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/filtering.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/half_power.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/log_axis.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/matrix_checks.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/matrix_comparison.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/overload.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/replication_fit.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/replication_segments.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/report_caption.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/resample.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/rms_check.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/scalogram.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/shock_check.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/shock_synthesis.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/shocks.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/signature.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/sine_distortion.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/sine_frequency.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/sine_profile.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/sine_tracking.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/srs_tolerance.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/stroke.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/sweep_rate.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/text_import.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/time_statistics.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/tone_readout.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/tracking_filter.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/transmissibility.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/plot/truncation.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/py.typed +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/report/calibration.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/report/markdown.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/report/page.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/rotate.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/theme.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/units.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/update.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/__init__.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/_quiet.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/animate.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/banded.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/geometry.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/mac_bars.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/marks.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/paired.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/pick.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/rigid.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/scalogram.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/sensors.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/sinespec.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/viz/waterfall.py +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics.egg-info/SOURCES.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics.egg-info/dependency_links.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics.egg-info/entry_points.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics.egg-info/requires.txt +0 -0
- {visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics.egg-info/top_level.txt +0 -0
|
@@ -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.
|
|
100
|
+
__version__ = '0.1.0a76'
|
|
101
101
|
|
|
102
102
|
|
|
103
103
|
def launch_gui(*paths: str, in_process: bool | None = None) -> Any:
|
|
@@ -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
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
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
|
-
|
|
1932
|
-
|
|
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
|
{visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/classical_pulse.py
RENAMED
|
@@ -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
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
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
|
|
@@ -2201,6 +2201,7 @@ class TimeHistory(DataArray):
|
|
|
2201
2201
|
# statement.
|
|
2202
2202
|
from .srs import DEFAULT_Q, KINDS, PER_OCTAVE, octave_frequencies
|
|
2203
2203
|
from .srs import peaks as srs_peaks
|
|
2204
|
+
from .srs import spectra as srs_spectra
|
|
2204
2205
|
|
|
2205
2206
|
rate = self.sample_rate
|
|
2206
2207
|
windows = self.srs_windows(shocks)
|
|
@@ -2217,11 +2218,10 @@ class TimeHistory(DataArray):
|
|
|
2217
2218
|
for i, record in enumerate(self.ordinate):
|
|
2218
2219
|
for k, window in enumerate(windows):
|
|
2219
2220
|
first, last = window.bounds(rate)
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
rows['maximax'].append(np.maximum(highest, -lowest))
|
|
2221
|
+
read = srs_spectra(*srs_peaks(record.real[first:last],
|
|
2222
|
+
frequencies, rate, q=q))
|
|
2223
|
+
for kind in KINDS:
|
|
2224
|
+
rows[kind].append(read[kind])
|
|
2225
2225
|
dofs.append(self.response_dof[i])
|
|
2226
2226
|
dims.append(self.ordinate_dim[i])
|
|
2227
2227
|
hints.append(self.dimension_hint[i])
|
{visualdynamics-0.1.0a75 → visualdynamics-0.1.0a76}/src/visualdynamics/core/drive_prediction.py
RENAMED
|
@@ -116,6 +116,7 @@ from typing import TYPE_CHECKING, Any
|
|
|
116
116
|
|
|
117
117
|
import numpy as np
|
|
118
118
|
|
|
119
|
+
from .matrix_checks import channel_labels, runs
|
|
119
120
|
from .progress import Ticker
|
|
120
121
|
from .shaker import ShakerRating
|
|
121
122
|
|
|
@@ -203,12 +204,25 @@ def regularized_inverse(plant: Any, *, method: str = 'truncated',
|
|
|
203
204
|
If `method` is not one of `METHODS`, or the tolerance or the
|
|
204
205
|
weight is out of its range.
|
|
205
206
|
"""
|
|
206
|
-
method
|
|
207
|
-
|
|
207
|
+
_check_inverse(method, tolerance, weight)
|
|
208
|
+
plant = np.asarray(plant, dtype=np.complex128)
|
|
209
|
+
lines, rows, columns = plant.shape
|
|
210
|
+
inverse = np.full((lines, columns, rows), np.nan, dtype=np.complex128)
|
|
211
|
+
kept = np.zeros(lines, dtype=int)
|
|
212
|
+
finite = np.isfinite(plant).all(axis=(1, 2))
|
|
213
|
+
if not finite.any():
|
|
214
|
+
return inverse, kept
|
|
215
|
+
inverse[finite], kept[finite] = inverse_from_svd(
|
|
216
|
+
*np.linalg.svd(plant[finite], full_matrices=False), method=method,
|
|
217
|
+
tolerance=tolerance, weight=weight, scale=scale)
|
|
218
|
+
return inverse, kept
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
def _check_inverse(method: str, tolerance: float, weight: float) -> None:
|
|
222
|
+
if str(method) not in METHODS:
|
|
208
223
|
raise ValueError(f'the inverse is {" or ".join(map(repr, METHODS))}, '
|
|
209
224
|
f'not {method!r}')
|
|
210
|
-
tolerance = float(tolerance)
|
|
211
|
-
weight = float(weight)
|
|
225
|
+
tolerance, weight = float(tolerance), float(weight)
|
|
212
226
|
if method == 'truncated' and not 0.0 < tolerance < 1.0:
|
|
213
227
|
raise ValueError('the truncation tolerance is a share of the '
|
|
214
228
|
'largest singular value, above 0 and under 1, '
|
|
@@ -216,14 +230,39 @@ def regularized_inverse(plant: Any, *, method: str = 'truncated',
|
|
|
216
230
|
if method == 'tikhonov' and not (np.isfinite(weight) and weight > 0.0):
|
|
217
231
|
raise ValueError('the Tikhonov weight has to be above zero and '
|
|
218
232
|
f'finite, not {weight:g}')
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def inverse_from_svd(u: Any, sigma: Any, vh: Any, *,
|
|
236
|
+
method: str = 'truncated', tolerance: float = TRUNCATION,
|
|
237
|
+
weight: float = TIKHONOV, scale: float | None = None
|
|
238
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
239
|
+
"""`regularized_inverse` from the plant's singular value
|
|
240
|
+
decomposition, (lines, rows, k), (lines, k) and (lines, k, columns)
|
|
241
|
+
as `numpy.linalg.svd` returns them with `full_matrices=False`:
|
|
242
|
+
the prediction decomposes the plant once and reads its condition
|
|
243
|
+
number off the same singular values.
|
|
244
|
+
|
|
245
|
+
Parameters
|
|
246
|
+
----------
|
|
247
|
+
u, sigma, vh : array_like
|
|
248
|
+
The decomposition, every line finite.
|
|
249
|
+
method, tolerance, weight, scale
|
|
250
|
+
As `regularized_inverse` takes them.
|
|
251
|
+
|
|
252
|
+
Returns
|
|
253
|
+
-------
|
|
254
|
+
tuple of numpy.ndarray
|
|
255
|
+
The inverse, (lines, columns, rows), and the directions kept
|
|
256
|
+
at each line.
|
|
257
|
+
"""
|
|
258
|
+
# The prediction decomposed the plant twice, once for the condition
|
|
259
|
+
# number and once here: two thirds of a 20 x 20 prediction's time
|
|
260
|
+
# (2026-10-28).
|
|
261
|
+
_check_inverse(method, tolerance, weight)
|
|
262
|
+
method, tolerance, weight = str(method), float(tolerance), float(weight)
|
|
263
|
+
u = np.asarray(u, dtype=np.complex128)
|
|
264
|
+
sigma = np.asarray(sigma, dtype=float)
|
|
265
|
+
vh = np.asarray(vh, dtype=np.complex128)
|
|
227
266
|
largest = sigma[:, :1]
|
|
228
267
|
if method == 'truncated':
|
|
229
268
|
keep = (sigma > 0.0) & (sigma >= tolerance * largest)
|
|
@@ -238,9 +277,8 @@ def regularized_inverse(plant: Any, *, method: str = 'truncated',
|
|
|
238
277
|
with np.errstate(divide='ignore', invalid='ignore'):
|
|
239
278
|
gain = np.where(denominator > 0.0, sigma / denominator, 0.0)
|
|
240
279
|
# V diag(gain) U^H, line by line
|
|
241
|
-
inverse
|
|
242
|
-
|
|
243
|
-
return inverse, kept
|
|
280
|
+
inverse = np.einsum('lji,lj,lkj->lik', vh.conj(), gain, u.conj())
|
|
281
|
+
return inverse, keep.sum(axis=1)
|
|
244
282
|
|
|
245
283
|
|
|
246
284
|
def through(plant: Any, density: Any) -> np.ndarray:
|
|
@@ -268,25 +306,18 @@ def _channel(dof: Any, dimension: str, side: int) -> tuple[str, str]:
|
|
|
268
306
|
return str(dof), channel_quantities(dimension)[side]
|
|
269
307
|
|
|
270
308
|
|
|
271
|
-
def _labels(keys: list[tuple[str, str]]) -> list[str]:
|
|
272
|
-
"""A label per (DOF, quantity) channel: the DOF, with the quantity
|
|
273
|
-
added only where one DOF carries two."""
|
|
274
|
-
dofs = [dof for dof, _quantity in keys]
|
|
275
|
-
return [dof if dofs.count(dof) == 1 else f'{dof} {quantity}'
|
|
276
|
-
for dof, quantity in keys]
|
|
277
|
-
|
|
278
|
-
|
|
279
309
|
def _plant(frf: Frf) -> tuple[list[tuple[str, str]], list[tuple[str, str]],
|
|
280
310
|
np.ndarray, np.ndarray]:
|
|
281
|
-
"""The FRF's records as (rows, columns, matrix, filled)
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
311
|
+
"""The FRF's records as (rows, columns, matrix, filled), assembled
|
|
312
|
+
by `matrix_checks.frf_matrix` as every matrix reading assembles
|
|
313
|
+
them: a row per response channel and a column per drive, (lines,
|
|
314
|
+
rows, columns), with `filled` saying which entries a record
|
|
315
|
+
supplied. Refuses a record whose units are not declared, two
|
|
316
|
+
records of one entry, and drives of two quantities."""
|
|
285
317
|
from ..units import UNKNOWN
|
|
318
|
+
from .matrix_checks import frf_matrix
|
|
286
319
|
|
|
287
|
-
|
|
288
|
-
columns: list[tuple[str, str]] = []
|
|
289
|
-
keys = []
|
|
320
|
+
seen: set[tuple[str, str, str]] = set()
|
|
290
321
|
for i in range(frf.num_records):
|
|
291
322
|
dimension = str(frf.ordinate_dim[i])
|
|
292
323
|
if dimension == UNKNOWN:
|
|
@@ -295,65 +326,36 @@ def _plant(frf: Frf) -> tuple[list[tuple[str, str]], list[tuple[str, str]],
|
|
|
295
326
|
f'{frf.reference_dof[i]} are not declared, so the drives '
|
|
296
327
|
'it predicts would be in a scale nobody chose; declare '
|
|
297
328
|
'them first')
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
if row not in rows:
|
|
301
|
-
rows.append(row)
|
|
302
|
-
if column not in columns:
|
|
303
|
-
columns.append(column)
|
|
304
|
-
key = (rows.index(row), columns.index(column))
|
|
305
|
-
if key in keys:
|
|
329
|
+
key = (str(frf.response_dof[i]), str(frf.reference_dof[i]), dimension)
|
|
330
|
+
if key in seen:
|
|
306
331
|
# two plants for one entry: which one was meant is not in
|
|
307
332
|
# the data, and taking either would be a guess
|
|
308
333
|
raise ValueError(
|
|
309
334
|
f'the FRF holds two records of {frf.response_dof[i]} over '
|
|
310
335
|
f'{frf.reference_dof[i]}; keep one')
|
|
311
|
-
|
|
336
|
+
seen.add(key)
|
|
337
|
+
rows, columns, matrix, filled = frf_matrix(frf)
|
|
312
338
|
drives = sorted({quantity for _dof, quantity in columns})
|
|
313
339
|
if len(drives) > 1:
|
|
314
340
|
raise ValueError(
|
|
315
341
|
f'the FRF is against drives of {" and ".join(drives)}; the '
|
|
316
342
|
'drives predicted together have to be one quantity')
|
|
317
|
-
matrix = np.zeros((len(frf.abscissa), len(rows), len(columns)),
|
|
318
|
-
dtype=np.complex128)
|
|
319
|
-
filled = np.zeros((len(rows), len(columns)), dtype=bool)
|
|
320
|
-
ordinate = np.asarray(frf.ordinate)
|
|
321
|
-
for i, (r, c) in enumerate(keys):
|
|
322
|
-
matrix[:, r, c] = ordinate[i]
|
|
323
|
-
filled[r, c] = True
|
|
324
343
|
return rows, columns, matrix, filled
|
|
325
344
|
|
|
326
345
|
|
|
327
|
-
def _per_bin(specification: Psd, values: np.ndarray,
|
|
328
|
-
lines: np.ndarray) -> np.ndarray:
|
|
329
|
-
"""A density on lines read at other lines: the value of the bin
|
|
330
|
-
each one falls in, not-a-number outside every bin."""
|
|
331
|
-
left, right = specification.bin_bounds()
|
|
332
|
-
order = np.argsort(left)
|
|
333
|
-
left, right, values = left[order], right[order], values[order]
|
|
334
|
-
k = np.searchsorted(right, lines, side='right')
|
|
335
|
-
inside = k < len(right)
|
|
336
|
-
k = np.minimum(k, len(right) - 1)
|
|
337
|
-
inside &= left[k] <= lines
|
|
338
|
-
out = np.full(len(lines), np.nan, dtype=values.dtype)
|
|
339
|
-
out[inside] = values[k[inside]]
|
|
340
|
-
return out
|
|
341
|
-
|
|
342
|
-
|
|
343
346
|
def _read_target(specification: Psd, channels: list[tuple[str, str]],
|
|
344
347
|
lines: np.ndarray) -> np.ndarray:
|
|
345
348
|
"""The specification's matrix on the control channels, at `lines`.
|
|
346
349
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
phase linear
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
350
|
+
Each record is read the way it is drawn, by `compliance.record_at`,
|
|
351
|
+
the reader the matrix comparison uses too: an autospectrum log-log
|
|
352
|
+
between breakpoints with its gaps, a cross term magnitude log-log
|
|
353
|
+
and phase linear with a zero taken as a value, a density on lines
|
|
354
|
+
per bin. A cross term held only by its mirror is its mirror's
|
|
355
|
+
conjugate. Lines where any autospectrum is not written are
|
|
356
|
+
not-a-number throughout."""
|
|
354
357
|
from ..units import UNKNOWN
|
|
355
|
-
from .
|
|
356
|
-
from .compliance import log_interpolate
|
|
358
|
+
from .compliance import record_at
|
|
357
359
|
|
|
358
360
|
references = (specification.reference_dof
|
|
359
361
|
if specification.reference_dof is not None
|
|
@@ -375,9 +377,8 @@ def _read_target(specification: Psd, channels: list[tuple[str, str]],
|
|
|
375
377
|
f'{specification.response_dof[i]}, {references[i]}; '
|
|
376
378
|
'keep one')
|
|
377
379
|
held[(row, column)] = i
|
|
378
|
-
labels =
|
|
380
|
+
labels = channel_labels(channels)
|
|
379
381
|
n = len(channels)
|
|
380
|
-
breakpoints = getattr(specification, 'interpolation', 'bin') == 'log_log'
|
|
381
382
|
ordinate = np.atleast_2d(np.asarray(specification.ordinate))
|
|
382
383
|
target = np.zeros((len(lines), n, n), dtype=np.complex128)
|
|
383
384
|
for a in range(n):
|
|
@@ -410,21 +411,13 @@ def _read_target(specification: Psd, channels: list[tuple[str, str]],
|
|
|
410
411
|
f'{labels[a]} and {labels[b]} are not conjugates '
|
|
411
412
|
'of each other, so the target between them '
|
|
412
413
|
'cannot be read; keep one or make them agree')
|
|
414
|
+
read = record_at(specification, record, lines)
|
|
413
415
|
if a == b:
|
|
414
|
-
|
|
415
|
-
read = (log_interpolate(lines, specification.abscissa, values)
|
|
416
|
-
if breakpoints else
|
|
417
|
-
_per_bin(specification, values.astype(float), lines))
|
|
416
|
+
read = np.real(read)
|
|
418
417
|
with np.errstate(invalid='ignore'):
|
|
419
418
|
read = np.where(read > 0.0, read, np.nan)
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
read = (read_at_lines(np.asarray(specification.abscissa,
|
|
423
|
-
dtype=float), values, lines)
|
|
424
|
-
if breakpoints else
|
|
425
|
-
_per_bin(specification, values, lines))
|
|
426
|
-
if pair not in held:
|
|
427
|
-
read = np.conj(read)
|
|
419
|
+
elif pair not in held:
|
|
420
|
+
read = np.conj(read)
|
|
428
421
|
target[:, a, b] = read
|
|
429
422
|
target[:, b, a] = np.conj(read)
|
|
430
423
|
unwritten = ~np.isfinite(np.real(np.einsum('lii->li', target))).all(axis=1)
|
|
@@ -518,16 +511,6 @@ def _dominant(frequency: np.ndarray, density: np.ndarray
|
|
|
518
511
|
(float(chosen.min()), float(chosen.max())))
|
|
519
512
|
|
|
520
513
|
|
|
521
|
-
def runs(flags: Any) -> list[tuple[int, int]]:
|
|
522
|
-
"""(first, last) index of each run of consecutive true flags."""
|
|
523
|
-
flags = np.asarray(flags, dtype=bool)
|
|
524
|
-
if not flags.any():
|
|
525
|
-
return []
|
|
526
|
-
edges = np.diff(np.concatenate([[0], flags.astype(int), [0]]))
|
|
527
|
-
return [(int(a), int(b) - 1) for a, b in
|
|
528
|
-
zip(np.flatnonzero(edges == 1), np.flatnonzero(edges == -1))]
|
|
529
|
-
|
|
530
|
-
|
|
531
514
|
def _amount(value: float, dimension: str,
|
|
532
515
|
unit_system: UnitSystem | None) -> str:
|
|
533
516
|
"""`value`, in SI, as a number and a unit in `unit_system`, SI's own
|
|
@@ -640,6 +623,44 @@ class DrivePrediction:
|
|
|
640
623
|
peak_factor: float | None
|
|
641
624
|
demands: list[Demand] = field(default_factory=list)
|
|
642
625
|
notes: list[str] = field(default_factory=list)
|
|
626
|
+
#: the rows and the drives as (DOF, quantity), what the ratings are
|
|
627
|
+
#: read against; and how many of `notes` the prediction itself made,
|
|
628
|
+
#: the rest being the ratings', so a rating moved rewrites only those
|
|
629
|
+
row_channels: list[tuple[str, str]] = field(default_factory=list)
|
|
630
|
+
drive_channels: list[tuple[str, str]] = field(default_factory=list)
|
|
631
|
+
plain_notes: int = 0
|
|
632
|
+
|
|
633
|
+
def with_ratings(self, *, drive_limit: float | Mapping[str, float] | None = None,
|
|
634
|
+
shakers: ShakerRating | Mapping[str, ShakerRating] | None = None,
|
|
635
|
+
tables: Mapping[str, str] | None = None,
|
|
636
|
+
limits: Mapping[str, float] | None = None,
|
|
637
|
+
duration: float = DURATION,
|
|
638
|
+
peak_factor: float | None = None) -> DrivePrediction:
|
|
639
|
+
"""This prediction judged against other ratings, the drives and
|
|
640
|
+
responses kept: the inverse does not depend on the ratings, so a
|
|
641
|
+
rating moved does not predict again. The ratings are taken
|
|
642
|
+
exactly as `predict_drives` takes them, and one not given here
|
|
643
|
+
is not judged, whatever the prediction was made with.
|
|
644
|
+
|
|
645
|
+
Returns
|
|
646
|
+
-------
|
|
647
|
+
DrivePrediction
|
|
648
|
+
A new prediction; this one is unchanged.
|
|
649
|
+
|
|
650
|
+
Raises
|
|
651
|
+
------
|
|
652
|
+
ValueError, TypeError
|
|
653
|
+
As `predict_drives` raises for a rating it cannot read.
|
|
654
|
+
"""
|
|
655
|
+
from dataclasses import replace
|
|
656
|
+
|
|
657
|
+
out = replace(self, duration=_positive(duration, 'duration'),
|
|
658
|
+
peak_factor=(None if peak_factor is None else
|
|
659
|
+
_positive(peak_factor, 'peak factor')),
|
|
660
|
+
demands=[], notes=list(self.notes[:self.plain_notes]))
|
|
661
|
+
out.demands.extend(_demands(out, out.row_channels, out.drive_channels,
|
|
662
|
+
drive_limit, shakers, tables, limits))
|
|
663
|
+
return out
|
|
643
664
|
|
|
644
665
|
@property
|
|
645
666
|
def single(self) -> bool:
|
|
@@ -796,7 +817,7 @@ def _for_each(given: Any, labels: list[str], what: str) -> dict[str, Any]:
|
|
|
796
817
|
return dict.fromkeys(labels, given)
|
|
797
818
|
out = {}
|
|
798
819
|
for key, value in given.items():
|
|
799
|
-
label =
|
|
820
|
+
label = str(key) if str(key) in labels else None
|
|
800
821
|
if label is None:
|
|
801
822
|
raise ValueError(f'{what} names {key}, which is not a drive of '
|
|
802
823
|
f'the FRF ({", ".join(labels)})')
|
|
@@ -804,13 +825,6 @@ def _for_each(given: Any, labels: list[str], what: str) -> dict[str, Any]:
|
|
|
804
825
|
return out
|
|
805
826
|
|
|
806
827
|
|
|
807
|
-
def _find(key: str, labels: list[str]) -> str | None:
|
|
808
|
-
"""The label `key` names, or None. A label is its channel's DOF, with
|
|
809
|
-
the quantity added only where one DOF holds two (`_labels`), so a
|
|
810
|
-
DOF names a channel wherever it can."""
|
|
811
|
-
return key if key in labels else None
|
|
812
|
-
|
|
813
|
-
|
|
814
828
|
def predict_drives(frf: Frf, specification: Psd, *,
|
|
815
829
|
method: str = 'truncated', tolerance: float = TRUNCATION,
|
|
816
830
|
weight: float = TIKHONOV,
|
|
@@ -931,8 +945,8 @@ def predict_drives(frf: Frf, specification: Psd, *,
|
|
|
931
945
|
ticker = Ticker(progress)
|
|
932
946
|
ticker.add(4)
|
|
933
947
|
rows, columns, matrix, filled = _plant(frf)
|
|
934
|
-
row_labels =
|
|
935
|
-
drive_labels =
|
|
948
|
+
row_labels = channel_labels(rows)
|
|
949
|
+
drive_labels = channel_labels(columns)
|
|
936
950
|
controls = _control_channels(specification, rows)
|
|
937
951
|
control_index = [rows.index(key) for key in controls]
|
|
938
952
|
for r in control_index:
|
|
@@ -969,7 +983,9 @@ def predict_drives(frf: Frf, specification: Psd, *,
|
|
|
969
983
|
f'FRF is not a number not predicted, first at {where}')
|
|
970
984
|
ticker.tick()
|
|
971
985
|
ticker.busy()
|
|
972
|
-
|
|
986
|
+
# one decomposition: the condition number, the dead lines and the
|
|
987
|
+
# inverse all read from it
|
|
988
|
+
u, gains, vh = np.linalg.svd(plant[judged], full_matrices=False)
|
|
973
989
|
if not gains.max() > 0.0:
|
|
974
990
|
raise ValueError('the FRF is zero at every line the specification '
|
|
975
991
|
'is written over: the drives reach nothing')
|
|
@@ -998,8 +1014,8 @@ def predict_drives(frf: Frf, specification: Psd, *,
|
|
|
998
1014
|
inverse = np.full((len(f), len(columns), len(controls)), np.nan,
|
|
999
1015
|
dtype=np.complex128)
|
|
1000
1016
|
kept = np.zeros(len(f), dtype=int)
|
|
1001
|
-
inverse[judged], kept[judged] =
|
|
1002
|
-
|
|
1017
|
+
inverse[judged], kept[judged] = inverse_from_svd(
|
|
1018
|
+
u, gains, vh, method=method, tolerance=tolerance, weight=weight)
|
|
1003
1019
|
ticker.tick()
|
|
1004
1020
|
drive_csd = np.full((len(f), len(columns), len(columns)), np.nan,
|
|
1005
1021
|
dtype=np.complex128)
|
|
@@ -1035,10 +1051,11 @@ def predict_drives(frf: Frf, specification: Psd, *,
|
|
|
1035
1051
|
target=target, control=control, judged=judged, kept=kept,
|
|
1036
1052
|
condition=condition, method=str(method), tolerance=float(tolerance),
|
|
1037
1053
|
weight=float(weight), duration=duration, peak_factor=peak_factor,
|
|
1038
|
-
notes=notes)
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1054
|
+
notes=notes, row_channels=list(rows), drive_channels=list(columns),
|
|
1055
|
+
plain_notes=len(notes))
|
|
1056
|
+
prediction = prediction.with_ratings(
|
|
1057
|
+
drive_limit=drive_limit, shakers=shakers, tables=tables,
|
|
1058
|
+
limits=limits, duration=duration, peak_factor=peak_factor)
|
|
1042
1059
|
ticker.tick()
|
|
1043
1060
|
return prediction
|
|
1044
1061
|
|
|
@@ -1129,7 +1146,7 @@ def _demands(prediction: DrivePrediction, rows: list[tuple[str, str]],
|
|
|
1129
1146
|
f'{where}')
|
|
1130
1147
|
|
|
1131
1148
|
for key, value in (limits or {}).items():
|
|
1132
|
-
found =
|
|
1149
|
+
found = str(key) if str(key) in prediction.rows else None
|
|
1133
1150
|
if found is None:
|
|
1134
1151
|
raise ValueError(f'a limit is given at {key}, which is not a '
|
|
1135
1152
|
'channel of the FRF')
|
|
@@ -1151,7 +1168,7 @@ def _table(prediction: DrivePrediction, rows: list[tuple[str, str]],
|
|
|
1151
1168
|
of a single-input test. Refused unless it is a predicted
|
|
1152
1169
|
acceleration."""
|
|
1153
1170
|
if asked is not None:
|
|
1154
|
-
found =
|
|
1171
|
+
found = str(asked) if str(asked) in prediction.rows else None
|
|
1155
1172
|
if found is None:
|
|
1156
1173
|
raise ValueError(f'the table of the shaker at {label} is '
|
|
1157
1174
|
f'{asked}, which is not a channel of the FRF')
|