Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: ✨ separate array storage and timing info #8

Merged
merged 3 commits into from
Jun 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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