[ANN] MDDatasets.jl: Multi-dimensional datasets & continuous x-variable interpolation

The MDDatasets.jl module provides tools to simplify manipulation of multi-dimensional datasets.
GitHub - ma-laforge/MDDatasets.jl: Multi-Dimensional Datasets for Parametric Analysis

Insallation:

] add MDDatasets

Grouping (x,y) vectors

MDDatasets also provides a means to group (x,y) data in a single data structure to simplify calculations (DataF1 structure). For example, we can take the derivative of a dataset (wrt x) very simply using:

(Using MDDatasets.jl:)

#Assuming f1 has time-domain data y=f(x=t):
slope = deriv(f1)

Note that internally, f1 contains both y values, and x values (which represent time, t). This is much more straightforward than the typical way to do differentiation:

(WITHOUT the help of MDDatasets.jl:)

#Assuming we have f1_x & f1_y data:
deltax = f1_x[2:end] .- f1_x[1:end-1]
deltay = f1_y[2:end] .- f1_y[1:end-1]
slope = deltay ./ deltax

AUTOMATIC INTERPOLATION

Note that operations on DataF1 objects will automatically interpolate intermediate values if the x sample points don’t match. This is particularly useful if you wish to perform operations on results collected from multiple simulations that have different/non-uniform time steps (or whatever the x-values might represent).

Multi-Dimensional Data & “Parametric Analysis”

MDDatasets.jl is designed to store values from a “Parametric Analysis”. Basically, you re-run the same experiments with different conditions by “sweeping” the values of different control variables in order to see how they affect results.

For example, you might want to simulate circuit behaviour by “sweeping” the following parameters:

for vSupply in [0.9V, 1V, 1.1V]
	for temp in [-40, 0, 100, 125]
		for trise in 10e-12:10e-12:100e-12
			#Read in data & compute characteristics that might be insighful
		end
	end
end

With MDDatasets.jl, the procedure is a bit more straightforward…

Step 1: Collect Data in Multi-Dimensional Dataset using DataRS

To build a multi-dimensional dataset, you would “fill” a DataRS structure:

signal1 = fill(DataRS, PSweep("tbit", [1, 3, 9] * 1e-9)) do tbit
	fill(DataRS, PSweep("VDD", 0.9 * [0.9, 1, 1.1])) do vdd

		#Inner-most sweep: need to specify element type (DataF1):
		fill(DataRS{DataF1}, PSweep("trise", [0.1, 0.15, 0.2] * tbit)) do trise
			y = get_ydata(tbit, vdd, trise) #Read in y values
			x = get_xdata(tbit, vdd, trise) #Read in x values
			return DataF1(x, y) #"Leaf" elements of DataRS structure are DataF1 containers.
		end
	end
end

This assumes get_xdata() & get_ydata() functions retrieve simulation data at certain values of tbit, vdd, & trise.

Indeed, this DOES seem like alot, but you only have to read in data once. Once data is read in, performing the actual calculations becomes much more straightforward and easy to read/write (see below).

Step 2: Run Calculations

Assuming vin & vout are DataRS structures storing the input & output voltages of a closed-loop op-amp time-domain simulation, one could easily compute the gain error over all of the simulated time:

gainideal = 6 #Desired gain of op-amp

#Compute error in gain value, over time:
error = 100 * ((vout/vin) - gainideal) / gainideal

NOTE:

  • There is no need for explicit for loops. MDDatasets.jl automatically broadcasts operations over all “swept” parameters.
  • There is no need for the “dot” notation (.+, .*, …). Unlike with Array objects, DataRS structures are not meant to natively support matrix operations (matrix multiplication, etc). Consequently, there is no confusion between element-by-element & matrix operations.

Sample Usage

More advanced uses of the MDDatasets.jl module can be found in the (not-yet-registered) SignalProcessing.jl module:

https://github.com/ma-laforge/SignalProcessing.jl

Concrete Example

using MDDatasets

Create (x,y) container pair, and call it “x”:

x = DataF1(0:.1:20)
#NOTE: Both x & y coordinates of "x" object initialized as y = x = [supplied range]

“Extract” maximum x-value from data:

xmax = maximum(x)

Construct a “normalized” ramp dataset, unity_ramp:

unity_ramp = x/xmax

Observe x and unity_ramp

(Note how unity_ramp is normalized such that the maximum value is 1)

image

Compute sin(x)

sinx = sin(x)

Compute ramps with different slopes using unity_ramp (previously computed):

#NOTE: for Inner-most sweep, we need to specify leaf element type (DataF1 here):
ramp = fill(DataRS{DataF1}, PSweep("slope", [0, 0.5, 1, 1.5, 2])) do slope
	return unity_ramp * slope
end

NOTE: the above expression constructs a multi-dimensional DataRS structure, and fills it with (x,y) values for each of the desired parameter values (the slope).

Observe sinx and ramp

image

Merge two datasets with different # of sweeps (sinx & ramp):

r_sin = sinx+ramp

Observe newly constructed r_sin dataset:

image

Shift all ramped sin(x) waveforms to make them centered at their mid-points:

midval = (minimum(ramp) + maximum(ramp)) / 2
c_sin = r_sin - midval #Shift by midval (different for each swept slope of "ramp")

Observe newly constructed c_sin dataset:

image