diff --git a/contents/convolutions/1d/1d.md b/contents/convolutions/1d/1d.md index 307e1d669..b061941c1 100644 --- a/contents/convolutions/1d/1d.md +++ b/contents/convolutions/1d/1d.md @@ -53,7 +53,9 @@ With this in mind, we can almost directly transcribe the discrete equation into {% method %} {% sample lang="jl" %} -[import:27-46, lang:"julia"](code/julia/1d_convolution.jl) +[import:27-46, lang:"julia"](../code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:63-84, lang:"csharp"](../code/csharp/1DConvolution.cs) {% endmethod %} The easiest way to reason about this code is to read it as you might read a textbook. @@ -184,7 +186,9 @@ Here it is again for clarity: {% method %} {% sample lang="jl" %} -[import:27-46, lang:"julia"](code/julia/1d_convolution.jl) +[import:27-46, lang:"julia"](../code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:63-84, lang:"csharp"](../code/csharp/1DConvolution.cs) {% endmethod %} Here, the main difference between the bounded and unbounded versions is that the output array size is smaller in the bounded case. @@ -192,14 +196,18 @@ For an unbounded convolution, the function would be called with a the output arr {% method %} {% sample lang="jl" %} -[import:58-59, lang:"julia"](code/julia/1d_convolution.jl) +[import:58-59, lang:"julia"](../code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:96-97, lang:"csharp"](../code/csharp/1DConvolution.cs) {% endmethod %} On the other hand, the bounded call would set the output array size to simply be the length of the signal {% method %} {% sample lang="jl" %} -[import:61-62, lang:"julia"](code/julia/1d_convolution.jl) +[import:61-62, lang:"julia"](../code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:98-99, lang:"csharp"](../code/csharp/1DConvolution.cs) {% endmethod %} Finally, as we mentioned before, it is possible to center bounded convolutions by changing the location where we calculate the each point along the filter. @@ -207,7 +215,9 @@ This can be done by modifying the following line: {% method %} {% sample lang="jl" %} -[import:35-35, lang:"julia"](code/julia/1d_convolution.jl) +[import:35-35, lang:"julia"](../code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:71-71, lang:"csharp"](../code/csharp/1DConvolution.cs) {% endmethod %} Here, `j` counts from `i-length(filter)` to `i`. @@ -239,7 +249,9 @@ In code, this typically amounts to using some form of modulus operation, as show {% method %} {% sample lang="jl" %} -[import:4-25, lang:"julia"](code/julia/1d_convolution.jl) +[import:4-25, lang:"julia"](../code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import:38-61, lang:"csharp"](../code/csharp/1DConvolution.cs) {% endmethod %} This is essentially the same as before, except for the modulus operations, which allow us to work on a periodic domain. @@ -254,7 +266,9 @@ For the code associated with this chapter, we have used the convolution to gener {% method %} {% sample lang="jl" %} -[import, lang:"julia"](code/julia/1d_convolution.jl) +[import, lang:"julia"](../code/julia/1d_convolution.jl) +{% sample lang="cs" %} +[import, lang:"csharp"](../code/csharp/1DConvolution.cs) {% endmethod %} At a test case, we have chosen to use two sawtooth functions, which should produce the following images: diff --git a/contents/convolutions/code/csharp/1DConvolution.cs b/contents/convolutions/code/csharp/1DConvolution.cs new file mode 100755 index 000000000..c4c1016c6 --- /dev/null +++ b/contents/convolutions/code/csharp/1DConvolution.cs @@ -0,0 +1,110 @@ +using System; +using System.IO; + +namespace Convolution1D +{ + public class Convolution1D + { + // Creates a sawtooth function with the given length. + static double[] CreateSawtooth(int length) + { + var array = new double[length]; + for (var i = 0; i < length; i++) + array[i] = (i + 1) / 200f; + return array; + } + + // Normalizes the given array. + static void Normalize(double[] array) + { + var norm = Norm(array); + for (var i = 0; i < array.Length; i++) + array[i] /= norm; + } + + // Calculates the norm of the array. + static double Norm(double[] array) + { + var sum = 0.0; + for (var i = 0; i < array.Length; i++) + sum += Math.Pow(array[i], 2); + return Math.Sqrt(sum); + } + + // Modulus function which handles negative values properly. + // Assumes that y >= 0. + static int Mod(int x, int y) => ((x % y) + y) % y; + + static double[] ConvolveCyclic(double[] signal, double[] filter) + { + var outputSize = Math.Max(signal.Length, filter.Length); + + // Convolutional output. + var output = new double[outputSize]; + var sum = 0.0; + + for (var i = 0; i < outputSize; i++) + { + for (var j = 0; j < outputSize; j++) + { + if (Mod(i - j, outputSize) < filter.Length) + { + sum += signal[Mod(j - 1, outputSize)] * filter[Mod(i - j, outputSize)]; + } + } + + output[i] = sum; + sum = 0.0; + } + + return output; + } + + static double[] ConvolveLinear(double[] signal, double[] filter, int outputSize) + { + // Convolutional output. + var output = new double[outputSize]; + var sum = 0.0; + + for (var i = 0; i < outputSize; i++) + { + for (var j = Math.Max(0, i - filter.Length); j <= i; j++) + { + if (j < signal.Length && (i - j) < filter.Length) + { + sum += signal[j] * filter[i - j]; + } + } + + output[i] = sum; + sum = 0.0; + } + + return output; + } + + static void Main() + { + // Create sawtooth functions for x and y. + var x = CreateSawtooth(200); + var y = CreateSawtooth(200); + + // Normalization is not strictly necessary, but good practice. + Normalize(x); + Normalize(y); + + // Full convolution, output will be the size of x + y - 1. + var fullLinearOutput = ConvolveLinear(x, y, x.Length + y.Length - 1); + // Simple boundaries. + var simpleLinearOutput = ConvolveLinear(x, y, x.Length); + // Cyclic convolution. + var cyclicOutput = ConvolveCyclic(x, y); + + // Output convolutions to different files for plotting. + File.WriteAllText("full_linear.dat", String.Join(Environment.NewLine, fullLinearOutput)); + File.WriteAllText("simple_linear.dat", String.Join(Environment.NewLine, simpleLinearOutput)); + File.WriteAllText("cyclic.dat", String.Join(Environment.NewLine, cyclicOutput)); + } + } +} +