digiproc 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (175) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +12 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +7 -0
  5. data/CODE_OF_CONDUCT.md +74 -0
  6. data/Gemfile +8 -0
  7. data/Gemfile.lock +48 -0
  8. data/LICENSE.txt +21 -0
  9. data/README.md +78 -0
  10. data/Rakefile +37 -0
  11. data/TODO.md +50 -0
  12. data/bin/console +14 -0
  13. data/bin/setup +8 -0
  14. data/config/environment.rb +118 -0
  15. data/console_tests.rb +44 -0
  16. data/digiproc.gemspec +49 -0
  17. data/examples/analog_signals/analog_to_digital.rb +31 -0
  18. data/examples/analog_signals/companded-signals.png +0 -0
  19. data/examples/analog_signals/companding.rb +68 -0
  20. data/examples/analog_signals/fft-plot.png +0 -0
  21. data/examples/analog_signals/plot_Digiproc::FFT.png +0 -0
  22. data/examples/analog_signals/plot_Dsp::FFT.png +0 -0
  23. data/examples/analog_signals/quantization-outputs.png +0 -0
  24. data/examples/analog_signals/quantize_compand.rb +69 -0
  25. data/examples/binomial_distribution/bit_error.rb +14 -0
  26. data/examples/binomial_distribution/dice.rb +35 -0
  27. data/examples/digital_signals/_coded_frequency_signal,_ts_=_1_s.png +0 -0
  28. data/examples/digital_signals/_coded_frequency_signal,_ts_=_2_s.png +0 -0
  29. data/examples/digital_signals/coded_power_spectral_density,__ts_=_1_s.png +0 -0
  30. data/examples/digital_signals/coded_power_spectral_density,__ts_=_2_s.png +0 -0
  31. data/examples/digital_signals/coded_time_signal,_ts_=_1_s.png +0 -0
  32. data/examples/digital_signals/coded_time_signal,_ts_=_2_s.png +0 -0
  33. data/examples/digital_signals/freq_sig_from_eqn,_ts_=_1_s.png +0 -0
  34. data/examples/digital_signals/freq_sig_from_eqn,_ts_=_2_s.png +0 -0
  35. data/examples/digital_signals/frequency_signal,_ts_=_1_s.png +0 -0
  36. data/examples/digital_signals/frequency_signal,_ts_=_2_s.png +0 -0
  37. data/examples/digital_signals/modulate_square_pulses.rb +9 -0
  38. data/examples/digital_signals/modulated_sq._pulses.png +0 -0
  39. data/examples/digital_signals/modulated_sq._pulses_alt.png +0 -0
  40. data/examples/digital_signals/power_spectral_density,__ts_=_1_s.png +0 -0
  41. data/examples/digital_signals/power_spectral_density,__ts_=_2_s.png +0 -0
  42. data/examples/digital_signals/square_signals.rb +90 -0
  43. data/examples/digital_signals/time_signal,_ts_=_1_s.png +0 -0
  44. data/examples/digital_signals/time_signal,_ts_=_2_s.png +0 -0
  45. data/examples/encoding/gray_code.rb +22 -0
  46. data/examples/encoding/psk.rb +91 -0
  47. data/examples/encoding/system_2_phase.png +0 -0
  48. data/examples/encoding/system_2_xmit_signal.png +0 -0
  49. data/examples/encoding/system_3_phase.png +0 -0
  50. data/examples/encoding/system_3_xmit_signal.png +0 -0
  51. data/examples/encoding/system_4_xmit_signal.png +0 -0
  52. data/examples/encoding/xor-dpsk-phase-signal-(sys1).png +0 -0
  53. data/examples/encoding/xor-dpsk-xmit-signal-(sys-1).png +0 -0
  54. data/examples/factories/Quickplot Graph.png +0 -0
  55. data/examples/factories/bandpass.rb +6 -0
  56. data/examples/fft/plot_Dsp::FFT.png +0 -0
  57. data/examples/fft/recieved_data_(time_domain).png +0 -0
  58. data/examples/fft/simple_fft_example.rb +47 -0
  59. data/examples/fft/unprocessed_fft.png +0 -0
  60. data/examples/filters/bandpass_filter.png +0 -0
  61. data/examples/filters/filter_a_signal.rb +38 -0
  62. data/examples/filters/white_noise_db_out_of_bp_filter.png +0 -0
  63. data/examples/filters/white_noise_mag_out_of_bp_filter.png +0 -0
  64. data/examples/filters/white_noise_spectra.png +0 -0
  65. data/examples/functions/compute_probability.rb +29 -0
  66. data/examples/functions/gram_schmidt.rb +10 -0
  67. data/examples/functions/minimize_energy.rb +29 -0
  68. data/examples/functions/orthoganalize.rb +18 -0
  69. data/examples/functions/simple_functions.rb +81 -0
  70. data/examples/linear_algebra/diverging_sys.rb +13 -0
  71. data/examples/linear_algebra/iterative_sys_of_eqns_methods.rb +27 -0
  72. data/examples/modulation_schemes/dpsk_2.png +0 -0
  73. data/examples/modulation_schemes/dpsk_256.png +0 -0
  74. data/examples/modulation_schemes/dpsk_freq_domain.rb +119 -0
  75. data/examples/modulation_schemes/psk.rb +36 -0
  76. data/examples/modulation_schemes/psk_2.png +0 -0
  77. data/examples/modulation_schemes/psk_256.png +0 -0
  78. data/examples/modulation_schemes/psksystem_1_xmit_signal.png +0 -0
  79. data/examples/modulation_schemes/psksystem_2_xmit_signal.png +0 -0
  80. data/examples/modulation_schemes/psksystem_3_xmit_signal.png +0 -0
  81. data/examples/modulation_schemes/system_1_xmit_signal.png +0 -0
  82. data/examples/modulation_schemes/system_2_xmit_signal.png +0 -0
  83. data/examples/modulation_schemes/system_3_xmit_signal.png +0 -0
  84. data/examples/quickplot/PlottableClass_plot.png +0 -0
  85. data/examples/quickplot/decorators.rb +13 -0
  86. data/examples/quickplot/direct_gruff.png +0 -0
  87. data/examples/quickplot/plot_PlottableClass.png +0 -0
  88. data/examples/quickplot/quickplot_vs_others.rb +85 -0
  89. data/examples/quickplot/random_data_quickplot,_dark.png +0 -0
  90. data/examples/quickplot/random_data_quickplot.png +0 -0
  91. data/examples/realized_gaussian/norm_dist_plot.png +0 -0
  92. data/examples/realized_gaussian/norm_dist_spectrum.png +0 -0
  93. data/examples/realized_gaussian/realized_gaussian_example.rb +23 -0
  94. data/lib/concerns/convolvable.rb +144 -0
  95. data/lib/concerns/data_properties.rb +223 -0
  96. data/lib/concerns/fourier_transformable.rb +178 -0
  97. data/lib/concerns/initializable.rb +43 -0
  98. data/lib/concerns/multipliable.rb +22 -0
  99. data/lib/concerns/os.rb +36 -0
  100. data/lib/concerns/plottable.rb +248 -0
  101. data/lib/concerns/requires_data.rb +8 -0
  102. data/lib/digiproc/version.rb +8 -0
  103. data/lib/digiproc.rb +2 -0
  104. data/lib/extensions/array_extension.rb +23 -0
  105. data/lib/extensions/core_extensions.rb +117 -0
  106. data/lib/factories/factories.rb +3 -0
  107. data/lib/factories/filter_factory.rb +83 -0
  108. data/lib/factories/window_factory.rb +22 -0
  109. data/lib/fft.rb +255 -0
  110. data/lib/filters/bandpass_filter.rb +43 -0
  111. data/lib/filters/bandstop_filter.rb +44 -0
  112. data/lib/filters/digital_filter.rb +59 -0
  113. data/lib/filters/highpass_filter.rb +27 -0
  114. data/lib/filters/lowpass_filter.rb +27 -0
  115. data/lib/functions.rb +221 -0
  116. data/lib/probability/binomial_distribution.rb +84 -0
  117. data/lib/probability/bit_generator.rb +94 -0
  118. data/lib/probability/gaussian_distribution.rb +29 -0
  119. data/lib/probability/probability.rb +234 -0
  120. data/lib/probability/theoretical_gaussian_distribution.rb +59 -0
  121. data/lib/quick_plot.rb +96 -0
  122. data/lib/rbplot.rb +219 -0
  123. data/lib/signals/analog_signal.rb +143 -0
  124. data/lib/signals/digital_signal.rb +181 -0
  125. data/lib/strategies/code/differential_encoding_strategy.rb +69 -0
  126. data/lib/strategies/code/gray_code.rb +75 -0
  127. data/lib/strategies/code/xor_differential_encoding_strategy.rb +100 -0
  128. data/lib/strategies/code/xor_differential_encoding_zero_angle_strategy.rb +103 -0
  129. data/lib/strategies/companding/custom_companding_strategy.rb +29 -0
  130. data/lib/strategies/convolution/bf_conv.rb +57 -0
  131. data/lib/strategies/fft/brute_force_dft_strategy.rb +31 -0
  132. data/lib/strategies/fft/inverse_fft_conjugate_strategy.rb +44 -0
  133. data/lib/strategies/fft/radix2_strategy.rb +84 -0
  134. data/lib/strategies/gaussian/gaussian_generator.rb +49 -0
  135. data/lib/strategies/linear_algebra/gauss_seidel_strategy.rb +90 -0
  136. data/lib/strategies/linear_algebra/jacobi_strategy.rb +81 -0
  137. data/lib/strategies/linear_algebra/sor2_strategy.rb +98 -0
  138. data/lib/strategies/linear_algebra/sor_strategy.rb +108 -0
  139. data/lib/strategies/modulation/phase_shift_keying_strategy.rb +96 -0
  140. data/lib/strategies/orthogonalize/gram_schmidt.rb +50 -0
  141. data/lib/strategies/strategies.rb +3 -0
  142. data/lib/strategies/window/blackman_window.rb +32 -0
  143. data/lib/strategies/window/hamming_window.rb +31 -0
  144. data/lib/strategies/window/hanning_window.rb +31 -0
  145. data/lib/strategies/window/kaiser_window.rb +27 -0
  146. data/lib/strategies/window/rectangular_window.rb +22 -0
  147. data/lib/strategies/window/window.rb +42 -0
  148. data/lib/systems/custom_system.rb +13 -0
  149. data/lib/systems/hilbert_transform.rb +6 -0
  150. data/lib/systems/matched_filter.rb +21 -0
  151. data/lib/systems/raised_cosine_filter.rb +11 -0
  152. data/lib/systems/system.rb +19 -0
  153. data/lib/systems/systems.rb +3 -0
  154. data/playground.rb +323 -0
  155. data/plots/_coded_frequency_signal,_ts_=_1_s.png +0 -0
  156. data/plots/_coded_frequency_signal,_ts_=_2_s.png +0 -0
  157. data/plots/coded_freq_sig_from_eqn,_ts_=_1_s.png +0 -0
  158. data/plots/coded_freq_sig_from_eqn,_ts_=_2_s.png +0 -0
  159. data/plots/coded_power_spectral_density,__ts_=_1_s.png +0 -0
  160. data/plots/coded_power_spectral_density,__ts_=_2_s.png +0 -0
  161. data/plots/coded_time_signal,_ts_=_1_s.png +0 -0
  162. data/plots/coded_time_signal,_ts_=_2_s.png +0 -0
  163. data/plots/dpsk_2.png +0 -0
  164. data/plots/freq_sig_from_eqn,_ts_=_1_s.png +0 -0
  165. data/plots/freq_sig_from_eqn,_ts_=_2_s.png +0 -0
  166. data/plots/frequency_signal,_ts_=_1_s.png +0 -0
  167. data/plots/frequency_signal,_ts_=_2_s.png +0 -0
  168. data/plots/power_spectral_density,__ts_=_1_s.png +0 -0
  169. data/plots/power_spectral_density,__ts_=_2_s.png +0 -0
  170. data/plots/psk_2.png +0 -0
  171. data/plots/time_signal,_ts_=_1_s.png +0 -0
  172. data/plots/time_signal,_ts_=_2_s.png +0 -0
  173. data/test-title-dark.png +0 -0
  174. data/test-title.png +0 -0
  175. metadata +322 -0
