Tutorial — Bode RLC da Script Workspace

Da undici acquisizioni di oscilloscopio a un Dataset derivato e a un normale grafico GX-TXT persistente.

Indice

Questo tutorial mostra un workflow completo eseguito nel GX-TXT Octave Script Workspace. Il caso scientifico è un sweep Bode di un circuito RLC serie, ma il risultato didattico più importante è generale: uno script .m eseguito dal runtime GNU Octave di GX-TXT può produrre Dataset e grafici pienamente nativi del framework, senza scrivere file di stato privati e senza aprire una via grafica separata.

Risultato da tenere a mente. Lo script non salva semplicemente un PNG. Pubblica prima il Dataset tutorial.rlc_bode_experimental, poi crea una normale Variant plot.dataset.xy con PLOT_DATASET_API_v3/create_plot. Il grafico appare nella pagina Plots e usa gli stessi controlli Axes, Legend, Graphics, Annotation, Variants, rerender ed export degli altri grafici GX-TXT.
Versione verificata. Il flusso è stato provato con GX-TXT 0.9.8.10-dev-037 e GNU Octave 9.4. Il descriptor depositato nella dev-037 espone analysis.octave_script 1.3.0. La caratteristica indispensabile per questo tutorial è l'esposizione Script Bridge di PLOT_DATASET_API_v3/create_plot, introdotta nella dev-037.

Scarica lo script Octave provato (.m)

1. Perché questo esempio è utile

Input

Più Dataset reali

Lo script riceve undici acquisizioni come normali Analysis Sources e lavora con gli ID logici dei Dataset, non con percorsi privati.

Calcolo

API numerica condivisa

Il fit sinusoidale usa NUMERIC_API_v1, compresa la risoluzione matriciale dei due canali in un'unica operazione.

Output scientifico

Dataset canonico

Frequenza, guadagno, fase e diagnostica del fit diventano un Dataset GX-TXT riutilizzabile da altri moduli.

Presentazione

Grafico del framework

Il PlotSpec a due pannelli viene registrato come normale Variant di plot.dataset, non come immagine privata dello script.

2. Il problema scientifico

Il circuito di prova è un RLC serie con uscita misurata ai capi della resistenza. I valori nominali sono:

La frequenza di risonanza ideale è

\[ f_0 = \frac{1}{2\pi\sqrt{LC}} \approx 1591.55\;\mathrm{Hz}. \]

Il dataset di sweep comprende un punto acquisito a circa 1598 Hz, dove il risultato estratto è vicino a 0 dB e la fase è prossima a zero: il comportamento atteso per la tensione sulla resistenza in prossimità della risonanza.

3. Architettura del workflow

undici waveform Dataset
        ↓
Analysis Source Map
        ↓
Schema + metadata pubblici
        ↓
lettura CANONICAL_DATASET_API_v2
        ↓
fit sinusoidale con NUMERIC_API_v1
        ↓
frequenza, gain, phase, diagnostica
        ↓
DATASET_PUBLICATION_API_v2
        ↓
tutorial.rlc_bode_experimental
        ↓
PLOT_DATASET_API_v3 / new_spec + create_plot
        ↓
plot.dataset.xy : Variant persistente
        ↓
Plots / Axes / Legend / Graphics / Variants / export

Questa separazione è fondamentale: il Dataset derivato è il risultato scientifico; il grafico è una rappresentazione persistente di quel Dataset. Ricalcolo e presentazione restano quindi concettualmente distinti.

4. Registrare lo script e collegare le sorgenti

Il percorso riproducibile usa uno script registrato nella Session. Nel pannello di configurazione di analysis.octave_script si seleziona registered_script e si associano le acquisizioni tramite Analysis Source Map.

Configurazione Octave Script con undici Analysis Sources
Figura 1 — Script registrato e undici Dataset collegati come Analysis Sources. Il numero di sorgenti è dinamico: lo script raccoglie tutti gli alias che possiedono un dataset_id.

