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