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

The MDDatasets.jl module provides tools to simplify manipulation of multi-dimensional datasets.
–> https://github.com/ma-laforge/MDDatasets.jl

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:

# 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)

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`

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

``````r_sin = sinx+ramp
``````

### Observe newly constructed `r_sin` dataset:

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")
``````