All'inizio dello script il contesto viene ottenuto con:

ctx = gxtxt_context();

aliases = fieldnames(ctx.inputs);
source_ids = {};
for k = 1:numel(aliases)
  item = ctx.inputs.(aliases{k});
  if (isstruct(item) && isfield(item,'dataset_id') && !isempty(item.dataset_id))
    source_ids{end+1} = item.dataset_id;
  endif
endfor
source_ids = unique(source_ids,'stable');

La conseguenza è importante: lo script non deve sapere dove siano fisicamente memorizzati i CSV/HDF5 della Session. Gli bastano le identità pubbliche dei Dataset.

5. Preflight delle API

Lo script dichiara subito le API di cui ha bisogno. Se una manca o è incompatibile, il run fallisce prima di produrre un risultato parziale:

gxtxt_api_require({
  'CANONICAL_DATASET_API_v2@2',
  'DATASET_VARIABLE_SCHEMA_API_v3@3',
  'DATASET_METADATA_API_v1@1',
  'NUMERIC_API_v1@1:math.numeric+complex_linear_algebra',
  'DATASET_PUBLICATION_API_v2@2',
  'PLOT_DATASET_API_v3@3:plot.dataset+plotspec',
  'SESSION_PERSISTENCE_API_v1@1'
});

CANONICAL_DATASET_API_v2DATASET_VARIABLE_SCHEMA_API_v3DATASET_METADATA_API_v1NUMERIC_API_v1DATASET_PUBLICATION_API_v2PLOT_DATASET_API_v3SESSION_PERSISTENCE_API_v1

APIRuolo nel tutorial
DATASET_VARIABLE_SCHEMA_API_v3Trova gli ID stabili delle variabili time_s, vin_V, vout_V e frequency_Hz.
CANONICAL_DATASET_API_v2Descrive e legge i valori dei Dataset sorgente.
DATASET_METADATA_API_v1Legge la frequenza effettiva di acquisizione come metadata namespaced.
NUMERIC_API_v1Calcola sin/cos, risolve il least-squares, ricava ampiezza, fase e guadagno in dB.
DATASET_PUBLICATION_API_v2Pubblica il risultato come Dataset canonico GX-TXT.
PLOT_DATASET_API_v3Costruisce, valida, crea e renderizza il normale grafico persistente.
SESSION_PERSISTENCE_API_v1Rende visibile lo stato appena pubblicato ai processi di rendering e lo conserva nella Session.

6. Identificare schema e metadata senza assumere il formato fisico

Ogni acquisizione espone quattro variabili canoniche. Il tutorial cerca le variabili per nome/label/ID e poi usa sempre il variable_id stabile restituito dallo schema.

Schema canonico del Dataset waveform
Figura 2 — Schema canonico di una acquisizione: tempo, ingresso, uscita e frequenza. Il codice scientifico usa identità Dataset/Variable, non il numero della colonna nel file.
schema = gxtxt_api_call(
    'DATASET_VARIABLE_SCHEMA_API_v3',
    'list',
    {dataset_id});

Dopo aver individuato gli ID necessari, il record viene letto tramite la Canonical Dataset API:

meta = gxtxt_api_call(
    'CANONICAL_DATASET_API_v2',
    'describe',
    {dataset_id});

block = gxtxt_api_call(
    'CANONICAL_DATASET_API_v2',
    'read_range',
    {dataset_id, read_ids, 0, meta.row_count});

Frequenza nominale e frequenza effettiva

Nel test la frequenza è disponibile sia come variabile costante frequency_Hz sia come metadata. Lo script privilegia acquisition.frequency_Hz, cioè la frequenza effettiva associata alla cattura, e usa la colonna come fallback. Se entrambe sono presenti ma differiscono di oltre l'1%, viene emesso un warning.

