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,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