digiproc 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +3 -0
- data/.travis.yml +7 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/Gemfile +8 -0
- data/Gemfile.lock +48 -0
- data/LICENSE.txt +21 -0
- data/README.md +78 -0
- data/Rakefile +37 -0
- data/TODO.md +50 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/config/environment.rb +118 -0
- data/console_tests.rb +44 -0
- data/digiproc.gemspec +49 -0
- data/examples/analog_signals/analog_to_digital.rb +31 -0
- data/examples/analog_signals/companded-signals.png +0 -0
- data/examples/analog_signals/companding.rb +68 -0
- data/examples/analog_signals/fft-plot.png +0 -0
- data/examples/analog_signals/plot_Digiproc::FFT.png +0 -0
- data/examples/analog_signals/plot_Dsp::FFT.png +0 -0
- data/examples/analog_signals/quantization-outputs.png +0 -0
- data/examples/analog_signals/quantize_compand.rb +69 -0
- data/examples/binomial_distribution/bit_error.rb +14 -0
- data/examples/binomial_distribution/dice.rb +35 -0
- data/examples/digital_signals/_coded_frequency_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/_coded_frequency_signal,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/coded_power_spectral_density,__ts_=_1_s.png +0 -0
- data/examples/digital_signals/coded_power_spectral_density,__ts_=_2_s.png +0 -0
- data/examples/digital_signals/coded_time_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/coded_time_signal,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/freq_sig_from_eqn,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/freq_sig_from_eqn,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/frequency_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/frequency_signal,_ts_=_2_s.png +0 -0
- data/examples/digital_signals/modulate_square_pulses.rb +9 -0
- data/examples/digital_signals/modulated_sq._pulses.png +0 -0
- data/examples/digital_signals/modulated_sq._pulses_alt.png +0 -0
- data/examples/digital_signals/power_spectral_density,__ts_=_1_s.png +0 -0
- data/examples/digital_signals/power_spectral_density,__ts_=_2_s.png +0 -0
- data/examples/digital_signals/square_signals.rb +90 -0
- data/examples/digital_signals/time_signal,_ts_=_1_s.png +0 -0
- data/examples/digital_signals/time_signal,_ts_=_2_s.png +0 -0
- data/examples/encoding/gray_code.rb +22 -0
- data/examples/encoding/psk.rb +91 -0
- data/examples/encoding/system_2_phase.png +0 -0
- data/examples/encoding/system_2_xmit_signal.png +0 -0
- data/examples/encoding/system_3_phase.png +0 -0
- data/examples/encoding/system_3_xmit_signal.png +0 -0
- data/examples/encoding/system_4_xmit_signal.png +0 -0
- data/examples/encoding/xor-dpsk-phase-signal-(sys1).png +0 -0
- data/examples/encoding/xor-dpsk-xmit-signal-(sys-1).png +0 -0
- data/examples/factories/Quickplot Graph.png +0 -0
- data/examples/factories/bandpass.rb +6 -0
- data/examples/fft/plot_Dsp::FFT.png +0 -0
- data/examples/fft/recieved_data_(time_domain).png +0 -0
- data/examples/fft/simple_fft_example.rb +47 -0
- data/examples/fft/unprocessed_fft.png +0 -0
- data/examples/filters/bandpass_filter.png +0 -0
- data/examples/filters/filter_a_signal.rb +38 -0
- data/examples/filters/white_noise_db_out_of_bp_filter.png +0 -0
- data/examples/filters/white_noise_mag_out_of_bp_filter.png +0 -0
- data/examples/filters/white_noise_spectra.png +0 -0
- data/examples/functions/compute_probability.rb +29 -0
- data/examples/functions/gram_schmidt.rb +10 -0
- data/examples/functions/minimize_energy.rb +29 -0
- data/examples/functions/orthoganalize.rb +18 -0
- data/examples/functions/simple_functions.rb +81 -0
- data/examples/linear_algebra/diverging_sys.rb +13 -0
- data/examples/linear_algebra/iterative_sys_of_eqns_methods.rb +27 -0
- data/examples/modulation_schemes/dpsk_2.png +0 -0
- data/examples/modulation_schemes/dpsk_256.png +0 -0
- data/examples/modulation_schemes/dpsk_freq_domain.rb +119 -0
- data/examples/modulation_schemes/psk.rb +36 -0
- data/examples/modulation_schemes/psk_2.png +0 -0
- data/examples/modulation_schemes/psk_256.png +0 -0
- data/examples/modulation_schemes/psksystem_1_xmit_signal.png +0 -0
- data/examples/modulation_schemes/psksystem_2_xmit_signal.png +0 -0
- data/examples/modulation_schemes/psksystem_3_xmit_signal.png +0 -0
- data/examples/modulation_schemes/system_1_xmit_signal.png +0 -0
- data/examples/modulation_schemes/system_2_xmit_signal.png +0 -0
- data/examples/modulation_schemes/system_3_xmit_signal.png +0 -0
- data/examples/quickplot/PlottableClass_plot.png +0 -0
- data/examples/quickplot/decorators.rb +13 -0
- data/examples/quickplot/direct_gruff.png +0 -0
- data/examples/quickplot/plot_PlottableClass.png +0 -0
- data/examples/quickplot/quickplot_vs_others.rb +85 -0
- data/examples/quickplot/random_data_quickplot,_dark.png +0 -0
- data/examples/quickplot/random_data_quickplot.png +0 -0
- data/examples/realized_gaussian/norm_dist_plot.png +0 -0
- data/examples/realized_gaussian/norm_dist_spectrum.png +0 -0
- data/examples/realized_gaussian/realized_gaussian_example.rb +23 -0
- data/lib/concerns/convolvable.rb +144 -0
- data/lib/concerns/data_properties.rb +223 -0
- data/lib/concerns/fourier_transformable.rb +178 -0
- data/lib/concerns/initializable.rb +43 -0
- data/lib/concerns/multipliable.rb +22 -0
- data/lib/concerns/os.rb +36 -0
- data/lib/concerns/plottable.rb +248 -0
- data/lib/concerns/requires_data.rb +8 -0
- data/lib/digiproc/version.rb +8 -0
- data/lib/digiproc.rb +2 -0
- data/lib/extensions/array_extension.rb +23 -0
- data/lib/extensions/core_extensions.rb +117 -0
- data/lib/factories/factories.rb +3 -0
- data/lib/factories/filter_factory.rb +83 -0
- data/lib/factories/window_factory.rb +22 -0
- data/lib/fft.rb +255 -0
- data/lib/filters/bandpass_filter.rb +43 -0
- data/lib/filters/bandstop_filter.rb +44 -0
- data/lib/filters/digital_filter.rb +59 -0
- data/lib/filters/highpass_filter.rb +27 -0
- data/lib/filters/lowpass_filter.rb +27 -0
- data/lib/functions.rb +221 -0
- data/lib/probability/binomial_distribution.rb +84 -0
- data/lib/probability/bit_generator.rb +94 -0
- data/lib/probability/gaussian_distribution.rb +29 -0
- data/lib/probability/probability.rb +234 -0
- data/lib/probability/theoretical_gaussian_distribution.rb +59 -0
- data/lib/quick_plot.rb +96 -0
- data/lib/rbplot.rb +219 -0
- data/lib/signals/analog_signal.rb +143 -0
- data/lib/signals/digital_signal.rb +181 -0
- data/lib/strategies/code/differential_encoding_strategy.rb +69 -0
- data/lib/strategies/code/gray_code.rb +75 -0
- data/lib/strategies/code/xor_differential_encoding_strategy.rb +100 -0
- data/lib/strategies/code/xor_differential_encoding_zero_angle_strategy.rb +103 -0
- data/lib/strategies/companding/custom_companding_strategy.rb +29 -0
- data/lib/strategies/convolution/bf_conv.rb +57 -0
- data/lib/strategies/fft/brute_force_dft_strategy.rb +31 -0
- data/lib/strategies/fft/inverse_fft_conjugate_strategy.rb +44 -0
- data/lib/strategies/fft/radix2_strategy.rb +84 -0
- data/lib/strategies/gaussian/gaussian_generator.rb +49 -0
- data/lib/strategies/linear_algebra/gauss_seidel_strategy.rb +90 -0
- data/lib/strategies/linear_algebra/jacobi_strategy.rb +81 -0
- data/lib/strategies/linear_algebra/sor2_strategy.rb +98 -0
- data/lib/strategies/linear_algebra/sor_strategy.rb +108 -0
- data/lib/strategies/modulation/phase_shift_keying_strategy.rb +96 -0
- data/lib/strategies/orthogonalize/gram_schmidt.rb +50 -0
- data/lib/strategies/strategies.rb +3 -0
- data/lib/strategies/window/blackman_window.rb +32 -0
- data/lib/strategies/window/hamming_window.rb +31 -0
- data/lib/strategies/window/hanning_window.rb +31 -0
- data/lib/strategies/window/kaiser_window.rb +27 -0
- data/lib/strategies/window/rectangular_window.rb +22 -0
- data/lib/strategies/window/window.rb +42 -0
- data/lib/systems/custom_system.rb +13 -0
- data/lib/systems/hilbert_transform.rb +6 -0
- data/lib/systems/matched_filter.rb +21 -0
- data/lib/systems/raised_cosine_filter.rb +11 -0
- data/lib/systems/system.rb +19 -0
- data/lib/systems/systems.rb +3 -0
- data/playground.rb +323 -0
- data/plots/_coded_frequency_signal,_ts_=_1_s.png +0 -0
- data/plots/_coded_frequency_signal,_ts_=_2_s.png +0 -0
- data/plots/coded_freq_sig_from_eqn,_ts_=_1_s.png +0 -0
- data/plots/coded_freq_sig_from_eqn,_ts_=_2_s.png +0 -0
- data/plots/coded_power_spectral_density,__ts_=_1_s.png +0 -0
- data/plots/coded_power_spectral_density,__ts_=_2_s.png +0 -0
- data/plots/coded_time_signal,_ts_=_1_s.png +0 -0
- data/plots/coded_time_signal,_ts_=_2_s.png +0 -0
- data/plots/dpsk_2.png +0 -0
- data/plots/freq_sig_from_eqn,_ts_=_1_s.png +0 -0
- data/plots/freq_sig_from_eqn,_ts_=_2_s.png +0 -0
- data/plots/frequency_signal,_ts_=_1_s.png +0 -0
- data/plots/frequency_signal,_ts_=_2_s.png +0 -0
- data/plots/power_spectral_density,__ts_=_1_s.png +0 -0
- data/plots/power_spectral_density,__ts_=_2_s.png +0 -0
- data/plots/psk_2.png +0 -0
- data/plots/time_signal,_ts_=_1_s.png +0 -0
- data/plots/time_signal,_ts_=_2_s.png +0 -0
- data/test-title-dark.png +0 -0
- data/test-title.png +0 -0
- 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,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,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
|