Metadata di acquisizione con frequenza effettiva e nominale
Figura 3 — Metadata namespaced dell'acquisizione. Il punto nominale da 100 Hz ha una frequenza effettiva leggermente diversa.
Principio riusabile. Quando un valore appartiene al contesto di acquisizione, non conviene ricavarlo dal nome del Dataset o dal filename. Se esiste un metadata pubblico, lo script deve preferire quello.

7. Estrarre ampiezza e fase con un fit sinusoidale

Per ogni cattura la frequenza è già nota. Non serve quindi stimare uno spettro completo: si può adattare direttamente ciascun canale alla base sinusoidale della frequenza di eccitazione.

Per \(\omega=2\pi f\):

\[ v(t)=a\sin(\omega t)+b\cos(\omega t)+c. \]

La matrice del problema lineare è:

\[ A = \begin{bmatrix}\sin(\omega t)&\cos(\omega t)&1\end{bmatrix}. \]

Lo script calcola seno e coseno attraverso il servizio numerico e risolve contemporaneamente ingresso e uscita:

sin_r = gxtxt_api_call(
    'NUMERIC_API_v1','array_unary',
    {'sin',struct('kind','vector','value',theta(:))});
cos_r = gxtxt_api_call(
    'NUMERIC_API_v1','array_unary',
    {'cos',struct('kind','vector','value',theta(:))});

A = [sin_r.values(:), cos_r.values(:), ones(numel(t),1)];
B = [vi(:), vo(:)];

fit = gxtxt_api_call(
    'NUMERIC_API_v1','linear_matrix',
    {'solve',struct('real',A),struct('real',B),struct('rank_policy','error')});
beta = double(fit.value_real);

Dai coefficienti:

\[ A_v=\sqrt{a^2+b^2}, \qquad \phi=\operatorname{atan2}(b,a). \]

Si ottengono quindi:

gain_linear = Aout / Ain
gain_dB     = 20 * log10(gain_linear)
phase_deg   = phase_out - phase_in

La fase è riportata nell'intervallo \([-180,+180)\) e lo script conserva anche offset e RMSE dei due fit. Queste quantità diagnostiche diventano colonne del Dataset derivato, invece di andare perse una volta costruito il grafico.

8. Risultato numerico del sweep

Frequenza [Hz]Gain [dB]Fase [deg]
100.080-24.027+86.760
220.262-17.092+82.335
470.459-10.224+72.383
820.216-4.814+55.278
1199.299-1.234+30.185
1498.299-0.063+7.242
1598.230-0.0009-0.130
1749.099-0.151-10.357
2200.747-1.569-33.064
4704.796-8.950-68.735
10011.815-15.862-80.432

Il massimo è prossimo alla risonanza teorica e il passaggio della fase attraverso lo zero avviene nella stessa regione. Questo controllo fisico è importante: un workflow API-correct non è automaticamente scientificamente corretto.

9. Pubblicare il risultato come Dataset GX-TXT

Il passaggio successivo non è “fare il grafico”, ma registrare il risultato numerico come un nuovo oggetto scientifico della Session:

dataset_id = 'tutorial.rlc_bode_experimental';

definition = struct();
definition.dataset_id = dataset_id;
definition.contract = 'table.generic';
definition.storage = 'auto';
definition.replace_owned = true;
definition.source_dataset_ids = source_ids;
definition.source_variable_ids = source_variable_ids;

writer = gxtxt_api_call(
    'DATASET_PUBLICATION_API_v2','begin',{definition});

Viene dichiarata una dimensione row estendibile e poi le dieci variabili del risultato:

Perché usare append per la tabella

Le variabili condividono tutte la stessa dimensione row. Lo script le pubblica quindi con un unico append allineato:

append_data = struct();
for ii = 1:numel(output_ids)
  append_data.(output_ids{ii}) = output_values{ii}(:);
endfor

gxtxt_api_call(
    'DATASET_PUBLICATION_API_v2','append',
    {writer,'row',append_data});

published = gxtxt_api_call(
    'DATASET_PUBLICATION_API_v2','finalize',{writer});
