oscura 0.0.1__py3-none-any.whl → 0.1.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,626 @@
|
|
|
1
|
+
"""Extended Power Analysis Visualization Functions.
|
|
2
|
+
|
|
3
|
+
This module provides visualization functions for power conversion analysis
|
|
4
|
+
including efficiency curves, ripple analysis, loss breakdown, and
|
|
5
|
+
multi-channel power waveforms.
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.visualization.power_extended import (
|
|
9
|
+
... plot_efficiency_curve, plot_ripple_waveform, plot_loss_breakdown
|
|
10
|
+
... )
|
|
11
|
+
>>> fig = plot_efficiency_curve(load_currents, efficiencies)
|
|
12
|
+
>>> fig = plot_ripple_waveform(voltage_trace, ripple_trace)
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
- Power supply measurement best practices
|
|
16
|
+
- DC-DC converter efficiency testing
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
from collections.abc import Callable
|
|
22
|
+
from pathlib import Path
|
|
23
|
+
from typing import TYPE_CHECKING, Any, cast
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
import matplotlib.pyplot as plt
|
|
29
|
+
|
|
30
|
+
HAS_MATPLOTLIB = True
|
|
31
|
+
except ImportError:
|
|
32
|
+
HAS_MATPLOTLIB = False
|
|
33
|
+
|
|
34
|
+
if TYPE_CHECKING:
|
|
35
|
+
from matplotlib.axes import Axes
|
|
36
|
+
from matplotlib.figure import Figure
|
|
37
|
+
from numpy.typing import NDArray
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"plot_efficiency_curve",
|
|
42
|
+
"plot_loss_breakdown",
|
|
43
|
+
"plot_power_waveforms",
|
|
44
|
+
"plot_ripple_waveform",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def plot_efficiency_curve(
|
|
49
|
+
load_values: NDArray[np.floating[Any]],
|
|
50
|
+
efficiency_values: NDArray[np.floating[Any]],
|
|
51
|
+
*,
|
|
52
|
+
v_in_values: list[float] | None = None,
|
|
53
|
+
efficiency_sets: list[NDArray[np.floating[Any]]] | None = None,
|
|
54
|
+
ax: Axes | None = None,
|
|
55
|
+
figsize: tuple[float, float] = (10, 6),
|
|
56
|
+
title: str | None = None,
|
|
57
|
+
load_unit: str = "A",
|
|
58
|
+
target_efficiency: float | None = None,
|
|
59
|
+
show_peak: bool = True,
|
|
60
|
+
show: bool = True,
|
|
61
|
+
save_path: str | Path | None = None,
|
|
62
|
+
) -> Figure:
|
|
63
|
+
"""Plot efficiency vs load curve for power converters.
|
|
64
|
+
|
|
65
|
+
Creates an efficiency plot showing converter efficiency as a function
|
|
66
|
+
of load current or power, with optional multiple input voltage curves.
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
load_values: Load current or power array.
|
|
70
|
+
efficiency_values: Efficiency values (0-100 or 0-1).
|
|
71
|
+
v_in_values: List of input voltages for multi-curve plot.
|
|
72
|
+
efficiency_sets: List of efficiency arrays for each v_in.
|
|
73
|
+
ax: Matplotlib axes.
|
|
74
|
+
figsize: Figure size.
|
|
75
|
+
title: Plot title.
|
|
76
|
+
load_unit: Load axis unit ("A", "W", "%").
|
|
77
|
+
target_efficiency: Target efficiency line.
|
|
78
|
+
show_peak: Annotate peak efficiency point.
|
|
79
|
+
show: Display plot.
|
|
80
|
+
save_path: Save path.
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Matplotlib Figure object.
|
|
84
|
+
|
|
85
|
+
Example:
|
|
86
|
+
>>> load = np.linspace(0.1, 5, 50) # 0.1A to 5A
|
|
87
|
+
>>> eff = 90 - 5 * np.exp(-load) # Example efficiency curve
|
|
88
|
+
>>> fig = plot_efficiency_curve(load, eff, target_efficiency=85)
|
|
89
|
+
"""
|
|
90
|
+
if not HAS_MATPLOTLIB:
|
|
91
|
+
raise ImportError("matplotlib is required for visualization")
|
|
92
|
+
|
|
93
|
+
if ax is None:
|
|
94
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
95
|
+
else:
|
|
96
|
+
fig_temp = ax.get_figure()
|
|
97
|
+
if fig_temp is None:
|
|
98
|
+
raise ValueError("Axes must have an associated figure")
|
|
99
|
+
fig = cast("Figure", fig_temp)
|
|
100
|
+
|
|
101
|
+
# Normalize efficiency to percentage if needed
|
|
102
|
+
if np.max(efficiency_values) <= 1.0:
|
|
103
|
+
efficiency_values = efficiency_values * 100
|
|
104
|
+
if efficiency_sets is not None:
|
|
105
|
+
efficiency_sets = [e * 100 for e in efficiency_sets]
|
|
106
|
+
|
|
107
|
+
# Color palette for multiple curves
|
|
108
|
+
colors = ["#3498DB", "#E74C3C", "#27AE60", "#9B59B6", "#F39C12"]
|
|
109
|
+
|
|
110
|
+
if v_in_values is not None and efficiency_sets is not None:
|
|
111
|
+
# Multiple input voltage curves
|
|
112
|
+
for i, (v_in, eff) in enumerate(zip(v_in_values, efficiency_sets, strict=False)):
|
|
113
|
+
color = colors[i % len(colors)]
|
|
114
|
+
ax.plot(load_values, eff, "-", linewidth=2, color=color, label=f"Vin = {v_in}V")
|
|
115
|
+
|
|
116
|
+
if show_peak:
|
|
117
|
+
peak_idx = np.argmax(eff)
|
|
118
|
+
ax.plot(load_values[peak_idx], eff[peak_idx], "o", color=color, markersize=8)
|
|
119
|
+
else:
|
|
120
|
+
# Single curve
|
|
121
|
+
ax.plot(
|
|
122
|
+
load_values, efficiency_values, "-", linewidth=2.5, color="#3498DB", label="Efficiency"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if show_peak:
|
|
126
|
+
peak_idx = np.argmax(efficiency_values)
|
|
127
|
+
peak_load = load_values[peak_idx]
|
|
128
|
+
peak_eff = efficiency_values[peak_idx]
|
|
129
|
+
ax.plot(peak_load, peak_eff, "o", color="#E74C3C", markersize=10, zorder=5)
|
|
130
|
+
ax.annotate(
|
|
131
|
+
f"Peak: {peak_eff:.1f}%\n@ {peak_load:.2f} {load_unit}",
|
|
132
|
+
xy=(peak_load, peak_eff),
|
|
133
|
+
xytext=(15, -15),
|
|
134
|
+
textcoords="offset points",
|
|
135
|
+
fontsize=9,
|
|
136
|
+
ha="left",
|
|
137
|
+
bbox={"boxstyle": "round,pad=0.3", "facecolor": "white", "alpha": 0.9},
|
|
138
|
+
arrowprops={"arrowstyle": "->", "connectionstyle": "arc3,rad=0.2"},
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
# Target efficiency line
|
|
142
|
+
if target_efficiency is not None:
|
|
143
|
+
ax.axhline(
|
|
144
|
+
target_efficiency,
|
|
145
|
+
color="#E74C3C",
|
|
146
|
+
linestyle="--",
|
|
147
|
+
linewidth=1.5,
|
|
148
|
+
label=f"Target: {target_efficiency}%",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
# Fill area under curve
|
|
152
|
+
ax.fill_between(
|
|
153
|
+
load_values,
|
|
154
|
+
0,
|
|
155
|
+
efficiency_values if efficiency_sets is None else efficiency_sets[0],
|
|
156
|
+
alpha=0.1,
|
|
157
|
+
color="#3498DB",
|
|
158
|
+
)
|
|
159
|
+
|
|
160
|
+
# Labels
|
|
161
|
+
ax.set_xlabel(f"Load ({load_unit})", fontsize=11)
|
|
162
|
+
ax.set_ylabel("Efficiency (%)", fontsize=11)
|
|
163
|
+
ax.set_ylim(0, 100)
|
|
164
|
+
ax.set_xlim(load_values[0], load_values[-1])
|
|
165
|
+
ax.grid(True, alpha=0.3)
|
|
166
|
+
ax.legend(loc="best")
|
|
167
|
+
|
|
168
|
+
if title:
|
|
169
|
+
ax.set_title(title, fontsize=12, fontweight="bold")
|
|
170
|
+
else:
|
|
171
|
+
ax.set_title("Converter Efficiency vs Load", fontsize=12, fontweight="bold")
|
|
172
|
+
|
|
173
|
+
fig.tight_layout()
|
|
174
|
+
|
|
175
|
+
if save_path is not None:
|
|
176
|
+
fig.savefig(save_path, dpi=300, bbox_inches="tight")
|
|
177
|
+
|
|
178
|
+
if show:
|
|
179
|
+
plt.show()
|
|
180
|
+
|
|
181
|
+
return fig
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def plot_power_waveforms(
|
|
185
|
+
time: NDArray[np.floating[Any]],
|
|
186
|
+
*,
|
|
187
|
+
v_in: NDArray[np.floating[Any]] | None = None,
|
|
188
|
+
i_in: NDArray[np.floating[Any]] | None = None,
|
|
189
|
+
v_out: NDArray[np.floating[Any]] | None = None,
|
|
190
|
+
i_out: NDArray[np.floating[Any]] | None = None,
|
|
191
|
+
figsize: tuple[float, float] = (12, 10),
|
|
192
|
+
title: str | None = None,
|
|
193
|
+
time_unit: str = "auto",
|
|
194
|
+
show_power: bool = True,
|
|
195
|
+
show: bool = True,
|
|
196
|
+
save_path: str | Path | None = None,
|
|
197
|
+
) -> Figure:
|
|
198
|
+
"""Plot multi-channel power waveforms with optional power calculation.
|
|
199
|
+
|
|
200
|
+
Creates a multi-panel plot showing input/output voltage and current
|
|
201
|
+
waveforms with optional instantaneous power overlay.
|
|
202
|
+
|
|
203
|
+
Args:
|
|
204
|
+
time: Time array in seconds.
|
|
205
|
+
v_in: Input voltage waveform.
|
|
206
|
+
i_in: Input current waveform.
|
|
207
|
+
v_out: Output voltage waveform.
|
|
208
|
+
i_out: Output current waveform.
|
|
209
|
+
figsize: Figure size.
|
|
210
|
+
title: Plot title.
|
|
211
|
+
time_unit: Time axis unit.
|
|
212
|
+
show_power: Calculate and show instantaneous power.
|
|
213
|
+
show: Display plot.
|
|
214
|
+
save_path: Save path.
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
Matplotlib Figure object.
|
|
218
|
+
"""
|
|
219
|
+
if not HAS_MATPLOTLIB:
|
|
220
|
+
raise ImportError("matplotlib is required for visualization")
|
|
221
|
+
|
|
222
|
+
# Determine number of subplots needed
|
|
223
|
+
n_plots = sum(
|
|
224
|
+
[
|
|
225
|
+
v_in is not None,
|
|
226
|
+
v_out is not None,
|
|
227
|
+
show_power and (v_in is not None or v_out is not None),
|
|
228
|
+
]
|
|
229
|
+
)
|
|
230
|
+
if n_plots == 0:
|
|
231
|
+
raise ValueError("At least one voltage waveform must be provided")
|
|
232
|
+
|
|
233
|
+
fig, axes = plt.subplots(n_plots, 1, figsize=figsize, sharex=True)
|
|
234
|
+
if n_plots == 1:
|
|
235
|
+
axes = [axes]
|
|
236
|
+
|
|
237
|
+
# Time unit conversion
|
|
238
|
+
if time_unit == "auto":
|
|
239
|
+
max_time = np.max(time)
|
|
240
|
+
if max_time < 1e-6:
|
|
241
|
+
time_unit = "us"
|
|
242
|
+
time_mult = 1e6
|
|
243
|
+
elif max_time < 1e-3:
|
|
244
|
+
time_unit = "ms"
|
|
245
|
+
time_mult = 1e3
|
|
246
|
+
else:
|
|
247
|
+
time_unit = "s"
|
|
248
|
+
time_mult = 1.0
|
|
249
|
+
else:
|
|
250
|
+
time_mult = {"s": 1, "ms": 1e3, "us": 1e6, "ns": 1e9}.get(time_unit, 1.0)
|
|
251
|
+
|
|
252
|
+
time_scaled = time * time_mult
|
|
253
|
+
|
|
254
|
+
ax_idx = 0
|
|
255
|
+
|
|
256
|
+
# Input voltage/current panel
|
|
257
|
+
if v_in is not None:
|
|
258
|
+
ax = axes[ax_idx]
|
|
259
|
+
ax.plot(time_scaled, v_in, "#3498DB", linewidth=1.5, label="V_in")
|
|
260
|
+
ax.set_ylabel("V_in (V)", color="#3498DB", fontsize=10)
|
|
261
|
+
ax.tick_params(axis="y", labelcolor="#3498DB")
|
|
262
|
+
ax.grid(True, alpha=0.3)
|
|
263
|
+
|
|
264
|
+
if i_in is not None:
|
|
265
|
+
ax2 = ax.twinx()
|
|
266
|
+
ax2.plot(time_scaled, i_in, "#E74C3C", linewidth=1.5, label="I_in")
|
|
267
|
+
ax2.set_ylabel("I_in (A)", color="#E74C3C", fontsize=10)
|
|
268
|
+
ax2.tick_params(axis="y", labelcolor="#E74C3C")
|
|
269
|
+
|
|
270
|
+
ax.set_title("Input", fontsize=10, fontweight="bold", loc="left")
|
|
271
|
+
ax_idx += 1
|
|
272
|
+
|
|
273
|
+
# Output voltage/current panel
|
|
274
|
+
if v_out is not None:
|
|
275
|
+
ax = axes[ax_idx]
|
|
276
|
+
ax.plot(time_scaled, v_out, "#27AE60", linewidth=1.5, label="V_out")
|
|
277
|
+
ax.set_ylabel("V_out (V)", color="#27AE60", fontsize=10)
|
|
278
|
+
ax.tick_params(axis="y", labelcolor="#27AE60")
|
|
279
|
+
ax.grid(True, alpha=0.3)
|
|
280
|
+
|
|
281
|
+
if i_out is not None:
|
|
282
|
+
ax2 = ax.twinx()
|
|
283
|
+
ax2.plot(time_scaled, i_out, "#9B59B6", linewidth=1.5, label="I_out")
|
|
284
|
+
ax2.set_ylabel("I_out (A)", color="#9B59B6", fontsize=10)
|
|
285
|
+
ax2.tick_params(axis="y", labelcolor="#9B59B6")
|
|
286
|
+
|
|
287
|
+
ax.set_title("Output", fontsize=10, fontweight="bold", loc="left")
|
|
288
|
+
ax_idx += 1
|
|
289
|
+
|
|
290
|
+
# Power panel
|
|
291
|
+
if show_power:
|
|
292
|
+
ax = axes[ax_idx]
|
|
293
|
+
|
|
294
|
+
if v_in is not None and i_in is not None:
|
|
295
|
+
p_in = v_in * i_in
|
|
296
|
+
ax.plot(
|
|
297
|
+
time_scaled,
|
|
298
|
+
p_in,
|
|
299
|
+
"#3498DB",
|
|
300
|
+
linewidth=1.5,
|
|
301
|
+
label=f"P_in (avg: {np.mean(p_in):.2f}W)",
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
if v_out is not None and i_out is not None:
|
|
305
|
+
p_out = v_out * i_out
|
|
306
|
+
ax.plot(
|
|
307
|
+
time_scaled,
|
|
308
|
+
p_out,
|
|
309
|
+
"#27AE60",
|
|
310
|
+
linewidth=1.5,
|
|
311
|
+
label=f"P_out (avg: {np.mean(p_out):.2f}W)",
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
ax.set_ylabel("Power (W)", fontsize=10)
|
|
315
|
+
ax.set_title("Instantaneous Power", fontsize=10, fontweight="bold", loc="left")
|
|
316
|
+
ax.legend(loc="upper right", fontsize=9)
|
|
317
|
+
ax.grid(True, alpha=0.3)
|
|
318
|
+
|
|
319
|
+
# X-axis label on bottom
|
|
320
|
+
axes[-1].set_xlabel(f"Time ({time_unit})", fontsize=11)
|
|
321
|
+
|
|
322
|
+
if title:
|
|
323
|
+
fig.suptitle(title, fontsize=14, fontweight="bold")
|
|
324
|
+
else:
|
|
325
|
+
fig.suptitle("Power Converter Waveforms", fontsize=14, fontweight="bold")
|
|
326
|
+
|
|
327
|
+
fig.tight_layout()
|
|
328
|
+
|
|
329
|
+
if save_path is not None:
|
|
330
|
+
fig.savefig(save_path, dpi=300, bbox_inches="tight")
|
|
331
|
+
|
|
332
|
+
if show:
|
|
333
|
+
plt.show()
|
|
334
|
+
|
|
335
|
+
return fig
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def plot_ripple_waveform(
|
|
339
|
+
time: NDArray[np.floating[Any]],
|
|
340
|
+
voltage: NDArray[np.floating[Any]],
|
|
341
|
+
*,
|
|
342
|
+
ax: Axes | None = None,
|
|
343
|
+
figsize: tuple[float, float] = (12, 8),
|
|
344
|
+
title: str | None = None,
|
|
345
|
+
time_unit: str = "auto",
|
|
346
|
+
show_dc: bool = True,
|
|
347
|
+
show_ac: bool = True,
|
|
348
|
+
show_spectrum: bool = True,
|
|
349
|
+
sample_rate: float | None = None,
|
|
350
|
+
show: bool = True,
|
|
351
|
+
save_path: str | Path | None = None,
|
|
352
|
+
) -> Figure:
|
|
353
|
+
"""Plot ripple waveform with DC, AC, and spectral analysis.
|
|
354
|
+
|
|
355
|
+
Creates a multi-panel view showing DC-coupled waveform, AC-coupled
|
|
356
|
+
ripple, and optionally the ripple frequency spectrum.
|
|
357
|
+
|
|
358
|
+
Args:
|
|
359
|
+
time: Time array in seconds.
|
|
360
|
+
voltage: Voltage waveform.
|
|
361
|
+
ax: Matplotlib axes (creates multi-panel if None).
|
|
362
|
+
figsize: Figure size.
|
|
363
|
+
title: Plot title.
|
|
364
|
+
time_unit: Time axis unit.
|
|
365
|
+
show_dc: Show DC-coupled waveform.
|
|
366
|
+
show_ac: Show AC-coupled ripple.
|
|
367
|
+
show_spectrum: Show ripple spectrum.
|
|
368
|
+
sample_rate: Sample rate for FFT (required if show_spectrum=True).
|
|
369
|
+
show: Display plot.
|
|
370
|
+
save_path: Save path.
|
|
371
|
+
|
|
372
|
+
Returns:
|
|
373
|
+
Matplotlib Figure object.
|
|
374
|
+
"""
|
|
375
|
+
if not HAS_MATPLOTLIB:
|
|
376
|
+
raise ImportError("matplotlib is required for visualization")
|
|
377
|
+
|
|
378
|
+
n_plots = sum([show_dc, show_ac, show_spectrum])
|
|
379
|
+
if n_plots == 0:
|
|
380
|
+
raise ValueError("At least one display option must be True")
|
|
381
|
+
|
|
382
|
+
fig, axes = plt.subplots(n_plots, 1, figsize=figsize)
|
|
383
|
+
if n_plots == 1:
|
|
384
|
+
axes = [axes]
|
|
385
|
+
|
|
386
|
+
# Time unit conversion
|
|
387
|
+
if time_unit == "auto":
|
|
388
|
+
max_time = np.max(time)
|
|
389
|
+
if max_time < 1e-6:
|
|
390
|
+
time_unit = "us"
|
|
391
|
+
time_mult = 1e6
|
|
392
|
+
elif max_time < 1e-3:
|
|
393
|
+
time_unit = "ms"
|
|
394
|
+
time_mult = 1e3
|
|
395
|
+
else:
|
|
396
|
+
time_unit = "s"
|
|
397
|
+
time_mult = 1.0
|
|
398
|
+
else:
|
|
399
|
+
time_mult = {"s": 1, "ms": 1e3, "us": 1e6, "ns": 1e9}.get(time_unit, 1.0)
|
|
400
|
+
|
|
401
|
+
time_scaled = time * time_mult
|
|
402
|
+
|
|
403
|
+
# Calculate DC level and ripple
|
|
404
|
+
dc_level = np.mean(voltage)
|
|
405
|
+
ac_ripple = voltage - dc_level
|
|
406
|
+
ripple_pp = np.ptp(ac_ripple)
|
|
407
|
+
ripple_rms = np.std(ac_ripple)
|
|
408
|
+
|
|
409
|
+
ax_idx = 0
|
|
410
|
+
|
|
411
|
+
# DC-coupled view
|
|
412
|
+
if show_dc:
|
|
413
|
+
ax = axes[ax_idx]
|
|
414
|
+
ax.plot(time_scaled, voltage, "#3498DB", linewidth=1)
|
|
415
|
+
ax.axhline(
|
|
416
|
+
dc_level, color="#E74C3C", linestyle="--", linewidth=1.5, label=f"DC: {dc_level:.3f}V"
|
|
417
|
+
)
|
|
418
|
+
ax.set_ylabel("Voltage (V)", fontsize=10)
|
|
419
|
+
ax.set_title("DC-Coupled Waveform", fontsize=10, fontweight="bold", loc="left")
|
|
420
|
+
ax.legend(loc="upper right", fontsize=9)
|
|
421
|
+
ax.grid(True, alpha=0.3)
|
|
422
|
+
ax_idx += 1
|
|
423
|
+
|
|
424
|
+
# AC-coupled (ripple only) view
|
|
425
|
+
if show_ac:
|
|
426
|
+
ax = axes[ax_idx]
|
|
427
|
+
ax.plot(time_scaled, ac_ripple * 1e3, "#27AE60", linewidth=1) # Convert to mV
|
|
428
|
+
ax.axhline(0, color="gray", linestyle="-", linewidth=0.5)
|
|
429
|
+
|
|
430
|
+
# Mark peak-to-peak
|
|
431
|
+
max_idx = np.argmax(ac_ripple)
|
|
432
|
+
min_idx = np.argmin(ac_ripple)
|
|
433
|
+
ax.annotate(
|
|
434
|
+
"",
|
|
435
|
+
xy=(time_scaled[max_idx], ac_ripple[max_idx] * 1e3),
|
|
436
|
+
xytext=(time_scaled[min_idx], ac_ripple[min_idx] * 1e3),
|
|
437
|
+
arrowprops={"arrowstyle": "<->", "color": "#E74C3C", "lw": 1.5},
|
|
438
|
+
)
|
|
439
|
+
|
|
440
|
+
ax.set_ylabel("Ripple (mV)", fontsize=10)
|
|
441
|
+
ax.set_title(
|
|
442
|
+
f"AC Ripple (pk-pk: {ripple_pp * 1e3:.2f}mV, RMS: {ripple_rms * 1e3:.2f}mV)",
|
|
443
|
+
fontsize=10,
|
|
444
|
+
fontweight="bold",
|
|
445
|
+
loc="left",
|
|
446
|
+
)
|
|
447
|
+
ax.grid(True, alpha=0.3)
|
|
448
|
+
ax_idx += 1
|
|
449
|
+
|
|
450
|
+
# Spectrum view
|
|
451
|
+
if show_spectrum:
|
|
452
|
+
ax = axes[ax_idx]
|
|
453
|
+
|
|
454
|
+
if sample_rate is None:
|
|
455
|
+
# Estimate from time array
|
|
456
|
+
sample_rate = 1 / (time[1] - time[0]) if len(time) > 1 else 1e6
|
|
457
|
+
|
|
458
|
+
n_fft = len(voltage)
|
|
459
|
+
freq = np.fft.rfftfreq(n_fft, 1 / sample_rate)
|
|
460
|
+
fft_mag = np.abs(np.fft.rfft(ac_ripple)) / n_fft * 2
|
|
461
|
+
fft_db = 20 * np.log10(fft_mag + 1e-12)
|
|
462
|
+
|
|
463
|
+
# Find dominant ripple frequency
|
|
464
|
+
peak_idx = np.argmax(fft_mag[1:]) + 1 # Skip DC
|
|
465
|
+
peak_freq = freq[peak_idx]
|
|
466
|
+
|
|
467
|
+
# Plot in kHz
|
|
468
|
+
freq_khz = freq / 1e3
|
|
469
|
+
ax.plot(freq_khz, fft_db, "#9B59B6", linewidth=1)
|
|
470
|
+
ax.plot(
|
|
471
|
+
freq_khz[peak_idx],
|
|
472
|
+
fft_db[peak_idx],
|
|
473
|
+
"ro",
|
|
474
|
+
markersize=8,
|
|
475
|
+
label=f"Peak: {peak_freq / 1e3:.1f}kHz",
|
|
476
|
+
)
|
|
477
|
+
|
|
478
|
+
ax.set_ylabel("Magnitude (dB)", fontsize=10)
|
|
479
|
+
ax.set_xlabel("Frequency (kHz)", fontsize=10)
|
|
480
|
+
ax.set_title("Ripple Spectrum", fontsize=10, fontweight="bold", loc="left")
|
|
481
|
+
ax.set_xlim(0, min(freq_khz[-1], sample_rate / 2e3))
|
|
482
|
+
ax.legend(loc="upper right", fontsize=9)
|
|
483
|
+
ax.grid(True, alpha=0.3)
|
|
484
|
+
else:
|
|
485
|
+
axes[-1].set_xlabel(f"Time ({time_unit})", fontsize=11)
|
|
486
|
+
|
|
487
|
+
if title:
|
|
488
|
+
fig.suptitle(title, fontsize=14, fontweight="bold")
|
|
489
|
+
else:
|
|
490
|
+
fig.suptitle("Ripple Analysis", fontsize=14, fontweight="bold")
|
|
491
|
+
|
|
492
|
+
fig.tight_layout()
|
|
493
|
+
|
|
494
|
+
if save_path is not None:
|
|
495
|
+
fig.savefig(save_path, dpi=300, bbox_inches="tight")
|
|
496
|
+
|
|
497
|
+
if show:
|
|
498
|
+
plt.show()
|
|
499
|
+
|
|
500
|
+
return fig
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def plot_loss_breakdown(
|
|
504
|
+
loss_values: dict[str, float],
|
|
505
|
+
*,
|
|
506
|
+
ax: Axes | None = None,
|
|
507
|
+
figsize: tuple[float, float] = (10, 8),
|
|
508
|
+
title: str | None = None,
|
|
509
|
+
show_watts: bool = True,
|
|
510
|
+
show: bool = True,
|
|
511
|
+
save_path: str | Path | None = None,
|
|
512
|
+
) -> Figure:
|
|
513
|
+
"""Plot power loss breakdown as pie chart.
|
|
514
|
+
|
|
515
|
+
Creates a pie chart showing the contribution of each loss mechanism
|
|
516
|
+
(switching, conduction, magnetic, etc.) to total power dissipation.
|
|
517
|
+
|
|
518
|
+
Args:
|
|
519
|
+
loss_values: Dictionary mapping loss type to value in Watts.
|
|
520
|
+
ax: Matplotlib axes.
|
|
521
|
+
figsize: Figure size.
|
|
522
|
+
title: Plot title.
|
|
523
|
+
show_watts: Show watt values on slices.
|
|
524
|
+
show: Display plot.
|
|
525
|
+
save_path: Save path.
|
|
526
|
+
|
|
527
|
+
Returns:
|
|
528
|
+
Matplotlib Figure object.
|
|
529
|
+
|
|
530
|
+
Example:
|
|
531
|
+
>>> losses = {
|
|
532
|
+
... "Switching": 0.5,
|
|
533
|
+
... "Conduction": 0.3,
|
|
534
|
+
... "Magnetic": 0.15,
|
|
535
|
+
... "Gate Drive": 0.05
|
|
536
|
+
... }
|
|
537
|
+
>>> fig = plot_loss_breakdown(losses)
|
|
538
|
+
"""
|
|
539
|
+
if not HAS_MATPLOTLIB:
|
|
540
|
+
raise ImportError("matplotlib is required for visualization")
|
|
541
|
+
|
|
542
|
+
if ax is None:
|
|
543
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
544
|
+
else:
|
|
545
|
+
fig_temp = ax.get_figure()
|
|
546
|
+
if fig_temp is None:
|
|
547
|
+
raise ValueError("Axes must have an associated figure")
|
|
548
|
+
fig = cast("Figure", fig_temp)
|
|
549
|
+
|
|
550
|
+
labels = list(loss_values.keys())
|
|
551
|
+
values = list(loss_values.values())
|
|
552
|
+
total_loss = sum(values)
|
|
553
|
+
|
|
554
|
+
# Color palette
|
|
555
|
+
colors = [
|
|
556
|
+
"#3498DB",
|
|
557
|
+
"#E74C3C",
|
|
558
|
+
"#27AE60",
|
|
559
|
+
"#9B59B6",
|
|
560
|
+
"#F39C12",
|
|
561
|
+
"#1ABC9C",
|
|
562
|
+
"#E67E22",
|
|
563
|
+
"#95A5A6",
|
|
564
|
+
]
|
|
565
|
+
|
|
566
|
+
# Format labels with percentages and watts
|
|
567
|
+
autopct_val: str | Callable[[float], str]
|
|
568
|
+
if show_watts:
|
|
569
|
+
|
|
570
|
+
def autopct_func(pct: float) -> str:
|
|
571
|
+
watts = pct / 100 * total_loss
|
|
572
|
+
return f"{pct:.1f}%\n({watts * 1e3:.1f}mW)"
|
|
573
|
+
|
|
574
|
+
autopct_val = autopct_func
|
|
575
|
+
else:
|
|
576
|
+
autopct_val = "%1.1f%%"
|
|
577
|
+
|
|
578
|
+
pie_result = ax.pie(
|
|
579
|
+
values,
|
|
580
|
+
labels=labels,
|
|
581
|
+
autopct=autopct_val, # type: ignore[arg-type]
|
|
582
|
+
colors=colors[: len(labels)],
|
|
583
|
+
startangle=90,
|
|
584
|
+
explode=[0.02] * len(labels),
|
|
585
|
+
shadow=True,
|
|
586
|
+
)
|
|
587
|
+
# ax.pie returns (wedges, texts, autotexts) when autopct is provided
|
|
588
|
+
# Unpack with length check for type safety
|
|
589
|
+
if len(pie_result) >= 3:
|
|
590
|
+
_wedges = pie_result[0]
|
|
591
|
+
_texts = pie_result[1]
|
|
592
|
+
autotexts = pie_result[2]
|
|
593
|
+
else:
|
|
594
|
+
autotexts = []
|
|
595
|
+
|
|
596
|
+
# Style autotexts
|
|
597
|
+
for autotext in autotexts:
|
|
598
|
+
autotext.set_fontsize(9)
|
|
599
|
+
autotext.set_fontweight("bold")
|
|
600
|
+
|
|
601
|
+
# Add total loss annotation
|
|
602
|
+
ax.text(
|
|
603
|
+
0,
|
|
604
|
+
-1.3,
|
|
605
|
+
f"Total Loss: {total_loss * 1e3:.1f}mW ({total_loss:.3f}W)",
|
|
606
|
+
ha="center",
|
|
607
|
+
fontsize=11,
|
|
608
|
+
fontweight="bold",
|
|
609
|
+
)
|
|
610
|
+
|
|
611
|
+
ax.set_aspect("equal")
|
|
612
|
+
|
|
613
|
+
if title:
|
|
614
|
+
ax.set_title(title, fontsize=12, fontweight="bold", pad=20)
|
|
615
|
+
else:
|
|
616
|
+
ax.set_title("Power Loss Breakdown", fontsize=12, fontweight="bold", pad=20)
|
|
617
|
+
|
|
618
|
+
fig.tight_layout()
|
|
619
|
+
|
|
620
|
+
if save_path is not None:
|
|
621
|
+
fig.savefig(save_path, dpi=300, bbox_inches="tight")
|
|
622
|
+
|
|
623
|
+
if show:
|
|
624
|
+
plt.show()
|
|
625
|
+
|
|
626
|
+
return fig
|