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,98 @@
1
+ ##
2
+ # This strategy will solve a system of linear equations using the Successive Over Relaxation 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
+ ## sorm = Digiproc::Strategies::SorStrategy.new(a,b)
8
+ ## x = sorm.calculate # => Matrix[[0.9998668946614292], [2.000021547671973], [3.000054218557957]]
9
+
10
+
11
+ class Digiproc::Strategies::Sor2Strategy
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 Successive Over Relaxation 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
+ ## sorm.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
+ ## sorm.calculate(safety_net: true)
57
+ # You can change the weighting factor w by inputting `w` as an argument, ie:
58
+ ## sorm.calculate(w: 0.35)
59
+ # The default value is w = 0.95
60
+ # The weighting factor does an average on each iteration of x_n between x_n and x_n+1, ie:
61
+ # x_1_new = ((1-w) * x_1_old) + w (eqn_for_x_1_new using x_k_olds)
62
+ # Whereas the gauss seidel strategy would just be
63
+ # x_1_new = (eqn_for_x_1_new using x_k_olds)
64
+ # Threshold strategy is by default set to test the euclidean norm of the delta x matrix. If euclid_norm is manually
65
+ # set to false, then it will compare element by element to ensure each term in delta_x is less than the threshold
66
+ def calculate(w: 0.95, threshold: 0.001, safety_net: false, euclid_norm: true)
67
+ dinv, b, l, u, d = @dinv, @b, @l, @u, @d
68
+ c = (l + d).inv * b
69
+ t = -1 * (l + d).inv * u
70
+ x_n = Matrix.column_vector(Array.new(@a.column_count, 0))
71
+ counter = 0
72
+
73
+ loop do
74
+ x_n_plus_1 = ((1-w) * x_n) + w * (c + t * x_n)
75
+ x_difference = (x_n_plus_1 - x_n).map{ |el| el.abs }
76
+ should_break = euclid_norm ? break_euclid?(x_difference, threshold) : break_threshold?(x_difference, threshold)
77
+ x_n = x_n_plus_1
78
+ break if should_break
79
+ counter += 1
80
+ if counter > 100000 and safety_net
81
+ return (@a.inv * b).map{ |el| el.to_f}
82
+ end
83
+ end
84
+ return (safety_net and x_n.find{ |el| el.to_f.nan? }) ? (@a.inv * b).map{ |el| el.to_f} : x_n
85
+ end
86
+
87
+
88
+ private
89
+
90
+ def break_euclid?(delta_x, norm_threshold)
91
+ euclid_norm = delta_x.map{ |el| el ** 2 }.sum ** 0.5 < norm_threshold
92
+ end
93
+
94
+ def break_threshold?(delta_x, threshold)
95
+ !delta_x.find{ |el| el > threshold }
96
+ end
97
+
98
+ end
@@ -0,0 +1,108 @@
1
+ ##
2
+ # This strategy will solve a system of linear equations using the Successive Over Relaxation 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
+ ## sorm = Digiproc::Strategies::SorStrategy.new(a,b)
8
+ ## x = sorm.calculate # => Matrix[[0.9998668946614292], [2.000021547671973], [3.000054218557957]]
9
+
10
+
11
+ class Digiproc::Strategies::SorStrategy
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 Successive Over Relaxation 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
+ ## sorm.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
+ ## sorm.calculate(safety_net: true)
57
+ # You can change the weighting factor w by inputting `w` as an argument, ie:
58
+ ## sorm.calculate(w: 0.35)
59
+ # The default value is w = 0.95
60
+ # The weighting factor does an average on each iteration of x_n between x_n and x_n+1, ie:
61
+ # x_1_new = ((1-w) * x_1_old) + w (eqn_for_x_1_new using x_k_olds)
62
+ # Whereas the gauss seidel strategy would just be
63
+ # x_1_new = (eqn_for_x_1_new using x_k_olds)
64
+ # Threshold strategy is by default set to test the euclidean norm of the delta x matrix. If euclid_norm is manually
65
+ # set to false, then it will compare element by element to ensure each term in delta_x is less than the threshold
66
+ def calculate(w: 0.95, threshold: 0.001, safety_net: false, euclid_norm: true)
67
+ dinv, b, l, u = @dinv, @b, @l, @u
68
+ c = dinv * b
69
+ t = -1 * dinv * (l + u)
70
+ x_n = Matrix.column_vector(Array.new(@a.column_count, 0))
71
+ counter = 0
72
+
73
+ #TODO: Investigate speed difference of using
74
+ # x_new = c + t*x_old , where:
75
+ # c = (l + d).inv * b
76
+ # t = -1 * (l + d).inv * u
77
+ loop do
78
+ x_n_plus_1 = x_n.dup
79
+ for i in 0...x_n.row_count do
80
+ x_n_i = ( Matrix[[(1-w) * x_n[i,0]]] ) + w * (c.row(i).to_matrix + t.row(i).to_matrix.transpose * x_n_plus_1)
81
+ # puts "#{Matrix[[(1-w) * x_n[0,0]]]} + #{w} * #{c.row(i).to_matrix} + #{t.row(i).to_matrix.transpose} * #{x_n_plus_1} = #{x_n_i}"
82
+ x_n_plus_1[i,0] = x_n_i[0,0]
83
+ # puts x_n_plus_1[0,0]
84
+ end
85
+ x_difference = (x_n_plus_1 - x_n).map{ |el| el.abs }
86
+ should_break = euclid_norm ? break_euclid?(x_difference, threshold) : break_threshold?(x_difference, threshold)
87
+ x_n = x_n_plus_1
88
+ break if should_break
89
+ counter += 1
90
+ if counter > 100000 and safety_net
91
+ return (@a.inv * b).map{ |el| el.to_f}
92
+ end
93
+ end
94
+ return (safety_net and x_n.find{ |el| el.to_f.nan? }) ? (@a.inv * b).map{ |el| el.to_f} : x_n
95
+ end
96
+
97
+
98
+ private
99
+
100
+ def break_euclid?(delta_x, norm_threshold)
101
+ euclid_norm = delta_x.map{ |el| el ** 2 }.sum ** 0.5 < norm_threshold
102
+ end
103
+
104
+ def break_threshold?(delta_x, threshold)
105
+ !delta_x.find{ |el| el > threshold }
106
+ end
107
+
108
+ end
@@ -0,0 +1,96 @@
1
+ class Digiproc::Strategies::PSK
2
+
3
+ attr_accessor :m, :modulating_signal , :carrier_signal_eqn, :coding_strategy, :phase_shift_eqn, :signal_to_phase, :coded_signal, :phase_signal, :carrier_frequency, :pulse_length
4
+
5
+ ##
6
+ # == Initialize Args
7
+ # modulating_signal:: Array[String]; each element is a "symbol" (should be a string reprisenting bits), where a symnbol is a number of bits symbolizing a single character (ie symbol means character)
8
+ # carrier_signal_eqn (optional):: Lambda (or Proc) default value is: ->(a, fo, t, theta){ a * Math.cos(2*Math::PI * fo * t + theta) }
9
+ # coding_strategy (optional):: Protocol, see Digiproc::Strategies::XORDifferentialEncodingZeroAngleStrategy, default value is nil
10
+ # carrier_frequency (optional)::Numeric (in Hz), defaults to 10000
11
+ # pulse_length (optional):: Float (in seconds), defaults to 0.00015 determines how long to apply a phase shift
12
+ def initialize(carrier_signal_eqn: ->(a, fo, t, theta){ a * Math.cos(2*Math::PI * fo * t + theta) }, modulating_signal: ,coding_strategy: nil, carrier_frequency: 10000, pulse_length: 0.00015)
13
+ @carrier_signal_eqn, @modulating_signal, @coding_strategy, @carrier_frequency, @pulse_length = carrier_signal_eqn, modulating_signal, coding_strategy, carrier_frequency, pulse_length
14
+ @m = 2 ** modulating_signal.first.length
15
+ if coding_strategy.nil?
16
+ @phase_shift_eqn = ->(i){ (2.0 * Math::PI * (i)) / @m }
17
+ @coded_signal = @modulating_signal
18
+ else
19
+ @phase_shift_eqn = coding_strategy.phase_shift_eqn(@m)
20
+ @coded_signal = coding_strategy.encode modulating_signal, @m, "0"
21
+ end
22
+ @signal_to_phase = {}
23
+ for i in 0...@m do
24
+ @signal_to_phase[i.to_s] = @phase_shift_eqn[i]
25
+ end
26
+
27
+ @phase_signal = @coded_signal.map{ |coded_symbol| @phase_shift_eqn[coded_symbol.to_i(2)] }
28
+
29
+ end
30
+
31
+ ##
32
+ # == Input Args
33
+ # a (optional):: Numeric for amplitude of the signal (default value = 1)
34
+ # Return Digiproc::AnalogSignal of the Phase Shift Keyed signal
35
+ def output(a: 1)
36
+ eqn = Proc.new do |t|
37
+ theta_index = (t.to_f / @pulse_length.to_f).floor
38
+ @carrier_signal_eqn[a, @carrier_frequency, t, @phase_signal[theta_index]]
39
+ end
40
+ sample_rate = 0.01 / @carrier_frequency
41
+ size = @pulse_length.to_f * @phase_signal.length / sample_rate
42
+ Digiproc::AnalogSignal.new(eqn: eqn, sample_rate: sample_rate, size: size)
43
+ end
44
+
45
+ ##
46
+ # Apply inverse of the PSK strategy to decode the PSK signal
47
+ def decode
48
+ eqn = coding_strategy.nil? ? decode_eqn : coding_strategy.phase_to_sym(@m)
49
+ sym = @phase_signal.map{ |phase| eqn[phase] }
50
+ sym = coding_strategy.decode(sym) if not coding_strategy.nil?
51
+ return sym
52
+ end
53
+
54
+ ##
55
+ # Based off of reciever Figure 4-15 INTRODUCTION TO DIGITAL COMMUNICATIONS, ZIEMER, PETERSON pg 237
56
+ # NOTE: SUBOPTIMUM DETECTOR, optimum detector referenced on the above page, different reference
57
+ # This simulates a real-life reciever recieving the siglans and decoding them
58
+ def reciever_decode
59
+ recieved = output.to_ds.fft.magnitude
60
+ ts = 1 / (@carrier_frequency / 0.01)
61
+ max_freq = 1.0 / ts
62
+ normalized_target = @carrier_frequency / max_freq.to_f
63
+ bw = (0.57 / ts) / max_freq.to_f
64
+ plt = Digiproc::QuickPlot
65
+ factory = Digiproc::Factories::FilterFactory
66
+ bpf = factory.filter_for(type: "bandpass", wo: normalized_target * Math::PI * 2, bw: bw * Math::PI * 2, transition_width: 0.0001, stopband_attenuation: 80)
67
+ bpf.fft.calculate_at_size(2 ** 16)
68
+
69
+ filtered = (bpf.fft * output.to_ds.fft(2** 16)).ifft.map(&:real)
70
+ sample_interval = @pulse_length / (0.01 / @carrier_frequency)
71
+ output = []
72
+ for i in (sample_interval + 1).to_i...filtered.length do
73
+ output << (filtered[i] * filtered[i-sample_interval])
74
+ end
75
+ lpf = factory.filter_for(type: "lowpass", wc: 2 * Math::PI / 4, transition_width: 0.0001, stopband_attenuation: 80)
76
+ outft = Digiproc::FFT.new(time_data: output, size: 2 ** 16)
77
+
78
+ # TODO FIND WHERE SIGNAL BEGINS AND OUTPUT THE SIGNAL BASED OFF OF
79
+ # SIGNAL SIZE. ALSO, THERE SHOULD BE A RECIEVER METHOD OR SEPERATE CLASS
80
+ # WHICH OUPUTS THE RAW DATA OF THE RECIEVERS
81
+ data = (outft * lpf.fft).ifft.map(&:real)
82
+ all_out = @phase_signal.size > 200 ? data[55000, 200 * sample_interval].concat(data[0, sample_interval * (@phase_signal.size - 200)]) : data[55000, @phase_signal.size * sample_interval]
83
+ final_out = []
84
+ for i in 0...all_out.length do
85
+ final_out << all_out[i] if (i + 10) % sample_interval == 0
86
+ end
87
+ final_out
88
+ end
89
+
90
+
91
+ private
92
+
93
+ def decode_eqn
94
+ ->(phase){ (phase * @m / (2.0 * Math::PI))}
95
+ end
96
+ end
@@ -0,0 +1,50 @@
1
+ ##
2
+ # Class to orthonormalize a set of numbers
3
+ class Digiproc::Strategies::GramSchmidtOrthonormalize
4
+
5
+ attr_reader :original_matrix, :orthonormalized_matrix
6
+
7
+ ##
8
+ # == Input Args
9
+ # matrix:: 2D array, each row is a set of numbers that should be orthogonalized
10
+ def initialize(matrix)
11
+ @original_vectors = matrix
12
+ # if matrix.is_a? Array
13
+ # matrix = Vector.elements(matrix)
14
+ # end
15
+ vector_matrix = matrix.map { |vector| vector.is_a?(Array) ? Vector.elements(vector) : vector }
16
+ @orthonormalized_matrix = gram_schmidt(vector_matrix)
17
+ end
18
+
19
+ ##
20
+ # No input arguments
21
+ # Output is a 2D array of Orthonormalized sets of numbers corresponding to the input set of numbers
22
+ def output
23
+ @orthonormalized_matrix.map{|vector| vector.to_a }
24
+ end
25
+
26
+ private
27
+
28
+ def gram_schmidt(matrix)
29
+ u = []
30
+ u << normalize_vector(matrix.first)
31
+ for i in 1...matrix.length do
32
+ u << matrix[i]
33
+ for j in 0...i
34
+ u[i] = u[i] - u[i].project_onto(u[j])
35
+ # puts u.last.to_s if i == matrix.length - 1
36
+ end
37
+ u[i] = normalize_vector(u[i])
38
+ end
39
+ return u
40
+ end
41
+
42
+ def normalize_vector(vector)
43
+ begin
44
+ vector.normalize
45
+ rescue Vector::ZeroVectorError
46
+ vector
47
+ end
48
+ end
49
+
50
+ end
@@ -0,0 +1,3 @@
1
+ module Digiproc::Strategies
2
+
3
+ end
@@ -0,0 +1,32 @@
1
+ ##
2
+ # Blackman Window
3
+ # Used to improve digital filters by using a non-retangular frequency domain window
4
+ class Digiproc::BlackmanWindow < Digiproc::WindowStrategy
5
+
6
+ ##
7
+ # == Input Args
8
+ # size (Optional):: Numeric (default: nil), how many datapoings the window should have
9
+ # norm_trans_freq:: Numeric (default: nil), the desired transition frequency
10
+ # If you know what size of the the window that you need, you can input size without norm_trans_freq
11
+ # If you kow the desired transition frequency, the necessary size will be calculated for you based off of
12
+ # the window type so it is not necessary to enter the size
13
+ def initialize(size: nil , norm_trans_freq: nil)
14
+ super(size: norm_trans_freq.nil? ? size : find_size(norm_trans_freq))
15
+ #size = @size + 2
16
+ #@equation = lambda { |n| 0.42 - 0.5 * Math.cos(2 * PI * (n + 1) / (size - 1)) + 0.08 * Math.cos(4*PI*(n + 1) / (size - 1)) }
17
+ @equation = lambda { |n| 0.42 - 0.5 * Math.cos(2 * PI * (n) / (size - 1)) + 0.08 * Math.cos(4*PI*(n) / (size - 1)) }
18
+ calculate
19
+ @values = @values.take(@size)
20
+ end
21
+
22
+ # Given a freqency, return the required size of a BlackmanWindow
23
+ def find_size(freq)
24
+ size = 5.5 / freq
25
+ make_odd(size.ceil)
26
+ end
27
+
28
+ # Return the transition width (in rad/s) based off of the size
29
+ def transition_width
30
+ 5.5 / @size
31
+ end
32
+ end
@@ -0,0 +1,31 @@
1
+ ##
2
+ # Hamming Window
3
+ # Used to improve digital filters by using a non-retangular frequency domain window
4
+ class Digiproc::HammingWindow < Digiproc::WindowStrategy
5
+
6
+ ##
7
+ # == Input Args
8
+ # size (Optional):: Numeric (default: nil), how many datapoings the window should have
9
+ # norm_trans_freq:: Numeric (default: nil), the desired transition frequency
10
+ # If you know what size of the the window that you need, you can input size without norm_trans_freq
11
+ # If you kow the desired transition frequency, the necessary size will be calculated for you based off of
12
+ # the window type so it is not necessary to enter the size
13
+ def initialize(size: nil , norm_trans_freq: nil)
14
+ super(size: norm_trans_freq.nil? ? size : find_size(norm_trans_freq))
15
+ size = @size + 2
16
+ @equation = lambda { |n| 0.54 - 0.46 * Math.cos(2 * PI * (n + 1) / (size - 1)) }
17
+ calculate
18
+ @values = @values.take(@size)
19
+ end
20
+
21
+ # Given a freqency, return the required size of a HammingWindow
22
+ def find_size(freq)
23
+ size = 3.3 / freq
24
+ make_odd(size.ceil)
25
+ end
26
+
27
+ # Return the transition width (in rad/s) based off of the size
28
+ def transition_width
29
+ 3.3 / @size
30
+ end
31
+ end
@@ -0,0 +1,31 @@
1
+ ##
2
+ # Hanning Window
3
+ # Used to improve digital filters by using a non-retangular frequency domain window
4
+ class Digiproc::HanningWindow < Digiproc::WindowStrategy
5
+
6
+ ##
7
+ # == Input Args
8
+ # size (Optional):: Numeric (default: nil), how many datapoings the window should have
9
+ # norm_trans_freq:: Numeric (default: nil), the desired transition frequency
10
+ # If you know what size of the the window that you need, you can input size without norm_trans_freq
11
+ # If you kow the desired transition frequency, the necessary size will be calculated for you based off of
12
+ # the window type so it is not necessary to enter the size
13
+ def initialize(size: nil , norm_trans_freq: nil)
14
+ super(size: norm_trans_freq.nil? ? size : find_size(norm_trans_freq))
15
+ size = @size + 2
16
+ @equation = lambda { |n| 0.5 - 0.5 * Math.cos(2 * PI * (n + 1) / (size - 1)) }
17
+ calculate
18
+ @values = @values.take(@size)
19
+ end
20
+
21
+ # Given a freqency, return the required size of a HanningWindow
22
+ def find_size(freq)
23
+ size = 3.1 / freq
24
+ make_odd(size.ceil)
25
+ end
26
+
27
+ # Return the transition width (in rad/s) based off of the size
28
+ def transition_width
29
+ 3.1 / @size
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ ##
2
+ # kaiser Window
3
+ # Used to improve digital filters by using a non-retangular frequency domain window
4
+ # NOT COMPLETE
5
+ # TODO: Finish this Window
6
+ class Digiproc::KaiserWindow < Digiproc::WindowStrategy
7
+
8
+
9
+ def initialize(size: , stopband_db: nil, beta: nil)
10
+ raise ArgumentError.new("Must have a stopband or a beta") if stopband_db.nil? && beta.nil?
11
+ super(size: size)
12
+ size = @size
13
+ @equation = lambda { |n| }
14
+ calculate
15
+ @values = @values.take(@size)
16
+ end
17
+
18
+ def beta_for_db(db)
19
+ if db <= 21
20
+ return 0
21
+ elsif db < 50
22
+ return 0.5842*((db - 21) ** 0.4) + 0.07886 * (db - 21)
23
+ else
24
+ return 0.1102*(db - 8.7)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,22 @@
1
+ ##
2
+ # A basic ReactangularWindow used when making a digital filter
3
+ class Digiproc::RectangularWindow < Digiproc::WindowStrategy
4
+
5
+ # Initialize with a size: Integer
6
+ # Number of datapoints
7
+ def initialize(size: )
8
+ super(size: size)
9
+ calculate
10
+ end
11
+
12
+ # Find the size given an input frequency in rad/s
13
+ def find_size(freq)
14
+ size = 0.9 / freq
15
+ make_odd(size.ceil)
16
+ end
17
+
18
+ # Return the transition width based on the @size
19
+ def transition_width
20
+ 0.9 / @size
21
+ end
22
+ end
@@ -0,0 +1,42 @@
1
+ ##
2
+ # Parent class to all types of windows
3
+
4
+ class Digiproc::WindowStrategy
5
+
6
+ PI = Math::PI
7
+
8
+
9
+ attr_accessor :size
10
+ attr_reader :values, :equation, :data
11
+
12
+ # Initialize with size: Numeric (numnber of datapoints in window)
13
+ def initialize(size: )
14
+ @size = size
15
+ @equation = lambda { |n| 1 }
16
+ end
17
+
18
+ ##
19
+ # No input args
20
+ # calculate the window values
21
+ def calculate
22
+ values = []
23
+ for n in 0...size
24
+ values << @equation.call(n)
25
+ end
26
+ @values = values
27
+ @data = values
28
+ end
29
+
30
+ #
31
+ # Make the number of datapoints in the window odd so that
32
+ # it can be used for all types of filters
33
+ def make_odd(num)
34
+ num.odd? ? num : num + 1
35
+ end
36
+
37
+ # Return window values as a Digiproc::DigitalSignal
38
+ def to_signal
39
+ Digiproc::DigitalSignal.new(data: values)
40
+ end
41
+
42
+ end
@@ -0,0 +1,13 @@
1
+ class Dsp::Systems::CustomSystems < Dsp::Systems::System
2
+
3
+ def initialize(eqn: , size: , sample_rate: , data: )
4
+ raise ArgumentError.new("Must have signal_eqn, sample_rate, and size XOR data") if (signal_eqn.nil? or sample_rate.nil? or size.nil?) and (data.nil?)
5
+ if data.nil?
6
+ data = Dsp::AnalogSignal.new(eqn: signal_eqn, sample_rate: sample_rate, size: size).digitize.map(:&conjugate).reverse
7
+ super(data)
8
+ else
9
+ super(data)
10
+ end
11
+ end
12
+
13
+ end
@@ -0,0 +1,6 @@
1
+ class Dsp::Systems::HilbertTransform < Dsp::Systems::System
2
+
3
+ def initialize()
4
+
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ class Dsp::Systems::MatchedFilter < Dsp::Systems::System
2
+
3
+ def new_from_analog_signal(signal)
4
+ self.new(signal_data: signal.digitize)
5
+ end
6
+
7
+ def new_from_digital_signal(signal)
8
+ self.new(signal_data: signal.data)
9
+ end
10
+
11
+ def initialize(signal_eqn: nil, sample_rate: nil, size: nil, signal_data: nil)
12
+ raise ArgumentError.new("Must have signal_eqn, sample_rate, and size XOR signal_data") if (signal_eqn.nil? or sample_rate.nil? or size.nil?) and (signal_data.nil?)
13
+ if signal_data.nil?
14
+ data = Dsp::AnalogSignal.new(eqn: signal_eqn, sample_rate: sample_rate, size: size).digitize.map(:&conjugate).reverse
15
+ super(data)
16
+ else
17
+ data = signal_data.map(&:conjugate).reverse
18
+ super(data)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,11 @@
1
+ class Dsp::Systems::RaisedCosineFilter < Dsp::Systems::System
2
+
3
+
4
+ def initialize(tb: ,beta: ,sample_rate: ,size: )
5
+ @eqn = ->(t, tb, beta){ (tb / (2 * beta.to_f) or (-tb / (2 * beta.to_f))) ? Math::PI * Dsp::Functions.sinc(1.0 / (2 * beta)) / (4.0 * tb) : (1 / tb.to_f) * Dsp::Functions.sinc(t / tb.to_f) * Math.cos((Math::PI * beta * t) / tb.to_f) / (1 - ((2*beta*t) / tb.to_f) ** 2)}
6
+ # super(eqn: @eqn, sample_rate: sample_rate, size: size)
7
+ data = Dsp::AnalogSignal.new(eqn: @eqn, sample_rate: sample_rate, size: size).digitize
8
+ super(data)
9
+ end
10
+
11
+ end
@@ -0,0 +1,19 @@
1
+ class Dsp::Systems::System
2
+
3
+ attr_accessor :data
4
+ include Dsp::Convolvable::InstanceMethods, Dsp::Initializable, Dsp::FourierTransformable
5
+
6
+ def initialize(data)
7
+ @data = data
8
+ initialize_modules(Dsp::FourierTransformable => {time_data: data})
9
+ end
10
+
11
+ def to_ds
12
+ Dsp::DigitalSignal.new(data: self.data)
13
+ end
14
+
15
+ def to_a
16
+ self.data
17
+ end
18
+
19
+ end
@@ -0,0 +1,3 @@
1
+ module Dsp::Systems
2
+
3
+ end