Errore reale emerso durante la prova. In Octave/MATLAB una cell literal spezzata su più righe può diventare una matrice 2×N; l'indicizzazione lineare {} è column-major. La versione verificata alloca esplicitamente output_values=cell(1,10) e assegna ogni colonna per indice. È un dettaglio piccolo, ma può associare vettori numericamente corretti agli ID variabile sbagliati.

10. Creare un grafico pienamente aderente al framework

Qui si trova il passaggio più importante del tutorial. Nella dev-037 lo Script Bridge espone il normale lifecycle di PLOT_DATASET_API_v3. Uno script può quindi creare una Variant ordinaria esattamente come un altro consumer pubblico del framework.

1Creare il PlotSpec

spec = gxtxt_api_call(
    'PLOT_DATASET_API_v3','new_spec',
    {'plot.dataset.xy','Experimental RLC Bode plot'});

Il PlotSpec contiene due pannelli, entrambi collegati allo stesso Dataset derivato.

2Definire le Graph Series con ID Dataset/Variable

gain_series = struct(
    'id','gain',
    'dataset_id',dataset_id,
    'x_variable_id','v_frequency_hz',
    'y_variable_id','v_gain_db',
    'axis','left',
    'kind','line_marker',
    'label','Gain');

phase_series = struct(
    'id','phase',
    'dataset_id',dataset_id,
    'x_variable_id','v_frequency_hz',
    'y_variable_id','v_phase_deg',
    'axis','left',
    'kind','line_marker',
    'label','Phase');

Non vengono passati array “sciolti” al renderer: le serie puntano a identità scientifiche persistenti.

3Costruire i due pannelli

xaxis = struct('label','Frequency [Hz]','scale','log');

p1 = struct();
p1.id = 'main';
p1.title = 'Magnitude';
p1.axes = struct('x',xaxis,'y_left',struct('label','Gain [dB]'));
p1.annotations = {};
p1.series = {gain_series};

p2 = struct();
p2.id = 'phase';
p2.title = 'Phase';
p2.axes = struct('x',xaxis,'y_left',struct('label','Phase [deg]'));
p2.annotations = {};
p2.series = {phase_series};

spec.multi_panel = 1;
spec.panels = {p1,p2};

4Validare e creare una normale Variant

plot_valid = gxtxt_api_call(
    'PLOT_DATASET_API_v3','validate_spec',{spec});

plot_ref = gxtxt_api_call(
    'PLOT_DATASET_API_v3','create_plot',
    {spec,'variant'});
create_plot vs upsert_plot. Per un normale script interattivo che vuole aggiungere un grafico alla Session, il percorso corretto è create_plot. upsert_plot serve invece a un producer stabile che vuole ritrovare e aggiornare la propria Variant attraverso una provider key. Sono due casi d'uso diversi.

5Persistenza, render e artifact discovery

gxtxt_api_call('SESSION_PERSISTENCE_API_v1','commit',{});

render_result = gxtxt_api_call(
    'PLOT_DATASET_API_v3','render',{plot_ref});

plot_artifacts = gxtxt_api_call(
    'PLOT_DATASET_API_v3','artifacts',{plot_ref});

Il riferimento ottenuto ha la forma:

module_id  = plot.dataset
graph_id   = plot.dataset.xy
variant_id = p0002    % esempio della Session verificata

Il variant_id concreto dipende dalla Session. Non va hardcodato.

11. La verifica che il grafico è davvero “GX-TXT”

Variant generata dallo script nella pagina Plots
Figura 4 — La Variant generata dallo script compare nella normale pagina Plots. Sono disponibili rerender, legend, axes, graphics, annotations, clone, export e Variants come per gli altri grafici.

Questo è il punto che distingue l'integrazione da una semplice immagine prodotta con plot(): GX-TXT conosce ancora il Dataset sorgente, le variabili, i pannelli e lo stato persistente del grafico.

