oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.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.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
"""Basic power analysis for TraceKit.
|
|
2
|
+
|
|
3
|
+
Provides fundamental power calculations including instantaneous power,
|
|
4
|
+
average power, RMS power, peak power, and energy.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.power.basic import instantaneous_power, power_statistics
|
|
9
|
+
>>> power_trace = instantaneous_power(voltage_trace, current_trace)
|
|
10
|
+
>>> stats = power_statistics(power_trace)
|
|
11
|
+
>>> print(f"Average: {stats['average']:.2f} W, Peak: {stats['peak']:.2f} W")
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from typing import Any
|
|
17
|
+
|
|
18
|
+
import numpy as np
|
|
19
|
+
from scipy import interpolate
|
|
20
|
+
|
|
21
|
+
from oscura.core.exceptions import AnalysisError
|
|
22
|
+
from oscura.core.types import TraceMetadata, WaveformTrace
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def instantaneous_power(
|
|
26
|
+
voltage: WaveformTrace,
|
|
27
|
+
current: WaveformTrace,
|
|
28
|
+
*,
|
|
29
|
+
interpolate_if_needed: bool = True,
|
|
30
|
+
) -> WaveformTrace:
|
|
31
|
+
"""Calculate instantaneous power from voltage and current traces.
|
|
32
|
+
|
|
33
|
+
P(t) = V(t) * I(t)
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
voltage: Voltage waveform trace.
|
|
37
|
+
current: Current waveform trace.
|
|
38
|
+
interpolate_if_needed: If True, interpolate if sample rates differ.
|
|
39
|
+
|
|
40
|
+
Returns:
|
|
41
|
+
Power waveform trace (in Watts if inputs are V and A).
|
|
42
|
+
|
|
43
|
+
Raises:
|
|
44
|
+
AnalysisError: If sample rates mismatch and interpolation disabled.
|
|
45
|
+
|
|
46
|
+
Example:
|
|
47
|
+
>>> power = instantaneous_power(v_trace, i_trace)
|
|
48
|
+
>>> print(f"Peak power: {np.max(power.data):.2f} W")
|
|
49
|
+
|
|
50
|
+
References:
|
|
51
|
+
IEC 61000-4-7 (power measurements)
|
|
52
|
+
"""
|
|
53
|
+
v_data = voltage.data
|
|
54
|
+
i_data = current.data
|
|
55
|
+
v_rate = voltage.metadata.sample_rate
|
|
56
|
+
i_rate = current.metadata.sample_rate
|
|
57
|
+
|
|
58
|
+
# Handle different sample rates
|
|
59
|
+
if v_rate != i_rate:
|
|
60
|
+
if not interpolate_if_needed:
|
|
61
|
+
raise AnalysisError(
|
|
62
|
+
f"Sample rate mismatch: voltage={v_rate}, current={i_rate}. "
|
|
63
|
+
"Set interpolate_if_needed=True to interpolate."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
# Use higher sample rate and interpolate the other
|
|
67
|
+
if v_rate > i_rate:
|
|
68
|
+
# Interpolate current to voltage sample rate
|
|
69
|
+
t_current = np.arange(len(i_data)) / i_rate
|
|
70
|
+
t_voltage = np.arange(len(v_data)) / v_rate
|
|
71
|
+
interp = interpolate.interp1d(
|
|
72
|
+
t_current, i_data, kind="linear", fill_value="extrapolate"
|
|
73
|
+
)
|
|
74
|
+
i_data = interp(t_voltage)
|
|
75
|
+
sample_rate = v_rate
|
|
76
|
+
else:
|
|
77
|
+
# Interpolate voltage to current sample rate
|
|
78
|
+
t_voltage = np.arange(len(v_data)) / v_rate
|
|
79
|
+
t_current = np.arange(len(i_data)) / i_rate
|
|
80
|
+
interp = interpolate.interp1d(
|
|
81
|
+
t_voltage, v_data, kind="linear", fill_value="extrapolate"
|
|
82
|
+
)
|
|
83
|
+
v_data = interp(t_current)
|
|
84
|
+
sample_rate = i_rate
|
|
85
|
+
else:
|
|
86
|
+
sample_rate = v_rate
|
|
87
|
+
|
|
88
|
+
# Handle different lengths
|
|
89
|
+
min_len = min(len(v_data), len(i_data))
|
|
90
|
+
v_data = v_data[:min_len]
|
|
91
|
+
i_data = i_data[:min_len]
|
|
92
|
+
|
|
93
|
+
# Calculate instantaneous power
|
|
94
|
+
power_data = v_data * i_data
|
|
95
|
+
|
|
96
|
+
return WaveformTrace(
|
|
97
|
+
data=power_data.astype(np.float64),
|
|
98
|
+
metadata=TraceMetadata(sample_rate=sample_rate),
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def average_power(
|
|
103
|
+
power: WaveformTrace | None = None,
|
|
104
|
+
*,
|
|
105
|
+
voltage: WaveformTrace | None = None,
|
|
106
|
+
current: WaveformTrace | None = None,
|
|
107
|
+
) -> float:
|
|
108
|
+
"""Calculate average (mean) power.
|
|
109
|
+
|
|
110
|
+
P_avg = (1/T) * integral(P(t) dt)
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
power: Power trace (if already calculated).
|
|
114
|
+
voltage: Voltage trace (alternative to power).
|
|
115
|
+
current: Current trace (alternative to power).
|
|
116
|
+
|
|
117
|
+
Returns:
|
|
118
|
+
Average power in Watts.
|
|
119
|
+
|
|
120
|
+
Raises:
|
|
121
|
+
AnalysisError: If neither power nor both voltage and current provided.
|
|
122
|
+
|
|
123
|
+
Example:
|
|
124
|
+
>>> p_avg = average_power(power_trace)
|
|
125
|
+
>>> # or
|
|
126
|
+
>>> p_avg = average_power(voltage=v, current=i)
|
|
127
|
+
"""
|
|
128
|
+
if power is None:
|
|
129
|
+
if voltage is None or current is None:
|
|
130
|
+
raise AnalysisError("Either power trace or both voltage and current traces required")
|
|
131
|
+
power = instantaneous_power(voltage, current)
|
|
132
|
+
|
|
133
|
+
return float(np.mean(power.data))
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def rms_power(
|
|
137
|
+
power: WaveformTrace | None = None,
|
|
138
|
+
*,
|
|
139
|
+
voltage: WaveformTrace | None = None,
|
|
140
|
+
current: WaveformTrace | None = None,
|
|
141
|
+
) -> float:
|
|
142
|
+
"""Calculate RMS power.
|
|
143
|
+
|
|
144
|
+
P_rms = sqrt(mean(P(t)^2))
|
|
145
|
+
|
|
146
|
+
Args:
|
|
147
|
+
power: Power trace.
|
|
148
|
+
voltage: Voltage trace (alternative).
|
|
149
|
+
current: Current trace (alternative).
|
|
150
|
+
|
|
151
|
+
Returns:
|
|
152
|
+
RMS power in Watts.
|
|
153
|
+
|
|
154
|
+
Raises:
|
|
155
|
+
AnalysisError: If neither power nor both voltage and current provided.
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
>>> p_rms = rms_power(power_trace)
|
|
159
|
+
"""
|
|
160
|
+
if power is None:
|
|
161
|
+
if voltage is None or current is None:
|
|
162
|
+
raise AnalysisError("Either power trace or both voltage and current traces required")
|
|
163
|
+
power = instantaneous_power(voltage, current)
|
|
164
|
+
|
|
165
|
+
return float(np.sqrt(np.mean(power.data**2)))
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def peak_power(
|
|
169
|
+
power: WaveformTrace | None = None,
|
|
170
|
+
*,
|
|
171
|
+
voltage: WaveformTrace | None = None,
|
|
172
|
+
current: WaveformTrace | None = None,
|
|
173
|
+
absolute: bool = True,
|
|
174
|
+
) -> float:
|
|
175
|
+
"""Calculate peak power.
|
|
176
|
+
|
|
177
|
+
Args:
|
|
178
|
+
power: Power trace.
|
|
179
|
+
voltage: Voltage trace (alternative).
|
|
180
|
+
current: Current trace (alternative).
|
|
181
|
+
absolute: If True, return absolute maximum. If False, maximum value.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Peak power in Watts.
|
|
185
|
+
|
|
186
|
+
Raises:
|
|
187
|
+
AnalysisError: If neither power nor both voltage and current provided.
|
|
188
|
+
|
|
189
|
+
Example:
|
|
190
|
+
>>> p_peak = peak_power(power_trace)
|
|
191
|
+
"""
|
|
192
|
+
if power is None:
|
|
193
|
+
if voltage is None or current is None:
|
|
194
|
+
raise AnalysisError("Either power trace or both voltage and current traces required")
|
|
195
|
+
power = instantaneous_power(voltage, current)
|
|
196
|
+
|
|
197
|
+
if absolute:
|
|
198
|
+
return float(np.max(np.abs(power.data)))
|
|
199
|
+
return float(np.max(power.data))
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
def energy(
|
|
203
|
+
power: WaveformTrace | None = None,
|
|
204
|
+
*,
|
|
205
|
+
voltage: WaveformTrace | None = None,
|
|
206
|
+
current: WaveformTrace | None = None,
|
|
207
|
+
start_time: float | None = None,
|
|
208
|
+
end_time: float | None = None,
|
|
209
|
+
) -> float:
|
|
210
|
+
"""Calculate energy (integral of power over time).
|
|
211
|
+
|
|
212
|
+
E = integral(P(t) dt)
|
|
213
|
+
|
|
214
|
+
Args:
|
|
215
|
+
power: Power trace.
|
|
216
|
+
voltage: Voltage trace (alternative).
|
|
217
|
+
current: Current trace (alternative).
|
|
218
|
+
start_time: Start time for integration (seconds).
|
|
219
|
+
end_time: End time for integration (seconds).
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Energy in Joules.
|
|
223
|
+
|
|
224
|
+
Raises:
|
|
225
|
+
AnalysisError: If neither power nor both voltage and current provided.
|
|
226
|
+
|
|
227
|
+
Example:
|
|
228
|
+
>>> e = energy(power_trace)
|
|
229
|
+
>>> print(f"Total energy: {e*1e3:.2f} mJ")
|
|
230
|
+
"""
|
|
231
|
+
if power is None:
|
|
232
|
+
if voltage is None or current is None:
|
|
233
|
+
raise AnalysisError("Either power trace or both voltage and current traces required")
|
|
234
|
+
power = instantaneous_power(voltage, current)
|
|
235
|
+
|
|
236
|
+
data = power.data
|
|
237
|
+
sample_period = power.metadata.time_base
|
|
238
|
+
|
|
239
|
+
# Apply time limits
|
|
240
|
+
if start_time is not None or end_time is not None:
|
|
241
|
+
time_vector = np.arange(len(data)) * sample_period
|
|
242
|
+
if start_time is not None:
|
|
243
|
+
mask = time_vector >= start_time
|
|
244
|
+
else:
|
|
245
|
+
mask = np.ones(len(data), dtype=bool)
|
|
246
|
+
if end_time is not None:
|
|
247
|
+
mask &= time_vector <= end_time
|
|
248
|
+
data = data[mask]
|
|
249
|
+
|
|
250
|
+
# Integrate using trapezoidal rule (scipy is stable across versions)
|
|
251
|
+
from scipy.integrate import trapezoid
|
|
252
|
+
|
|
253
|
+
return float(trapezoid(data, dx=sample_period))
|
|
254
|
+
|
|
255
|
+
|
|
256
|
+
def power_statistics(
|
|
257
|
+
power: WaveformTrace | None = None,
|
|
258
|
+
*,
|
|
259
|
+
voltage: WaveformTrace | None = None,
|
|
260
|
+
current: WaveformTrace | None = None,
|
|
261
|
+
) -> dict[str, float]:
|
|
262
|
+
"""Calculate comprehensive power statistics.
|
|
263
|
+
|
|
264
|
+
Args:
|
|
265
|
+
power: Power trace.
|
|
266
|
+
voltage: Voltage trace (alternative).
|
|
267
|
+
current: Current trace (alternative).
|
|
268
|
+
|
|
269
|
+
Returns:
|
|
270
|
+
Dictionary with:
|
|
271
|
+
- average: Mean power
|
|
272
|
+
- rms: RMS power
|
|
273
|
+
- peak: Peak power (absolute)
|
|
274
|
+
- peak_positive: Maximum positive power
|
|
275
|
+
- peak_negative: Maximum negative power (regeneration)
|
|
276
|
+
- energy: Total energy
|
|
277
|
+
- min: Minimum power value
|
|
278
|
+
- std: Standard deviation
|
|
279
|
+
|
|
280
|
+
Raises:
|
|
281
|
+
AnalysisError: If neither power nor both voltage and current provided.
|
|
282
|
+
|
|
283
|
+
Example:
|
|
284
|
+
>>> stats = power_statistics(voltage=v, current=i)
|
|
285
|
+
>>> print(f"Average: {stats['average']:.2f} W")
|
|
286
|
+
>>> print(f"Peak: {stats['peak']:.2f} W")
|
|
287
|
+
>>> print(f"Energy: {stats['energy']*1e3:.2f} mJ")
|
|
288
|
+
"""
|
|
289
|
+
if power is None:
|
|
290
|
+
if voltage is None or current is None:
|
|
291
|
+
raise AnalysisError("Either power trace or both voltage and current traces required")
|
|
292
|
+
power = instantaneous_power(voltage, current)
|
|
293
|
+
|
|
294
|
+
data = power.data
|
|
295
|
+
sample_period = power.metadata.time_base
|
|
296
|
+
|
|
297
|
+
# Use scipy trapezoid for stable API across NumPy versions
|
|
298
|
+
from scipy.integrate import trapezoid
|
|
299
|
+
|
|
300
|
+
return {
|
|
301
|
+
"average": float(np.mean(data)),
|
|
302
|
+
"rms": float(np.sqrt(np.mean(data**2))),
|
|
303
|
+
"peak": float(np.max(np.abs(data))),
|
|
304
|
+
"peak_positive": float(np.max(data)),
|
|
305
|
+
"peak_negative": float(np.min(data)),
|
|
306
|
+
"energy": float(trapezoid(data, dx=sample_period)),
|
|
307
|
+
"min": float(np.min(data)),
|
|
308
|
+
"std": float(np.std(data)),
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def power_profile(
|
|
313
|
+
voltage: WaveformTrace,
|
|
314
|
+
current: WaveformTrace,
|
|
315
|
+
*,
|
|
316
|
+
window_size: int | None = None,
|
|
317
|
+
) -> dict[str, Any]:
|
|
318
|
+
"""Generate power profile with rolling statistics.
|
|
319
|
+
|
|
320
|
+
Args:
|
|
321
|
+
voltage: Voltage trace.
|
|
322
|
+
current: Current trace.
|
|
323
|
+
window_size: Window size for rolling calculations. If None, auto-select.
|
|
324
|
+
|
|
325
|
+
Returns:
|
|
326
|
+
Dictionary with:
|
|
327
|
+
- power_trace: Instantaneous power trace
|
|
328
|
+
- rolling_avg: Rolling average power
|
|
329
|
+
- rolling_peak: Rolling peak power
|
|
330
|
+
- cumulative_energy: Cumulative energy over time
|
|
331
|
+
- statistics: Overall power statistics
|
|
332
|
+
"""
|
|
333
|
+
power = instantaneous_power(voltage, current)
|
|
334
|
+
data = power.data
|
|
335
|
+
sample_period = power.metadata.time_base
|
|
336
|
+
|
|
337
|
+
if window_size is None:
|
|
338
|
+
# Auto-select: ~1% of total samples or 100, whichever is larger
|
|
339
|
+
window_size = max(100, len(data) // 100)
|
|
340
|
+
|
|
341
|
+
# Ensure window size is odd for centered window
|
|
342
|
+
if window_size % 2 == 0:
|
|
343
|
+
window_size += 1
|
|
344
|
+
|
|
345
|
+
# Rolling average
|
|
346
|
+
kernel = np.ones(window_size) / window_size
|
|
347
|
+
rolling_avg = np.convolve(data, kernel, mode="same")
|
|
348
|
+
|
|
349
|
+
# Rolling peak (using scipy.ndimage.maximum_filter would be faster but numpy-only)
|
|
350
|
+
from scipy.ndimage import maximum_filter1d
|
|
351
|
+
|
|
352
|
+
rolling_peak = maximum_filter1d(np.abs(data), size=window_size)
|
|
353
|
+
|
|
354
|
+
# Cumulative energy
|
|
355
|
+
cumulative_energy = np.cumsum(data) * sample_period
|
|
356
|
+
|
|
357
|
+
return {
|
|
358
|
+
"power_trace": power,
|
|
359
|
+
"rolling_avg": WaveformTrace(
|
|
360
|
+
data=rolling_avg.astype(np.float64),
|
|
361
|
+
metadata=power.metadata,
|
|
362
|
+
),
|
|
363
|
+
"rolling_peak": WaveformTrace(
|
|
364
|
+
data=rolling_peak.astype(np.float64),
|
|
365
|
+
metadata=power.metadata,
|
|
366
|
+
),
|
|
367
|
+
"cumulative_energy": WaveformTrace(
|
|
368
|
+
data=cumulative_energy.astype(np.float64),
|
|
369
|
+
metadata=power.metadata,
|
|
370
|
+
),
|
|
371
|
+
"statistics": power_statistics(power),
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
|
|
375
|
+
__all__ = [
|
|
376
|
+
"average_power",
|
|
377
|
+
"energy",
|
|
378
|
+
"instantaneous_power",
|
|
379
|
+
"peak_power",
|
|
380
|
+
"power_profile",
|
|
381
|
+
"power_statistics",
|
|
382
|
+
"rms_power",
|
|
383
|
+
]
|
|
@@ -0,0 +1,314 @@
|
|
|
1
|
+
"""Conduction loss analysis for TraceKit.
|
|
2
|
+
|
|
3
|
+
Provides conduction loss calculations for power semiconductor devices
|
|
4
|
+
during their on-state.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.power.conduction import conduction_loss
|
|
9
|
+
>>> p_cond = conduction_loss(v_on_trace, i_d_trace, duty_cycle=0.5)
|
|
10
|
+
>>> print(f"Conduction loss: {p_cond:.2f} W")
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
from typing import TYPE_CHECKING
|
|
16
|
+
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
from oscura.core.exceptions import AnalysisError
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from oscura.core.types import WaveformTrace
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def conduction_loss(
|
|
26
|
+
voltage: WaveformTrace,
|
|
27
|
+
current: WaveformTrace,
|
|
28
|
+
duty_cycle: float | None = None,
|
|
29
|
+
) -> float:
|
|
30
|
+
"""Calculate conduction loss during on-state.
|
|
31
|
+
|
|
32
|
+
P_cond = V_on * I_on * D (steady state)
|
|
33
|
+
or
|
|
34
|
+
P_cond = mean(V(t) * I(t)) over on-state periods
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
voltage: On-state voltage trace (V_ds(on) or V_ce(sat)).
|
|
38
|
+
current: On-state current trace.
|
|
39
|
+
duty_cycle: Duty cycle (0 to 1). If None, calculates from waveforms.
|
|
40
|
+
|
|
41
|
+
Returns:
|
|
42
|
+
Conduction loss in Watts.
|
|
43
|
+
|
|
44
|
+
Example:
|
|
45
|
+
>>> p_cond = conduction_loss(v_on, i_d, duty_cycle=0.5)
|
|
46
|
+
>>> print(f"Conduction loss: {p_cond:.2f} W")
|
|
47
|
+
|
|
48
|
+
References:
|
|
49
|
+
Infineon Application Note AN-9010
|
|
50
|
+
"""
|
|
51
|
+
v_data = voltage.data
|
|
52
|
+
i_data = current.data
|
|
53
|
+
|
|
54
|
+
# Ensure same length
|
|
55
|
+
min_len = min(len(v_data), len(i_data))
|
|
56
|
+
v_data = v_data[:min_len]
|
|
57
|
+
i_data = i_data[:min_len]
|
|
58
|
+
|
|
59
|
+
if duty_cycle is not None:
|
|
60
|
+
# Use average values and duty cycle
|
|
61
|
+
v_on = float(np.mean(v_data))
|
|
62
|
+
i_on = float(np.mean(i_data))
|
|
63
|
+
return v_on * i_on * duty_cycle
|
|
64
|
+
else:
|
|
65
|
+
# Calculate instantaneous power and average
|
|
66
|
+
power = v_data * i_data
|
|
67
|
+
return float(np.mean(power))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def on_resistance(
|
|
71
|
+
voltage: WaveformTrace,
|
|
72
|
+
current: WaveformTrace,
|
|
73
|
+
*,
|
|
74
|
+
min_current: float | None = None,
|
|
75
|
+
min_current_fraction: float = 0.1,
|
|
76
|
+
) -> float:
|
|
77
|
+
"""Calculate on-state resistance (R_ds(on) or R_ce(sat)).
|
|
78
|
+
|
|
79
|
+
R_on = V_on / I_on
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
voltage: On-state voltage trace.
|
|
83
|
+
current: On-state current trace.
|
|
84
|
+
min_current: Minimum current threshold (to avoid division by small values).
|
|
85
|
+
If None, uses min_current_fraction * peak current.
|
|
86
|
+
min_current_fraction: Fraction of peak current to use as minimum threshold
|
|
87
|
+
when min_current is None (default: 0.1 = 10% of peak).
|
|
88
|
+
|
|
89
|
+
Returns:
|
|
90
|
+
On-state resistance in Ohms.
|
|
91
|
+
|
|
92
|
+
Example:
|
|
93
|
+
>>> r_on = on_resistance(v_ds, i_d)
|
|
94
|
+
>>> print(f"R_ds(on): {r_on*1e3:.2f} mOhm")
|
|
95
|
+
>>> # With tighter threshold (5% of peak):
|
|
96
|
+
>>> r_on = on_resistance(v_ds, i_d, min_current_fraction=0.05)
|
|
97
|
+
"""
|
|
98
|
+
v_data = voltage.data
|
|
99
|
+
i_data = current.data
|
|
100
|
+
|
|
101
|
+
min_len = min(len(v_data), len(i_data))
|
|
102
|
+
v_data = v_data[:min_len]
|
|
103
|
+
i_data = i_data[:min_len]
|
|
104
|
+
|
|
105
|
+
# Filter by minimum current
|
|
106
|
+
if min_current is None:
|
|
107
|
+
min_current = min_current_fraction * np.max(np.abs(i_data))
|
|
108
|
+
|
|
109
|
+
mask = np.abs(i_data) >= min_current
|
|
110
|
+
if not np.any(mask):
|
|
111
|
+
return np.nan # type: ignore[no-any-return]
|
|
112
|
+
|
|
113
|
+
v_on = v_data[mask]
|
|
114
|
+
i_on = i_data[mask]
|
|
115
|
+
|
|
116
|
+
# Linear fit to get resistance
|
|
117
|
+
# V = I * R -> slope is R
|
|
118
|
+
coeffs = np.polyfit(i_on, v_on, 1)
|
|
119
|
+
return float(coeffs[0])
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def forward_voltage(
|
|
123
|
+
voltage: WaveformTrace,
|
|
124
|
+
current: WaveformTrace,
|
|
125
|
+
*,
|
|
126
|
+
current_threshold: float | None = None,
|
|
127
|
+
current_threshold_fraction: float = 0.1,
|
|
128
|
+
threshold_window: float = 0.1,
|
|
129
|
+
) -> float:
|
|
130
|
+
"""Calculate forward voltage drop (for diodes or IGBT V_ce(sat)).
|
|
131
|
+
|
|
132
|
+
Extracts the voltage at a reference current level.
|
|
133
|
+
|
|
134
|
+
Args:
|
|
135
|
+
voltage: Forward voltage trace.
|
|
136
|
+
current: Forward current trace.
|
|
137
|
+
current_threshold: Current level at which to measure Vf.
|
|
138
|
+
If None, uses current_threshold_fraction * peak current.
|
|
139
|
+
current_threshold_fraction: Fraction of peak current to use as threshold
|
|
140
|
+
when current_threshold is None (default: 0.1 = 10% of peak).
|
|
141
|
+
threshold_window: Tolerance window around the current threshold,
|
|
142
|
+
as a fraction of the threshold (default: 0.1 = +/-10% window).
|
|
143
|
+
Samples within this window are averaged to compute Vf.
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
Forward voltage in Volts.
|
|
147
|
+
|
|
148
|
+
Example:
|
|
149
|
+
>>> vf = forward_voltage(v_f, i_f)
|
|
150
|
+
>>> print(f"Forward voltage: {vf:.2f} V")
|
|
151
|
+
>>> # With tighter window for more precise measurement:
|
|
152
|
+
>>> vf = forward_voltage(v_f, i_f, threshold_window=0.05)
|
|
153
|
+
"""
|
|
154
|
+
v_data = voltage.data
|
|
155
|
+
i_data = current.data
|
|
156
|
+
|
|
157
|
+
min_len = min(len(v_data), len(i_data))
|
|
158
|
+
v_data = v_data[:min_len]
|
|
159
|
+
i_data = i_data[:min_len]
|
|
160
|
+
|
|
161
|
+
if current_threshold is None:
|
|
162
|
+
current_threshold = current_threshold_fraction * np.max(np.abs(i_data))
|
|
163
|
+
|
|
164
|
+
# Find samples near the current threshold
|
|
165
|
+
near_threshold = np.abs(i_data - current_threshold) < threshold_window * current_threshold
|
|
166
|
+
if not np.any(near_threshold):
|
|
167
|
+
# Interpolate
|
|
168
|
+
idx = np.argmin(np.abs(i_data - current_threshold))
|
|
169
|
+
return float(v_data[idx])
|
|
170
|
+
|
|
171
|
+
return float(np.mean(v_data[near_threshold]))
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
def duty_cycle_weighted_loss(
|
|
175
|
+
losses: list[tuple[float, float]],
|
|
176
|
+
) -> float:
|
|
177
|
+
"""Calculate total loss from multiple operating points.
|
|
178
|
+
|
|
179
|
+
Useful for variable duty cycle or multi-mode operation.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
losses: List of (loss_watts, duty_cycle) tuples.
|
|
183
|
+
|
|
184
|
+
Returns:
|
|
185
|
+
Total weighted average loss in Watts.
|
|
186
|
+
|
|
187
|
+
Raises:
|
|
188
|
+
AnalysisError: If total duty cycle exceeds 1.0
|
|
189
|
+
|
|
190
|
+
Example:
|
|
191
|
+
>>> # 10W at 30% duty, 5W at 50% duty
|
|
192
|
+
>>> total = duty_cycle_weighted_loss([(10, 0.3), (5, 0.5)])
|
|
193
|
+
"""
|
|
194
|
+
total = 0.0
|
|
195
|
+
total_duty = 0.0
|
|
196
|
+
|
|
197
|
+
for loss, duty in losses:
|
|
198
|
+
total += loss * duty
|
|
199
|
+
total_duty += duty
|
|
200
|
+
|
|
201
|
+
if total_duty > 1.0:
|
|
202
|
+
raise AnalysisError(f"Total duty cycle exceeds 1.0: {total_duty}")
|
|
203
|
+
|
|
204
|
+
return total
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
def temperature_derating(
|
|
208
|
+
r_on_25c: float,
|
|
209
|
+
temperature: float,
|
|
210
|
+
temp_coefficient: float = 0.004,
|
|
211
|
+
) -> float:
|
|
212
|
+
"""Calculate temperature-derated on-resistance.
|
|
213
|
+
|
|
214
|
+
R_on(T) = R_on(25C) * (1 + alpha * (T - 25))
|
|
215
|
+
|
|
216
|
+
Args:
|
|
217
|
+
r_on_25c: On-resistance at 25C in Ohms.
|
|
218
|
+
temperature: Operating temperature in Celsius.
|
|
219
|
+
temp_coefficient: Temperature coefficient (default 0.4%/C for Si MOSFET).
|
|
220
|
+
|
|
221
|
+
Returns:
|
|
222
|
+
Derated on-resistance in Ohms.
|
|
223
|
+
|
|
224
|
+
Example:
|
|
225
|
+
>>> r_on_100c = temperature_derating(0.010, 100) # 10mOhm at 25C
|
|
226
|
+
>>> print(f"R_on at 100C: {r_on_100c*1e3:.2f} mOhm")
|
|
227
|
+
"""
|
|
228
|
+
return r_on_25c * (1 + temp_coefficient * (temperature - 25))
|
|
229
|
+
|
|
230
|
+
|
|
231
|
+
def mosfet_conduction_loss(
|
|
232
|
+
i_rms: float,
|
|
233
|
+
r_ds_on: float,
|
|
234
|
+
temperature: float = 25.0,
|
|
235
|
+
temp_coefficient: float = 0.004,
|
|
236
|
+
) -> float:
|
|
237
|
+
"""Calculate MOSFET conduction loss.
|
|
238
|
+
|
|
239
|
+
P_cond = I_rms^2 * R_ds(on)
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
i_rms: RMS drain current in Amps.
|
|
243
|
+
r_ds_on: On-state resistance at 25C in Ohms.
|
|
244
|
+
temperature: Junction temperature in Celsius.
|
|
245
|
+
temp_coefficient: Temperature coefficient.
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Conduction loss in Watts.
|
|
249
|
+
|
|
250
|
+
Example:
|
|
251
|
+
>>> p = mosfet_conduction_loss(i_rms=10, r_ds_on=0.010, temperature=100)
|
|
252
|
+
"""
|
|
253
|
+
r_on = temperature_derating(r_ds_on, temperature, temp_coefficient)
|
|
254
|
+
return i_rms**2 * r_on
|
|
255
|
+
|
|
256
|
+
|
|
257
|
+
def diode_conduction_loss(
|
|
258
|
+
i_avg: float,
|
|
259
|
+
i_rms: float,
|
|
260
|
+
v_f: float,
|
|
261
|
+
r_d: float = 0.0,
|
|
262
|
+
) -> float:
|
|
263
|
+
"""Calculate diode conduction loss.
|
|
264
|
+
|
|
265
|
+
P_cond = V_f * I_avg + r_d * I_rms^2
|
|
266
|
+
|
|
267
|
+
Args:
|
|
268
|
+
i_avg: Average forward current in Amps.
|
|
269
|
+
i_rms: RMS forward current in Amps.
|
|
270
|
+
v_f: Forward voltage drop in Volts.
|
|
271
|
+
r_d: Dynamic resistance in Ohms.
|
|
272
|
+
|
|
273
|
+
Returns:
|
|
274
|
+
Conduction loss in Watts.
|
|
275
|
+
|
|
276
|
+
Example:
|
|
277
|
+
>>> p = diode_conduction_loss(i_avg=5, i_rms=7, v_f=0.7, r_d=0.01)
|
|
278
|
+
"""
|
|
279
|
+
return v_f * i_avg + r_d * i_rms**2
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def igbt_conduction_loss(
|
|
283
|
+
i_c: float,
|
|
284
|
+
v_ce_sat: float,
|
|
285
|
+
r_c: float = 0.0,
|
|
286
|
+
) -> float:
|
|
287
|
+
"""Calculate IGBT conduction loss.
|
|
288
|
+
|
|
289
|
+
P_cond = V_ce(sat) * I_c + r_c * I_c^2
|
|
290
|
+
|
|
291
|
+
Args:
|
|
292
|
+
i_c: Collector current in Amps.
|
|
293
|
+
v_ce_sat: Collector-emitter saturation voltage in Volts.
|
|
294
|
+
r_c: Collector resistance in Ohms.
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
Conduction loss in Watts.
|
|
298
|
+
|
|
299
|
+
Example:
|
|
300
|
+
>>> p = igbt_conduction_loss(i_c=50, v_ce_sat=2.0, r_c=0.01)
|
|
301
|
+
"""
|
|
302
|
+
return v_ce_sat * i_c + r_c * i_c**2
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
__all__ = [
|
|
306
|
+
"conduction_loss",
|
|
307
|
+
"diode_conduction_loss",
|
|
308
|
+
"duty_cycle_weighted_loss",
|
|
309
|
+
"forward_voltage",
|
|
310
|
+
"igbt_conduction_loss",
|
|
311
|
+
"mosfet_conduction_loss",
|
|
312
|
+
"on_resistance",
|
|
313
|
+
"temperature_derating",
|
|
314
|
+
]
|