digiproc 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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