Skip to content

Commit

Permalink
feat: ✨ separate array storage and timing info (#8)
Browse files Browse the repository at this point in the history
* feat: pooled complex array storage

* feat: ✨ switch to `PooledComplexArray` and use generic math

* feat: simplify `PooledComplexArray` Slicing
  • Loading branch information
kahojyun authored Jun 27, 2023
1 parent 983cf67 commit a2598f5
Show file tree
Hide file tree
Showing 23 changed files with 907 additions and 529 deletions.
155 changes: 93 additions & 62 deletions examples/WaveGenDemo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,75 +1,106 @@
using System.Diagnostics;
using System.Numerics;

using Qynit.Pulsewave;

using ScottPlot;

var sw = Stopwatch.StartNew();

var ch1 = new Channel("ch1");
var ch2 = new Channel("ch2");

var inputNode1 = new InputNode();
var inputNode2 = new InputNode();

var n = 100000;
var sampleRate = 2e9;
var outputNode1 = new OutputNode(n, sampleRate, 0);
var outputNode2 = new OutputNode(n, sampleRate, 0);

ConnectNode(inputNode1, outputNode1);
ConnectNode(inputNode2, outputNode2);

var generator = new WaveformGenerator();
generator.AddChannel(ch1, inputNode1, 100e6);
generator.AddChannel(ch2, inputNode2, 250e6);

var instructions = new List<Instruction>();
var shape = new HannPulseShape();
instructions.Add(new Play(shape, 0, 30e-9, 100e-9, 0.5, 0, 0, ch1));
instructions.Add(new Play(shape, 0, 30e-9, 100e-9, 0.6, 0, 0, ch2));
instructions.Add(new ShiftPhase(0.25 * Math.Tau, ch1));
instructions.Add(new ShiftPhase(-0.25 * Math.Tau, ch2));
instructions.Add(new Play(shape, 200e-9, 30e-9, 100e-9, 0.5, 0, 0, ch1));
instructions.Add(new Play(shape, 200e-9, 30e-9, 100e-9, 0.6, 0, 0, ch2));
instructions.Add(new ShiftFrequency(-100e6, 400e-9, ch1));
instructions.Add(new ShiftFrequency(-250e6, 400e-9, ch2));
instructions.Add(new Play(shape, 400e-9, 30e-9, 100e-9, 0.5, 0, 0, ch1));
instructions.Add(new Play(shape, 400e-9, 30e-9, 100e-9, 0.6, 0, 0, ch2));
instructions.Add(new SetFrequency(0, 600e-9, ch1));
instructions.Add(new SetFrequency(0, 600e-9, ch2));

var tStart = 600e-9;
var count = 0;
while (tStart < 49e-6)
for (var i = 0; i < 5; i++)
{
instructions.Add(new Play(shape, tStart, 30e-9, 0, 0.5, 0, 0, ch1));
instructions.Add(new Play(shape, tStart, 30e-9, 0, 0.6, 0, 0, ch2));
instructions.Add(new ShiftPhase(0.25 * Math.Tau, ch1));
instructions.Add(new ShiftPhase(-0.25 * Math.Tau, ch2));
tStart += 0.1e-9;
count++;
RunDouble();
}

generator.Run(instructions);
for (var i = 0; i < 5; i++)
{
RunSingle();
}

sw.Stop();
Console.WriteLine($"Elapsed time: {sw.Elapsed.TotalMilliseconds} ms");
Console.WriteLine($"Count = {count}");
static void ConnectNode<T>(IFilterNode<T> source, IFilterNode<T> target) where T : unmanaged, IFloatingPointIeee754<T>
{
source.Outputs.Add(target);
target.Inputs.Add(source);
}

using var waveform1 = outputNode1.TakeWaveform();
using var waveform2 = outputNode2.TakeWaveform();
static void RunDouble()
{
Console.WriteLine("RunDouble:");
Run<double>();
Console.WriteLine("------------------------");
}

var plot = new Plot();
plot.AddSignal(waveform1.DataI.ToArray(), sampleRate, label: $"wave 1 real");
plot.AddSignal(waveform1.DataQ.ToArray(), sampleRate, label: $"wave 1 imag");
plot.AddSignal(waveform2.DataI.ToArray(), sampleRate, label: $"wave 2 real");
plot.AddSignal(waveform2.DataQ.ToArray(), sampleRate, label: $"wave 2 imag");
plot.Legend();
plot.SaveFig("demo.png");
static void RunSingle()
{
Console.WriteLine("RunSingle:");
Run<float>();
Console.WriteLine("------------------------");
}

static void ConnectNode(IFilterNode source, IFilterNode target)
static void Run<T>() where T : unmanaged, IFloatingPointIeee754<T>
{
source.Outputs.Add(target);
target.Inputs.Add(source);
var sw = Stopwatch.StartNew();

var ch1 = new Channel("ch1");
var ch2 = new Channel("ch2");

var inputNode1 = new InputNode<T>();
var inputNode2 = new InputNode<T>();

var n = 100000;
var sampleRate = 2e9;
var outputNode1 = new OutputNode<T>(n, sampleRate, 0, -4);
var outputNode2 = new OutputNode<T>(n, sampleRate, 0, -4);

ConnectNode(inputNode1, outputNode1);
ConnectNode(inputNode2, outputNode2);

var generator = new WaveformGenerator<T>();
generator.AddChannel(ch1, inputNode1, 100e6);
generator.AddChannel(ch2, inputNode2, 250e6);

var instructions = new List<Instruction>();
var shape = new HannPulseShape();
instructions.Add(new Play(shape, 0, 30e-9, 100e-9, 0.5, 0, 0, ch1));
instructions.Add(new Play(shape, 0, 30e-9, 100e-9, 0.6, 0, 0, ch2));
instructions.Add(new ShiftPhase(0.25 * Math.Tau, ch1));
instructions.Add(new ShiftPhase(-0.25 * Math.Tau, ch2));
instructions.Add(new Play(shape, 200e-9, 30e-9, 100e-9, 0.5, 0, 0, ch1));
instructions.Add(new Play(shape, 200e-9, 30e-9, 100e-9, 0.6, 0, 0, ch2));
instructions.Add(new ShiftFrequency(-100e6, 400e-9, ch1));
instructions.Add(new ShiftFrequency(-250e6, 400e-9, ch2));
instructions.Add(new Play(shape, 400e-9, 30e-9, 100e-9, 0.5, 0, 0, ch1));
instructions.Add(new Play(shape, 400e-9, 30e-9, 100e-9, 0.6, 0, 0, ch2));
instructions.Add(new SetFrequency(0, 600e-9, ch1));
instructions.Add(new SetFrequency(0, 600e-9, ch2));

var tStart = 600e-9;
var count = 0;
while (tStart < 49e-6)
{
//instructions.Add(new Play(shape, tStart, 30e-9, 0, 0.5, 0, 0, ch1));
//instructions.Add(new Play(shape, tStart, 30e-9, 0, 0.6, 0, 0, ch2));
instructions.Add(new Play(shape, tStart, 30e-9, 500e-9, 0.5, 0, 0, ch1));
instructions.Add(new Play(shape, tStart, 30e-9, 500e-9, 0.6, 0, 0, ch2));
instructions.Add(new ShiftPhase(0.25 * Math.Tau, ch1));
instructions.Add(new ShiftPhase(-0.25 * Math.Tau, ch2));
tStart += 0.1e-9;
count++;
}

var t1 = sw.Elapsed;

generator.Run(instructions);

sw.Stop();
var t2 = sw.Elapsed;
Console.WriteLine($"Instructions time: {t1.TotalMilliseconds} ms");
Console.WriteLine($"Run time: {(t2 - t1).TotalMilliseconds} ms");
Console.WriteLine($"Total elapsed time: {sw.Elapsed.TotalMilliseconds} ms");
Console.WriteLine($"Count = {count}");
using var waveform1 = outputNode1.TakeWaveform();
using var waveform2 = outputNode2.TakeWaveform();
//var plot = new Plot(1920, 1080);
//plot.AddSignal(waveform1.DataI[..300].ToArray(), sampleRate, label: $"wave 1 real");
//plot.AddSignal(waveform1.DataQ[..300].ToArray(), sampleRate, label: $"wave 1 imag");
//plot.AddSignal(waveform2.DataI[..300].ToArray(), sampleRate, label: $"wave 2 real");
//plot.AddSignal(waveform2.DataQ[..300].ToArray(), sampleRate, label: $"wave 2 imag");
//plot.Legend();
//plot.SaveFig("demo2.png");
}
5 changes: 4 additions & 1 deletion src/Qynit.Pulsewave/Channel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@

public record Channel(string Name)
{
public static implicit operator Channel(string name) => new(name);
public static implicit operator Channel(string name)
{
return new(name);
}
}
43 changes: 43 additions & 0 deletions src/Qynit.Pulsewave/ComplexArrayReadOnlySpan.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace Qynit.Pulsewave;
public readonly ref struct ComplexArrayReadOnlySpan<T>
where T : unmanaged
{
public ReadOnlySpan<T> DataI { get; }
public ReadOnlySpan<T> DataQ { get; }
public int Length => DataI.Length;
internal ComplexArrayReadOnlySpan(ReadOnlySpan<T> dataI, ReadOnlySpan<T> dataQ)
{
Debug.Assert(dataI.Length == dataQ.Length);
DataI = dataI;
DataQ = dataQ;
}
public static implicit operator ComplexArrayReadOnlySpan<T>(PooledComplexArray<T> source)
{
return new ComplexArrayReadOnlySpan<T>(source.DataI, source.DataQ);
}
public static implicit operator ComplexArrayReadOnlySpan<T>(ComplexArraySpan<T> source)
{
return new ComplexArrayReadOnlySpan<T>(source.DataI, source.DataQ);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComplexArrayReadOnlySpan<T> Slice(int start)
{
return new ComplexArrayReadOnlySpan<T>(DataI[start..], DataQ[start..]);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComplexArrayReadOnlySpan<T> Slice(int start, int length)
{
return new ComplexArrayReadOnlySpan<T>(DataI.Slice(start, length), DataQ.Slice(start, length));
}

public void CopyTo(ComplexArraySpan<T> destination)
{
DataI.CopyTo(destination.DataI);
DataQ.CopyTo(destination.DataQ);
}
}
33 changes: 33 additions & 0 deletions src/Qynit.Pulsewave/ComplexArraySpan.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using System.Diagnostics;
using System.Runtime.CompilerServices;

namespace Qynit.Pulsewave;
public readonly ref struct ComplexArraySpan<T>
where T : unmanaged
{
public Span<T> DataI { get; }
public Span<T> DataQ { get; }
public int Length => DataI.Length;
internal ComplexArraySpan(Span<T> dataI, Span<T> dataQ)
{
Debug.Assert(dataI.Length == dataQ.Length);
DataI = dataI;
DataQ = dataQ;
}
public static implicit operator ComplexArraySpan<T>(PooledComplexArray<T> source)
{
return new ComplexArraySpan<T>(source.DataI, source.DataQ);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComplexArraySpan<T> Slice(int start)
{
return new ComplexArraySpan<T>(DataI[start..], DataQ[start..]);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public ComplexArraySpan<T> Slice(int start, int length)
{
return new ComplexArraySpan<T>(DataI.Slice(start, length), DataQ.Slice(start, length));
}
}
27 changes: 27 additions & 0 deletions src/Qynit.Pulsewave/EnvelopeInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using CommunityToolkit.Diagnostics;

namespace Qynit.Pulsewave;

public sealed record class EnvelopeInfo
{
private readonly double _indexOffset;
public double IndexOffset
{
get => _indexOffset;
init
{
if (value < 0 || value >= 1)
{
ThrowHelper.ThrowArgumentOutOfRangeException(nameof(IndexOffset), value, $"Index offset of `EnvelopeInfo` should be in range [0, 1).");
}
_indexOffset = IndexOffset;
}
}
public double SampleRate { get; init; }

public EnvelopeInfo(double indexOffset, double sampleRate)
{
IndexOffset = indexOffset;
SampleRate = sampleRate;
}
}
13 changes: 8 additions & 5 deletions src/Qynit.Pulsewave/FilterNodeBase.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
using CommunityToolkit.Diagnostics;
using System.Numerics;

using CommunityToolkit.Diagnostics;

namespace Qynit.Pulsewave;
public abstract class FilterNodeBase : IFilterNode
public abstract class FilterNodeBase<T> : IFilterNode<T>
where T : unmanaged, IFloatingPointIeee754<T>
{
public virtual double SampleRate
{
Expand Down Expand Up @@ -45,8 +48,8 @@ public virtual double TEnd
}

public string? Name { get; set; }
public IList<IFilterNode> Outputs { get; } = new List<IFilterNode>();
public IList<IFilterNode> Inputs { get; } = new List<IFilterNode>();
public IList<IFilterNode<T>> Outputs { get; } = new List<IFilterNode<T>>();
public IList<IFilterNode<T>> Inputs { get; } = new List<IFilterNode<T>>();

public virtual void Initialize()
{
Expand All @@ -65,5 +68,5 @@ public virtual void Complete()
}

public abstract void AddPulse(IPulseShape shape, double tStart, double width, double plateau, double amplitude, double frequency, double phase, double referenceTime);
public abstract void AddWaveform(Waveform waveform, double tShift, double amplitude, double frequency, double phase, double referenceTime);
public abstract void AddWaveform(ComplexArrayReadOnlySpan<T> waveform, WaveformInfo waveformInfo, double amplitude, double frequency, double phase, double referenceTime);
}
40 changes: 23 additions & 17 deletions src/Qynit.Pulsewave/HannPulseShape.cs
Original file line number Diff line number Diff line change
@@ -1,31 +1,37 @@
using System.Diagnostics;
using System.Numerics;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;

namespace Qynit.Pulsewave;
public sealed class HannPulseShape : IPulseShape
{
public Complex SampleAt(double x)
public IqPair<T> SampleAt<T>(T x)
where T : unmanaged, IFloatingPointIeee754<T>
{
return (x >= -0.5 && x <= 0.5) ? (1 + Math.Cos(Math.Tau * x)) / 2 : 0;
var half = T.CreateChecked(0.5);
var i = (x >= -half && x <= half) ? (T.One + T.Cos(T.Tau * x)) * half : T.Zero;
return i;
}

public void SampleIQ(Span<double> targetI, Span<double> targetQ, double x0, double dx)
public void SampleIQ<T>(ComplexArraySpan<T> target, T x0, T dx)
where T : unmanaged, IFloatingPointIeee754<T>
{
var l = targetI.Length;
Debug.Assert(targetQ.Length == l);

var c = Complex.FromPolarCoordinates(0.5, Math.Tau * x0);
var w = Complex.FromPolarCoordinates(1, Math.Tau * dx);

ref var ti = ref MemoryMarshal.GetReference(targetI);
ref var tq = ref MemoryMarshal.GetReference(targetQ);
for (var i = 0; i < l; i++)
var length = target.Length;
if (length == 0)
{
return;
}
var half = T.CreateChecked(0.5);
var c = IqPair<T>.FromPolarCoordinates(half, T.Tau * x0);
var w = IqPair<T>.FromPolarCoordinates(T.One, T.Tau * dx);
ref var ti = ref MemoryMarshal.GetReference(target.DataI);
ref var tq = ref MemoryMarshal.GetReference(target.DataQ);
var ii = T.Zero;
for (var i = 0; i < length; i++, ii++)
{
var x = x0 + i * dx;
Unsafe.Add(ref ti, i) = (x >= -0.5 && x <= 0.5) ? 0.5 + c.Real : 0;
Unsafe.Add(ref tq, i) = 0;
var x = x0 + ii * dx;
Unsafe.Add(ref ti, i) = (x >= -half && x <= half) ? half + c.I : T.Zero;
Unsafe.Add(ref tq, i) = T.Zero;
c *= w;
}
}
Expand Down
13 changes: 8 additions & 5 deletions src/Qynit.Pulsewave/IFilterNode.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
namespace Qynit.Pulsewave;
public interface IFilterNode
using System.Numerics;

namespace Qynit.Pulsewave;
public interface IFilterNode<T>
where T : unmanaged, IFloatingPointIeee754<T>
{
void Initialize();
void Complete();
void AddPulse(IPulseShape shape, double tStart, double width, double plateau, double amplitude, double frequency, double phase, double referenceTime);
void AddWaveform(Waveform waveform, double tShift, double amplitude, double frequency, double phase, double referenceTime);
void AddWaveform(ComplexArrayReadOnlySpan<T> waveform, WaveformInfo waveformInfo, double amplitude, double frequency, double phase, double referenceTime);
double SampleRate { get; }
double TStart { get; }
double TEnd { get; }
string? Name { get; set; }
IList<IFilterNode> Outputs { get; }
IList<IFilterNode> Inputs { get; }
IList<IFilterNode<T>> Outputs { get; }
IList<IFilterNode<T>> Inputs { get; }
}
Loading

0 comments on commit a2598f5

Please sign in to comment.