@@ -0,0 +1,100 @@
1
+ ##
2
+ # Class used as a strategy for differentially encoding a bitstream
3
+ # by XORing it with a time-delayed version of itself:
4
+ # Differentially encode the binary stream using the XOR operator,
5
+ # and then map the binary stream using the equation
6
+ # βn = (2l−1)π %2π, l⩽M, which gives you a mapping of 0=> −π ,1=> π
7
+ # The Differentially Encoded Signal => Cn = Dn ⨁ Cn−1
8
+ # is the signal mapped to phase as described above.
9
+ # The XOR operation in seeded with a 1
10
+ class Digiproc::Strategies::XORDifferentialEncodingStrategy
11
+
12
+ ##
13
+ # Accept an int reprisenting a bit stream. Encode the
14
+ # bits by XORing it with a time delay of itself via
15
+ # the `self.encode_str` method below. The first bit is seeded
16
+ # with a "1"
17
+ def self.encode_bits(bits)
18
+ encode_str(bits.to_s(2))
19
+ end
20
+
21
+ ##
22
+ # Input a string of bits
23
+ # Outupt the XOR'd version of the bits, seeded with a beginning "1"
24
+ def self.encode_str(bits)
25
+ bits_arr = bits.split("")
26
+ encoded = ["1"]
27
+ for i in 0...bits_arr.length do
28
+ encoded << (encoded.last.to_i(2) ^ bits_arr[i].to_i(2)).to_s
29
+ end
30
+ encoded.join
31
+ end
32
+
33
+ ##
34
+ # Encoding an incoming array of bits (as strings) into an array of XOR'd bits
35
+ # Requires an input of an array, and has optional arguments of m (number of bits per symbol)
36
+ # And a beginning value (a starting reference phase angle). Outputs an array of
37
+ # XOR'd bits
38
+ def self.encode(arr, m = 2, beginning_val = "1")
39
+ beginning_val = beginning_val.to_s(2) unless beginning_val.is_a? String
40
+ encoded = [beginning_val]
41
+ for i in 0...arr.length do
42
+ encoded << (encoded.last.to_i(2) ^ arr[i].to_i(2)).to_s
43
+ end
44
+ encoded
45
+ end
46
+
47
+ ##
48
+ # Input is an integer
49
+ # The method calls the `decode_str` method below and inputs
50
+ # a string of the binary of the input integer.
51
+ # The output will be the original bitstream encoded by this encoding strategy
52
+ # in string form
53
+ def self.decode_bits(bits)
54
+ dencode_str(bits.to_s(2))
55
+ end
56
+
57
+
58
+
59
+ ##
60
+ # Input an encoded binary bit stream in string form
61
+ # Output a string of the original bitstream
62
+ def self.decode_str(bits)
63
+ bits_arr = bits.split("")
64
+ encoded = []
65
+ for i in 1...bits_arr.length do
66
+ encoded << (bits_arr[i - 1].to_i(2) ^ bits_arr[i].to_i(2)).to_s
67
+ end
68
+ encoded.join
69
+ end
70
+
71
+ ##
72
+ # Input an array of encoded bits (as strings)
73
+ # Output an array of decoded bits
74
+ def self.decode(bits)
75
+ encoded = []
76
+ for i in 1...bits.length do
77
+ encoded << (bits[i - 1].to_i.to_s.to_i(2) ^ bits[i].to_i.to_s.to_i(2)).to_s
78
+ end
79
+ encoded
80
+ end
81
+
82
+ ##
83
+ # Return a lambda which transforms a bit into a phase
84
+ # Input an integer specifying the number of bits in a symbol
85
+ # Input to the returned lambda should be the symbol value in decimal form
86
+ # Ouptut of the lambda is the encoded phase angle
87
+ def self.phase_shift_eqn(m)
88
+ ->(l){ (((2.0 * (l+1) - 1.0) / m) % 2) * Math::PI }
89
+ end
90
+
91
+ ##
92
+ # Return a lambda which transforms a pahse into a symbol
93
+ # Input an integer specifying the number of bits in a symbol
94
+ # Input to the returned lambda is the encoded phase angle
95
+ # Output of the lambda is the binary symbol
96
+ def self.phase_to_sym(m)
97
+ ->(code){ (((code / (Math::PI)) * m) + 1.0) / 2.0 - 1.0 }
98
+ end
99
+
100
+ end
@@ -0,0 +1,103 @@
1
+ ##
2
+ # Class used as a strategy for differentially encoding a bitstream
3
+ # by XORing it with a time-delayed version of itself:
4
+ # Differentially encode the binary stream using the XOR operator,
5
+ # and then map the binary stream using the equation
6
+ # βn = 2π(i−1) , i = 1..M which gives you a mapping of 0 => 0, 1 => π
7
+ # The Differentially Encoded Signal => Cn = Dn ⨁ Cn−1
8
+ # is the signal mapped to phase as described above.
9
+ # The XOR operation in seeded with a 1
10
+
11
+ class Digiproc::Strategies::XORDifferentialEncodingZeroAngleStrategy
12
+
13
+ ##
14
+ # Accept an int reprisenting a bit stream. Encode the
15
+ # bits by XORing it with a time delay of itself via
16
+ # the `self.encode_str` method below. The first bit is seeded
17
+ # with a "1"
18
+ def self.encode_bits(bits)
19
+ encode_str(bits.to_s(2))
20
+ end
21
+
22
+
23
+ ##
24
+ # Input a string of bits
25
+ # Outupt the XOR'd version of the bits, seeded with a beginning "1"
26
+ def self.encode_str(bits)
27
+ bits_arr = bits.split("")
28
+ encoded = ["1"]
29
+ for i in 0...bits_arr.length do
30
+ encoded << (encoded.last.to_i(2) ^ bits_arr[i].to_i(2)).to_s
31
+ end
32
+ encoded.join
33
+ end
34
+
35
+ ##
36
+ # Encoding an incoming array of bits (as strings) into an array of XOR'd bits
37
+ # Requires an input of an array, and has optional arguments of m (number of bits per symbol)
38
+ # And a beginning value (a starting reference phase angle). Outputs an array of
39
+ # XOR'd bits
40
+ def self.encode(arr, m = 2, beginning_val = "1")
41
+ beginning_val = beginning_val.to_s(2) unless beginning_val.is_a? String
42
+ encoded = [beginning_val]
43
+ for i in 0...arr.length do
44
+ encoded << (encoded.last.to_i(2) ^ arr[i].to_i(2)).to_s(2)
45
+ end
46
+ encoded
47
+ end
48
+
49
+
50
+ ##
51
+ # Input is an integer
52
+ # The method calls the `decode_str` method below and inputs
53
+ # a string of the binary of the input integer.
54
+ # The output will be the original bitstream encoded by this encoding strategy
55
+ # in string form
56
+ def self.decode_bits(bits)
57
+ dencode_str(bits.to_s(2))
58
+ end
59
+
60
+
61
+ ##
62
+ # Input an encoded binary bit stream in string form
63
+ # Output a string of the original bitstream
64
+ def self.decode_str(bits)
65
+ bits_arr = bits.split("")
66
+ encoded = []
67
+ for i in 1...bits_arr.length do
68
+ encoded << (bits_arr[i - 1].to_i(2) ^ bits_arr[i].to_i(2)).to_s(2)
69
+ end
70
+ encoded.join
71
+ end
72
+
73
+
74
+ ##
75
+ # Input an array of encoded bits (as strings)
76
+ # Output an array of decoded bits
77
+ def self.decode(bits)
78
+ encoded = []
79
+ for i in 1...bits.length do
80
+ encoded << (bits[i - 1].to_i.to_s.to_i(2) ^ bits[i].to_i.to_s.to_i(2)).to_s(2)
81
+ end
82
+ encoded
83
+ end
84
+
85
+ ##
86
+ # Return a lambda which transforms a bit into a phase
87
+ # Input an integer specifying the number of bits in a symbol
88
+ # Input to the returned lambda should be the symbol value in decimal form
89
+ # Ouptut of the lambda is the encoded phase angle
90
+ def self.phase_shift_eqn(m)
91
+ ->(i){ (2 * Math::PI * (i)) / m }
92
+ end
93
+
94
+ ##
95
+ # Return a lambda which transforms a pahse into a symbol
96
+ # Input an integer specifying the number of bits in a symbol
97
+ # Input to the returned lambda is the encoded phase angle
98
+ # Output of the lambda is the binary symbol
99
+ def self.phase_to_sym(m)
100
+ ->(phase){ (phase * m / (2.0 * Math::PI))}
101
+ end
102
+
103
+ end
@@ -0,0 +1,29 @@
1
+ ##
2
+ # A class which allows a custom companding strategy to be used via a lambda function
3
+ # inputted into the initilizer.
4
+
5
+ class Digiproc::Strategies::CustomCompandingStrategy
6
+
7
+ attr_accessor :eqn, :inverse
8
+
9
+ # initialize wiht a companding equation (via a proc or lambda) as well as the inverse equation
10
+ def initialize(eqn, inverse)
11
+ @eqn, @inverse = eqn, inverse
12
+ end
13
+
14
+ # maps an array (first argument) with a lambda (second argument)
15
+ def process(data, fn)
16
+ data.map{ |n| fn.call(n) }
17
+ end
18
+
19
+ # use the compression companding lambda (or proc) to compress an array of numerics
20
+ def compress(data)
21
+ self.process(data, eqn)
22
+ end
23
+
24
+ # Use the inverse labda (or proc) to decompress an array of numerics
25
+ def expand(data)
26
+ self.process(data, inverse)
27
+ end
28
+
29
+ end
@@ -0,0 +1,57 @@
1
+ ##
2
+ # Strategy for convolving two arrays of numbers
3
+ # This is an O(n^2) operation, it is more time efficient
4
+ # to use FFT to perform this calculation
5
+ class Digiproc::Strategies::BFConvolutionStrategy
6
+
7
+ ##
8
+ # == Input Args
9
+ # data1:: Array[Numeric]
10
+ # data2:: Array[Numeric]
11
+ # == Output
12
+ # convolution:: Numeric
13
+ def self.conv(data1, data2)
14
+ dynamic_data = data1.dup
15
+ static_data = data2.dup
16
+ conv_sum = []
17
+ n = 0
18
+ start, stop = conv_overlap_area(static_data.length, dynamic_data.length, n)
19
+ while start <= stop
20
+ sum = 0
21
+ for val in start..stop
22
+ sum += static_data[val] * dynamic_data[transform_to_local_index(val, n)]
23
+ end
24
+ conv_sum << sum
25
+ n = conv_sum.length
26
+ start, stop = conv_overlap_area(static_data.length, dynamic_data.length, n)
27
+ end
28
+ conv_sum
29
+ end
30
+
31
+ private
32
+
33
+ ##
34
+ # Gives start and stop values of overlap inclusive
35
+ def self.conv_overlap_area(static_len, dynamic_len, n)
36
+ dynamic_lo = transform_to_global_index(dynamic_len - 1, n)
37
+ dynamic_hi = transform_to_global_index(0, n)
38
+ static_lo = 0
39
+ static_hi = static_len - 1
40
+ start = dynamic_lo > static_lo ? dynamic_lo : static_lo
41
+ stop = dynamic_hi < static_hi ? dynamic_hi : static_hi
42
+ return [start, stop]
43
+ end
44
+
45
+ def self.transform_index(index, xform)
46
+ xform.call(index)
47
+ end
48
+
49
+ def self.transform_to_global_index(index, n)
50
+ transform_index(index, ->(i){ n - i })
51
+ end
52
+
53
+ def self.transform_to_local_index(index, n)
54
+ transform_index(index, ->(i){ n - i})
55
+ end
56
+
57
+ end
@@ -0,0 +1,31 @@
1
+ ##
2
+ # A brute force Discrete Fourier Transform strategy
3
+ # O(n^2) algorith, no reason to sue it over FFT unless you
4
+ # don't want to work within the parameters of a Radix2 strategy
5
+ # (ie data points of DFT size will be a power of 2). This strategy does
6
+ # not have those parameters
7
+ class Digiproc::BFDFTStrategy
8
+
9
+ attr_accessor :data
10
+
11
+ # initialize with an array of numerics
12
+ def initialize(data)
13
+ @data = data.dup
14
+ end
15
+
16
+ # Calculate the DFT with an O(n^2) algorithm
17
+ # Can accept an array of numerics, with a default value of
18
+ # @data
19
+ def calculate(data = @data)
20
+ ft = []
21
+ for k in 0...data.length do
22
+ tot = 0
23
+ data.each_with_index do |x_n, n|
24
+ tot += x_n * Math::E ** (Complex(0,-1) * 2.0 * Math::PI * k * n / data.length.to_f)
25
+ end
26
+ ft << tot
27
+ end
28
+ ft
29
+ end
30
+
31
+ end
@@ -0,0 +1,44 @@
1
+ ##
2
+ # Strategy for calculating the Inverse Fast Fourier Transform (IFFT)
3
+ # O(nlgn) runtime, depends on the FFT strategy to calculate thie IFFT
4
+ # Uses "conjugate strategy"
5
+ class Digiproc::Strategies::IFFTConjugateStrategy
6
+
7
+ attr_accessor :data, :strategy
8
+
9
+ ##
10
+ # == Input args:
11
+ # data:: Array[Numeric] (DFT values)
12
+ # fft_strategy(Optional):: See Digiproc::Strategies::Radix2Strategy for required protocol (This is the default value)
13
+ def initialize(data, fft_strategy = Digiproc::Strategies::Radix2Strategy)
14
+ @data = data
15
+ @strategy = fft_strategy.new
16
+ end
17
+
18
+ ##
19
+ # No input args
20
+ # Calculate the IFFT given Discrete Fourier Transform (DFT) values
21
+ def calculate
22
+ strategy.data = conjugate(data)
23
+ fft_out = strategy.calculate
24
+ n = fft_out.length.to_f
25
+ conjugate(fft_out){ |real, imag| OpenStruct.new(real: (real / n), imaginary: (imag / n) ) }
26
+ end
27
+
28
+
29
+ private
30
+
31
+ def conjugate(data)
32
+ data.map do |value|
33
+ if value.is_a? Complex
34
+ complex_num = block_given? ? yield(value.real, value.imaginary) : OpenStruct.new(real: value.real, imaginary: value.imaginary)
35
+ Complex(complex_num.real, -1 * complex_num.imaginary)
36
+ else
37
+ real_num = block_given? ? yield(value, 0) : OpenStruct.new(real: value, imaginary: 0)
38
+ real_num.real
39
+ end
40
+ end
41
+ end
42
+
43
+
44
+ end
@@ -0,0 +1,84 @@
1
+ ##
2
+ # O(n*lgn) FFT algorithm
3
+ # Requires that the number of datapoints be a power of 2
4
+ # if it isn't the calculation will zero pad to the closest
5
+ # power of 2 above the current data size
6
+ # The size of the outputted DFT will be the size of the zero-padded
7
+ # dataset. Zero-padding causes the "sample locations" of the DFT to
8
+ # be at different locations than if it was not zero padded, but
9
+ # The resolution of the DFT will INCREASE due to zero padding
10
+ class Digiproc::Strategies::Radix2Strategy
11
+
12
+ E = Math::E
13
+ PI = Math::PI
14
+
15
+ attr_reader :data
16
+
17
+ ##
18
+ # == Initialize Arg
19
+ # data (Optional):: Array[Numeric] (defaults to nil)
20
+ def initialize(data = nil)
21
+ @data = data
22
+ zero_fill if not data.nil?
23
+ end
24
+
25
+ ##
26
+ # == Input arg
27
+ # data (Optional):: Array[Numiric] (defaults to @data)
28
+ def calculate(data = @data)
29
+ recursive_fft(data)
30
+ end
31
+
32
+ # @data writer, and zero fills array to
33
+ # obtain a size of the closest power of 2 above current size
34
+ def data=(data)
35
+ @data = data
36
+ zero_fill
37
+ end
38
+
39
+ private
40
+
41
+ def recursive_fft(arr)
42
+ n = arr.length
43
+ return arr if n == 1
44
+ w_n = E ** ((-2 * PI * Complex(0,1)) / n)
45
+ w = 1
46
+ a0 = get_even(arr)
47
+ a1 = get_odd(arr)
48
+ y0 = recursive_fft(a0)
49
+ y1 = recursive_fft(a1)
50
+ y = Array.new(n, 0)
51
+ for k in 0...(n/2) do
52
+ y[k] = (y0[k] + w * y1[k])
53
+ y[k + n/2] = (y0[k] - w * y1[k])
54
+ w = w * w_n
55
+ end
56
+ y
57
+ end
58
+
59
+ def get_even(arr)
60
+ even = []
61
+ arr.each_with_index { |a, i| even << a.dup if i % 2 == 0 }
62
+ even
63
+ end
64
+
65
+ def get_odd(arr)
66
+ odd = []
67
+ arr.each_with_index { |a, i| odd << a.dup if i % 2 == 1 }
68
+ odd
69
+ end
70
+
71
+ def zero_fill
72
+ len = self.data.length
73
+ @data = @data.concat Array.new(closest_pow_of_2(len) - len, 0)
74
+ end
75
+
76
+ def power_of_two?(num)
77
+ lg = Math.log(num,2)
78
+ lg.round.to_f == lg
79
+ end
80
+
81
+ def closest_pow_of_2(num)
82
+ 2 ** Math.log(num,2).ceil
83
+ end
84
+ end
@@ -0,0 +1,49 @@
1
+ ##
2
+ # Class for generating random numbers from a Gaussin Population
3
+ # of a given mean or standard deviation
4
+ class Digiproc::Strategies::GaussianGeneratorBoxMullerStrategy
5
+
6
+ ##
7
+ # No input args
8
+ # Returns 2 random numbers from a gaussain population with
9
+ # stddev of 1 and mean of 0
10
+ def self.rand2
11
+ uniform_random = Random.new
12
+ r = (-2 * Math.log(1 - uniform_random.rand)) ** 0.5
13
+ theta = 2 * Math::PI * (1 - uniform_random.rand)
14
+ return r * Math.cos(theta), r * Math.sin(theta)
15
+ end
16
+
17
+ attr_accessor :mean, :stddev
18
+
19
+ ##
20
+ # == Input Args:
21
+ # mean (Optional):: Numeric (default 0)
22
+ # stddev (Optional):: Numeric (default 1)
23
+ def initialize(mean = 0, stddev = 1)
24
+ @mean, @stddev = mean, stddev
25
+ @needs_gen = true
26
+ @next = nil
27
+ end
28
+
29
+ # Get a single random number from a Gaussian distribution with a
30
+ # mean and stddev as defined by @mean and @stddev
31
+ # Use the .rand2 method to get 2 random numbers. Save one, return the other
32
+ def rand
33
+ if @needs_gen
34
+ x,y = self.class.rand2
35
+ @next = y
36
+ @needs_gen = false
37
+ return self.mean + self.stddev * x
38
+ else
39
+ @needs_gen = true
40
+ return self.mean + self.stddev * @next
41
+ end
42
+ end
43
+
44
+ # Calls the .rand2 method
45
+ def rand2
46
+ self.class.rand2
47
+ end
48
+
49
+ end
@@ -0,0 +1,90 @@
1
+ ##
2
+ # This strategy will solve a system of linear equations using the Gauss Seidel iterative strategy
3
+ # Constructor Inputs: (a_arr, b_arr) correspond to A and B in the equation Ax = B (a should be an nxn 2D array, and b should be a 1D array (even though in the equation it is a column vector))
4
+ #
5
+ ## a = [[4,1,-1],[2,7,1],[1,-3,12]]
6
+ ## b = [3,19,31]
7
+ ## gs = Digiproc::Strategies::GaussSeidelStrategy.new(a,b)
8
+ ## x = gs.calculate # => Matrix[[0.9998668946614292], [2.000021547671973], [3.000054218557957]]
9
+
10
+
11
+ class Digiproc::Strategies::GaussSeidelStrategy
12
+
13
+
14
+ ##
15
+ # ==Initialize args
16
+ # a_arr:: 2D array representing your A matrix
17
+ # b_arr:: 1D array representing your B matrix
18
+ # Where B = Ax defines your series of linear equations
19
+ def initialize(a_arr,b_arr)
20
+ # TODO: Raise exception if a_arr is not square and b_arr row_count != a_arr row count
21
+ @b = Matrix.column_vector(b_arr)
22
+ @a = Matrix.rows(a_arr, true)
23
+ d_arr, l_arr, u_arr = [],[],[]
24
+ num_cols = @a.column_count
25
+ @a.row_count.times do
26
+ d_arr << Array.new(num_cols, 0)
27
+ l_arr << Array.new(num_cols, 0)
28
+ u_arr << Array.new(num_cols, 0)
29
+ end
30
+
31
+ @a.each_with_index do |el, row, col|
32
+ if row > col
33
+ l_arr[row][col] = el
34
+ elsif row < col
35
+ u_arr[row][col] = el
36
+ else
37
+ d_arr[row][col] = el
38
+ end
39
+ end
40
+ @d = Matrix.rows(d_arr)
41
+ @l = Matrix.rows(l_arr)
42
+ @u = Matrix.rows(u_arr)
43
+ #TODO: Ensure no zeros on diagonal
44
+ @dinv = @d.map{ |el| el == 0 ? 0 : 1.0 / el }
45
+ end
46
+
47
+ ##
48
+ # Iteratively solves the linear system of equations using the Gauss Seidel method
49
+ # accepts an optional parameter which is the threshold value x(n+1) - x(n) should achieve before returning.
50
+ # Must be used with a key-value pair ie
51
+ ## gs.calculate(threshold: 0.001)
52
+ # default threshold = 0.001
53
+ # Returns a column vector Matrix => access via matrix_return[row,col]
54
+ # If run with the option safety_net: true and the equation diverges, performs A_inverse * B to solve
55
+ # ie
56
+ ## s.calculate(safety_net: true)
57
+ #
58
+ def calculate(threshold: 0.001, safety_net: false)
59
+ dinv, b, l, u = @dinv, @b, @l, @u
60
+ c = dinv * b
61
+ t = -1 * dinv * (l + u)
62
+ x_n = Matrix.column_vector(Array.new(@a.column_count, 0))
63
+ counter = 0
64
+
65
+ #TODO: Investigate speed difference of using
66
+ # x_new = c + t*x_old , where:
67
+ # c = (l + d).inv * b
68
+ # t = -1 * (l + d).inv * u
69
+ loop do
70
+ x_n_plus_1 = x_n.dup
71
+ for i in 0...x_n.row_count do
72
+ x_n_i = c.row(i).to_matrix + t.row(i).to_matrix.transpose * x_n_plus_1
73
+ # puts "#{c.row(i).to_matrix} + #{t.row(i).to_matrix.transpose} * #{x_n_plus_1} = #{x_n_i}"
74
+ x_n_plus_1[i,0] = x_n_i[0,0]
75
+ end
76
+ x_difference = (x_n_plus_1 - x_n).map{ |el| el.abs }
77
+ should_break = !x_difference.find{ |el| el > threshold }
78
+ x_n = x_n_plus_1
79
+ break if should_break
80
+ counter += 1
81
+ if counter > 1000000 and safety_net
82
+ return (@a.inv * b).map{ |el| el.to_f}
83
+ end
84
+ end
85
+ return (safety_net and x_n.find{ |el| el.to_f.nan? }) ? (@a.inv * b).map{ |el| el.to_f} : x_n
86
+ end
87
+
88
+
89
+
90
+ end
@@ -0,0 +1,81 @@
1
+
2
+ ##
3
+ # This strategy will solve a system of linear equations using the Jacobi iterative strategy
4
+ # Constructor Inputs: (a_arr, b_arr) correspond to A and B in the equation Ax = B (a should be an nxn 2D array, and b should be a 1D array (even though in the equation it is a column vector))
5
+ #
6
+ ## a = [[4,1,-1],[2,7,1],[1,-3,12]]
7
+ ## b = [3,19,31]
8
+ ## gs = Digiproc::Strategies::JacobiStrategy.new(a,b)
9
+ ## x = gs.calculate # => Matrix[[0.9998668946614292], [2.000021547671973], [3.000054218557957]]
10
+
11
+ class Digiproc::Strategies::JacobiStrategy
12
+
13
+ attr_reader :a, :b, :d, :u, :l, :dinv
14
+
15
+ ##
16
+ # ==Initialize args
17
+ # a_arr:: 2D array representing your A matrix
18
+ # b_arr:: 1D array representing your B matrix
19
+ # Where B = Ax defines your series of linear equations
20
+ def initialize(a_arr,b_arr)
21
+ # TODO: Raise exception if a_arr is not square and b_arr row_count != a_arr row count
22
+ @b = Matrix.column_vector(b_arr)
23
+ @a = Matrix.rows(a_arr, true)
24
+ d_arr, l_arr, u_arr = [],[],[]
25
+ num_cols = @a.column_count
26
+ @a.row_count.times do
27
+ d_arr << Array.new(num_cols, 0)
28
+ l_arr << Array.new(num_cols, 0)
29
+ u_arr << Array.new(num_cols, 0)
30
+ end
31
+
32
+ @a.each_with_index do |el, row, col|
33
+ if row > col
34
+ l_arr[row][col] = el
35
+ elsif row < col
36
+ u_arr[row][col] = el
37
+ else
38
+ d_arr[row][col] = el
39
+ end
40
+ end
41
+ @d = Matrix.rows(d_arr)
42
+ @l = Matrix.rows(l_arr)
43
+ @u = Matrix.rows(u_arr)
44
+ #TODO: Ensure no zeros on diagonal
45
+ @dinv = @d.map{ |el| el == 0 ? 0 : 1.0 / el }
46
+ end
47
+
48
+ ##
49
+ # Iteratively solves the linear system of equations using the jacobi method
50
+ # accepts an optional parameter which is the threshold value x(n+1) - x(n) should achieve before returning.
51
+ # Must be used with a key-value pair ie
52
+ ## gs.calculate(threshold: 0.001)
53
+ # default threshold = 0.001
54
+ # Returns a column vector Matrix => access via matrix_return[row,col]
55
+ # If run with the option safety_net: true and the equation diverges, performs A_inverse * B to solve
56
+ # ie
57
+ ## gs.calculate(safety_net: true)
58
+ #
59
+ def calculate(threshold: 0.001, safety_net: false)
60
+ dinv, b, l, u = @dinv, @b, @l, @u
61
+ c = dinv * b
62
+ t = -1 * dinv * (l + u)
63
+ x_n = Matrix.column_vector(Array.new(@a.column_count, 0))
64
+ counter = 0
65
+ loop do
66
+ x_n_plus_1 = c + t * x_n
67
+ x_difference = (x_n_plus_1 - x_n).map{ |el| el.abs }
68
+ # puts x_n_plus_1
69
+ should_break = !x_difference.find{ |el| el > threshold }
70
+ x_n = x_n_plus_1
71
+ break if should_break
72
+ counter += 1
73
+ if counter > 10000000 and safety_net
74
+ return (@a.inv * b).map{ |el| el.to_f}
75
+ end
76
+ end
77
+ return (safety_net and x_n.find{ |el| el.to_f.nan? }) ? (@a.inv * b).map{ |el| el.to_f} : x_n
78
+ end
79
+
80
+
81
+ end