Hello PLECS community,
I wanted to share a finding regarding the trace indexing order when using GetCursorData in combination with HoldTrace during parallel parameter sweeps, as this behavior is not documented and may cause confusion.
Setup:
I run a parallel parameter sweep over three input voltages using plecs('simulate', simStructs, @callback). In the callback, I hold traces with named labels:
octave
Vbus_values = [100, 200, 300];
function result = callback(index, data, vbusVals)
name = ['Vin = ', mat2str(vbusVals(index)), 'V'];
plecs('scope', './Scope', 'HoldTrace', name);
result = name;
end
out = plecs('simulate', simStructs, ...
@(index, data) callback(index, data, Vbus_values));
After all simulations complete, I extract the mean values using GetCursorData:
octave
cData = plecs('scope', './Scope', 'GetCursorData', [t1 t2], 'mean');
The documentation states that for multiple traces, the field values are vectors with one element per trace, for example:
octave
cData.cursorData{plot}{signal}.mean(traceIndex)
The problem:
I initially assumed that mean(1) corresponds to the first held trace (Vin=100V), mean(2) to the second (Vin=200V), and mean(3) to the third (Vin=300V). However, after comparing the script output with the scope cursor values, I found that the order is reversed: mean(1) corresponds to the last held trace, and mean(N) corresponds to the first held trace.
This is consistent with how the scope visually displays traces — the most recently held trace appears at the top of the Traces window and gets index 1.
In my case with three voltage steps:
| Trace index in GetCursorData | Actual operating point |
|---|---|
| 1 | Vin = 300V (last held) |
| 2 | Vin = 200V |
| 3 | Vin = 100V (first held) |
The solution:
To correctly map the cursor data back to the original parameter values, I reverse the index:
octave
nSteps = length(Vbus_values);
for ix = 1:nSteps
traceIdx = nSteps + 1 - ix; % reverse mapping
vals(ix) = cData.cursorData{p}{s}.mean(traceIdx);
end
This ensures that vals(1) corresponds to Vbus_values(1), vals(2) to Vbus_values(2), etc.
Questions for the community and PLECS team:
- Can you confirm that
GetCursorDataalways returns trace data in reverse hold order (most recently held trace = index 1)? Is this behavior guaranteed and stable across versions? - Is there an alternative method to retrieve cursor data by trace name rather than by index? For example, something like:
octave
plecs('scope', scopePath, 'GetCursorData', [t1 t2], 'mean', 'TraceName', 'Vin = 100V')
This would eliminate any ambiguity about trace ordering.
- With parallel simulations, the callback execution order is non-deterministic (simulations may complete in any order). In my tests, the
HoldTracecalls inside the callback seem to be serialized correctly by PLECS, so the hold order matches the completion order. However, if the hold order depends on which simulation finishes first, the reverse-index approach alone would not be reliable. Is there a way to query the trace names associated with each index to build a robust mapping?
A possible improvement to the API could be having GetCursorData return a struct that includes the trace names alongside the data, for example:
octave
cData.traceNames = {'Vin = 100V', 'Vin = 200V', 'Vin = 300V'};
This would make post-processing scripts more robust regardless of internal trace ordering.
Thank you for any clarification. I hope this helps others who encounter the same issue during automated parameter sweeps.