Finestra Axes del normale plot.dataset Variant
Figura 5 — Il normale editor Axes agisce sulla Variant plot.dataset.xy:p0002. Le modifiche di unità, scala, griglia e presentazione non riscrivono i valori canonici del Dataset.

Unità canoniche e unità di presentazione

Nel Dataset la frequenza resta in Hz. Il renderer può visualizzarla in kHz tramite il normale sistema di unità del framework. La conversione è quindi presentation state, non una mutazione dei dati scientifici.

12. Il grafico finale

Bode RLC finale a due pannelli
Figura 6 — Bode RLC finale: magnitudine in dB, fase in gradi e asse di frequenza logaritmico condiviso. È il rendering di una Variant persistente, non il prodotto grafico privato dello script.

13. Il pattern generale da riutilizzare

La parte RLC è solo un esempio. Lo schema di integrazione è riutilizzabile in molti script di ricerca:

% 1. Contesto e preflight
ctx = gxtxt_context();
gxtxt_api_require({...});

% 2. Leggere input con Dataset/Variable IDs pubblici
schema = gxtxt_api_call('DATASET_VARIABLE_SCHEMA_API_v3','list',{dataset_id});
data   = gxtxt_api_call('CANONICAL_DATASET_API_v2','read_range',{...});

% 3. Calcolare con Octave e/o servizi GX-TXT
result = ...;

% 4. Pubblicare il risultato scientifico
dataset_ref = gxtxt_api_call('DATASET_PUBLICATION_API_v2', ...);

% 5. Descrivere il grafico con PlotSpec
spec = gxtxt_api_call('PLOT_DATASET_API_v3','new_spec',{graph_id,title});
% ... Dataset/Variable series, panels, axes ...

% 6. Creare una normale Variant
plot_ref = gxtxt_api_call('PLOT_DATASET_API_v3','create_plot',{spec,'variant'});

% 7. Render attraverso il provider ufficiale
gxtxt_api_call('SESSION_PERSISTENCE_API_v1','commit',{});
gxtxt_api_call('PLOT_DATASET_API_v3','render',{plot_ref});

14. Che cosa è specifico del RLC e che cosa no

Specifico di questo esperimentoRiutilizzabile in altri script
Variabili vin_V, vout_V, frequenza di eccitazione.Analysis Sources e identità Dataset/Variable.
Fit sin/cos/DC a frequenza nota.Uso di NUMERIC_API_v1 e callback/linear algebra pubbliche.
Gain, fase e diagnostica RMSE.Dataset Publication v2 con provenance verso le sorgenti.
Bode con due pannelli Magnitude/Phase.PlotSpec, Graph Series, Variant, render e artifact discovery.
Asse X logaritmico di frequenza.Separazione fra unità canoniche e unità di presentazione.

15. Errori da evitare

16. MATLAB e GNU Octave: cosa significa qui

Lo script usa sintassi .m largamente compatibile con MATLAB/Octave, ma il runtime ufficiale del GX-TXT Script Workspace è GNU Octave. Funzioni come gxtxt_context(), gxtxt_api_require() e gxtxt_api_call() sono fornite da GX-TXT durante l'esecuzione e non esistono in una sessione MATLAB standalone. Il valore della compatibilità MATLAB è quindi soprattutto nel linguaggio numerico e nella portabilità del codice scientifico, non nell'esistenza di un secondo runner GX-TXT.

17. Script completo

Il file seguente è mantenuto intenzionalmente come sorgente separato dal tutorial: permette di confrontare il testo didattico con il codice realmente provato e di importarlo nel Workspace senza copiarlo dalla pagina.

BIDE_SWEEP_v1.8_dev037_v3.m

Conclusione. Lo Script Workspace non è un ambiente isolato che produce risultati “a lato” del framework. Quando lo script usa le API pubbliche, può partecipare allo stesso modello di Dataset, provenance, PlotSpec, Variant e rendering dei moduli ufficiali. Questo tutorial usa il Bode RLC per mostrare l'intero percorso in una sola esperienza riproducibile.