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,100 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Class used as a strategy for differentially encoding a bitstream
|
|
3
|
+
# by XORing it with a time-delayed version of itself:
|
|
4
|
+
# Differentially encode the binary stream using the XOR operator,
|
|
5
|
+
# and then map the binary stream using the equation
|
|
6
|
+
# βn = (2l−1)π %2π, l⩽M, which gives you a mapping of 0=> −π ,1=> π
|
|
7
|
+
# The Differentially Encoded Signal => Cn = Dn ⨁ Cn−1
|
|
8
|
+
# is the signal mapped to phase as described above.
|
|
9
|
+
# The XOR operation in seeded with a 1
|
|
10
|
+
class Digiproc::Strategies::XORDifferentialEncodingStrategy
|
|
11
|
+
|
|
12
|
+
##
|
|
13
|
+
# Accept an int reprisenting a bit stream. Encode the
|
|
14
|
+
# bits by XORing it with a time delay of itself via
|
|
15
|
+
# the `self.encode_str` method below. The first bit is seeded
|
|
16
|
+
# with a "1"
|
|
17
|
+
def self.encode_bits(bits)
|
|
18
|
+
encode_str(bits.to_s(2))
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Input a string of bits
|
|
23
|
+
# Outupt the XOR'd version of the bits, seeded with a beginning "1"
|
|
24
|
+
def self.encode_str(bits)
|
|
25
|
+
bits_arr = bits.split("")
|
|
26
|
+
encoded = ["1"]
|
|
27
|
+
for i in 0...bits_arr.length do
|
|
28
|
+
encoded << (encoded.last.to_i(2) ^ bits_arr[i].to_i(2)).to_s
|
|
29
|
+
end
|
|
30
|
+
encoded.join
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# Encoding an incoming array of bits (as strings) into an array of XOR'd bits
|
|
35
|
+
# Requires an input of an array, and has optional arguments of m (number of bits per symbol)
|
|
36
|
+
# And a beginning value (a starting reference phase angle). Outputs an array of
|
|
37
|
+
# XOR'd bits
|
|
38
|
+
def self.encode(arr, m = 2, beginning_val = "1")
|
|
39
|
+
beginning_val = beginning_val.to_s(2) unless beginning_val.is_a? String
|
|
40
|
+
encoded = [beginning_val]
|
|
41
|
+
for i in 0...arr.length do
|
|
42
|
+
encoded << (encoded.last.to_i(2) ^ arr[i].to_i(2)).to_s
|
|
43
|
+
end
|
|
44
|
+
encoded
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Input is an integer
|
|
49
|
+
# The method calls the `decode_str` method below and inputs
|
|
50
|
+
# a string of the binary of the input integer.
|
|
51
|
+
# The output will be the original bitstream encoded by this encoding strategy
|
|
52
|
+
# in string form
|
|
53
|
+
def self.decode_bits(bits)
|
|
54
|
+
dencode_str(bits.to_s(2))
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
##
|
|
60
|
+
# Input an encoded binary bit stream in string form
|
|
61
|
+
# Output a string of the original bitstream
|
|
62
|
+
def self.decode_str(bits)
|
|
63
|
+
bits_arr = bits.split("")
|
|
64
|
+
encoded = []
|
|
65
|
+
for i in 1...bits_arr.length do
|
|
66
|
+
encoded << (bits_arr[i - 1].to_i(2) ^ bits_arr[i].to_i(2)).to_s
|
|
67
|
+
end
|
|
68
|
+
encoded.join
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
##
|
|
72
|
+
# Input an array of encoded bits (as strings)
|
|
73
|
+
# Output an array of decoded bits
|
|
74
|
+
def self.decode(bits)
|
|
75
|
+
encoded = []
|
|
76
|
+
for i in 1...bits.length do
|
|
77
|
+
encoded << (bits[i - 1].to_i.to_s.to_i(2) ^ bits[i].to_i.to_s.to_i(2)).to_s
|
|
78
|
+
end
|
|
79
|
+
encoded
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
##
|
|
83
|
+
# Return a lambda which transforms a bit into a phase
|
|
84
|
+
# Input an integer specifying the number of bits in a symbol
|
|
85
|
+
# Input to the returned lambda should be the symbol value in decimal form
|
|
86
|
+
# Ouptut of the lambda is the encoded phase angle
|
|
87
|
+
def self.phase_shift_eqn(m)
|
|
88
|
+
->(l){ (((2.0 * (l+1) - 1.0) / m) % 2) * Math::PI }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
##
|
|
92
|
+
# Return a lambda which transforms a pahse into a symbol
|
|
93
|
+
# Input an integer specifying the number of bits in a symbol
|
|
94
|
+
# Input to the returned lambda is the encoded phase angle
|
|
95
|
+
# Output of the lambda is the binary symbol
|
|
96
|
+
def self.phase_to_sym(m)
|
|
97
|
+
->(code){ (((code / (Math::PI)) * m) + 1.0) / 2.0 - 1.0 }
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Class used as a strategy for differentially encoding a bitstream
|
|
3
|
+
# by XORing it with a time-delayed version of itself:
|
|
4
|
+
# Differentially encode the binary stream using the XOR operator,
|
|
5
|
+
# and then map the binary stream using the equation
|
|
6
|
+
# βn = 2π(i−1) , i = 1..M which gives you a mapping of 0 => 0, 1 => π
|
|
7
|
+
# The Differentially Encoded Signal => Cn = Dn ⨁ Cn−1
|
|
8
|
+
# is the signal mapped to phase as described above.
|
|
9
|
+
# The XOR operation in seeded with a 1
|
|
10
|
+
|
|
11
|
+
class Digiproc::Strategies::XORDifferentialEncodingZeroAngleStrategy
|
|
12
|
+
|
|
13
|
+
##
|
|
14
|
+
# Accept an int reprisenting a bit stream. Encode the
|
|
15
|
+
# bits by XORing it with a time delay of itself via
|
|
16
|
+
# the `self.encode_str` method below. The first bit is seeded
|
|
17
|
+
# with a "1"
|
|
18
|
+
def self.encode_bits(bits)
|
|
19
|
+
encode_str(bits.to_s(2))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
##
|
|
24
|
+
# Input a string of bits
|
|
25
|
+
# Outupt the XOR'd version of the bits, seeded with a beginning "1"
|
|
26
|
+
def self.encode_str(bits)
|
|
27
|
+
bits_arr = bits.split("")
|
|
28
|
+
encoded = ["1"]
|
|
29
|
+
for i in 0...bits_arr.length do
|
|
30
|
+
encoded << (encoded.last.to_i(2) ^ bits_arr[i].to_i(2)).to_s
|
|
31
|
+
end
|
|
32
|
+
encoded.join
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
##
|
|
36
|
+
# Encoding an incoming array of bits (as strings) into an array of XOR'd bits
|
|
37
|
+
# Requires an input of an array, and has optional arguments of m (number of bits per symbol)
|
|
38
|
+
# And a beginning value (a starting reference phase angle). Outputs an array of
|
|
39
|
+
# XOR'd bits
|
|
40
|
+
def self.encode(arr, m = 2, beginning_val = "1")
|
|
41
|
+
beginning_val = beginning_val.to_s(2) unless beginning_val.is_a? String
|
|
42
|
+
encoded = [beginning_val]
|
|
43
|
+
for i in 0...arr.length do
|
|
44
|
+
encoded << (encoded.last.to_i(2) ^ arr[i].to_i(2)).to_s(2)
|
|
45
|
+
end
|
|
46
|
+
encoded
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
##
|
|
51
|
+
# Input is an integer
|
|
52
|
+
# The method calls the `decode_str` method below and inputs
|
|
53
|
+
# a string of the binary of the input integer.
|
|
54
|
+
# The output will be the original bitstream encoded by this encoding strategy
|
|
55
|
+
# in string form
|
|
56
|
+
def self.decode_bits(bits)
|
|
57
|
+
dencode_str(bits.to_s(2))
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# Input an encoded binary bit stream in string form
|
|
63
|
+
# Output a string of the original bitstream
|
|
64
|
+
def self.decode_str(bits)
|
|
65
|
+
bits_arr = bits.split("")
|
|
66
|
+
encoded = []
|
|
67
|
+
for i in 1...bits_arr.length do
|
|
68
|
+
encoded << (bits_arr[i - 1].to_i(2) ^ bits_arr[i].to_i(2)).to_s(2)
|
|
69
|
+
end
|
|
70
|
+
encoded.join
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
##
|
|
75
|
+
# Input an array of encoded bits (as strings)
|
|
76
|
+
# Output an array of decoded bits
|
|
77
|
+
def self.decode(bits)
|
|
78
|
+
encoded = []
|
|
79
|
+
for i in 1...bits.length do
|
|
80
|
+
encoded << (bits[i - 1].to_i.to_s.to_i(2) ^ bits[i].to_i.to_s.to_i(2)).to_s(2)
|
|
81
|
+
end
|
|
82
|
+
encoded
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
##
|
|
86
|
+
# Return a lambda which transforms a bit into a phase
|
|
87
|
+
# Input an integer specifying the number of bits in a symbol
|
|
88
|
+
# Input to the returned lambda should be the symbol value in decimal form
|
|
89
|
+
# Ouptut of the lambda is the encoded phase angle
|
|
90
|
+
def self.phase_shift_eqn(m)
|
|
91
|
+
->(i){ (2 * Math::PI * (i)) / m }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
##
|
|
95
|
+
# Return a lambda which transforms a pahse into a symbol
|
|
96
|
+
# Input an integer specifying the number of bits in a symbol
|
|
97
|
+
# Input to the returned lambda is the encoded phase angle
|
|
98
|
+
# Output of the lambda is the binary symbol
|
|
99
|
+
def self.phase_to_sym(m)
|
|
100
|
+
->(phase){ (phase * m / (2.0 * Math::PI))}
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
##
|
|
2
|
+
# A class which allows a custom companding strategy to be used via a lambda function
|
|
3
|
+
# inputted into the initilizer.
|
|
4
|
+
|
|
5
|
+
class Digiproc::Strategies::CustomCompandingStrategy
|
|
6
|
+
|
|
7
|
+
attr_accessor :eqn, :inverse
|
|
8
|
+
|
|
9
|
+
# initialize wiht a companding equation (via a proc or lambda) as well as the inverse equation
|
|
10
|
+
def initialize(eqn, inverse)
|
|
11
|
+
@eqn, @inverse = eqn, inverse
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# maps an array (first argument) with a lambda (second argument)
|
|
15
|
+
def process(data, fn)
|
|
16
|
+
data.map{ |n| fn.call(n) }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# use the compression companding lambda (or proc) to compress an array of numerics
|
|
20
|
+
def compress(data)
|
|
21
|
+
self.process(data, eqn)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Use the inverse labda (or proc) to decompress an array of numerics
|
|
25
|
+
def expand(data)
|
|
26
|
+
self.process(data, inverse)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Strategy for convolving two arrays of numbers
|
|
3
|
+
# This is an O(n^2) operation, it is more time efficient
|
|
4
|
+
# to use FFT to perform this calculation
|
|
5
|
+
class Digiproc::Strategies::BFConvolutionStrategy
|
|
6
|
+
|
|
7
|
+
##
|
|
8
|
+
# == Input Args
|
|
9
|
+
# data1:: Array[Numeric]
|
|
10
|
+
# data2:: Array[Numeric]
|
|
11
|
+
# == Output
|
|
12
|
+
# convolution:: Numeric
|
|
13
|
+
def self.conv(data1, data2)
|
|
14
|
+
dynamic_data = data1.dup
|
|
15
|
+
static_data = data2.dup
|
|
16
|
+
conv_sum = []
|
|
17
|
+
n = 0
|
|
18
|
+
start, stop = conv_overlap_area(static_data.length, dynamic_data.length, n)
|
|
19
|
+
while start <= stop
|
|
20
|
+
sum = 0
|
|
21
|
+
for val in start..stop
|
|
22
|
+
sum += static_data[val] * dynamic_data[transform_to_local_index(val, n)]
|
|
23
|
+
end
|
|
24
|
+
conv_sum << sum
|
|
25
|
+
n = conv_sum.length
|
|
26
|
+
start, stop = conv_overlap_area(static_data.length, dynamic_data.length, n)
|
|
27
|
+
end
|
|
28
|
+
conv_sum
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# Gives start and stop values of overlap inclusive
|
|
35
|
+
def self.conv_overlap_area(static_len, dynamic_len, n)
|
|
36
|
+
dynamic_lo = transform_to_global_index(dynamic_len - 1, n)
|
|
37
|
+
dynamic_hi = transform_to_global_index(0, n)
|
|
38
|
+
static_lo = 0
|
|
39
|
+
static_hi = static_len - 1
|
|
40
|
+
start = dynamic_lo > static_lo ? dynamic_lo : static_lo
|
|
41
|
+
stop = dynamic_hi < static_hi ? dynamic_hi : static_hi
|
|
42
|
+
return [start, stop]
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def self.transform_index(index, xform)
|
|
46
|
+
xform.call(index)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.transform_to_global_index(index, n)
|
|
50
|
+
transform_index(index, ->(i){ n - i })
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def self.transform_to_local_index(index, n)
|
|
54
|
+
transform_index(index, ->(i){ n - i})
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
##
|
|
2
|
+
# A brute force Discrete Fourier Transform strategy
|
|
3
|
+
# O(n^2) algorith, no reason to sue it over FFT unless you
|
|
4
|
+
# don't want to work within the parameters of a Radix2 strategy
|
|
5
|
+
# (ie data points of DFT size will be a power of 2). This strategy does
|
|
6
|
+
# not have those parameters
|
|
7
|
+
class Digiproc::BFDFTStrategy
|
|
8
|
+
|
|
9
|
+
attr_accessor :data
|
|
10
|
+
|
|
11
|
+
# initialize with an array of numerics
|
|
12
|
+
def initialize(data)
|
|
13
|
+
@data = data.dup
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Calculate the DFT with an O(n^2) algorithm
|
|
17
|
+
# Can accept an array of numerics, with a default value of
|
|
18
|
+
# @data
|
|
19
|
+
def calculate(data = @data)
|
|
20
|
+
ft = []
|
|
21
|
+
for k in 0...data.length do
|
|
22
|
+
tot = 0
|
|
23
|
+
data.each_with_index do |x_n, n|
|
|
24
|
+
tot += x_n * Math::E ** (Complex(0,-1) * 2.0 * Math::PI * k * n / data.length.to_f)
|
|
25
|
+
end
|
|
26
|
+
ft << tot
|
|
27
|
+
end
|
|
28
|
+
ft
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Strategy for calculating the Inverse Fast Fourier Transform (IFFT)
|
|
3
|
+
# O(nlgn) runtime, depends on the FFT strategy to calculate thie IFFT
|
|
4
|
+
# Uses "conjugate strategy"
|
|
5
|
+
class Digiproc::Strategies::IFFTConjugateStrategy
|
|
6
|
+
|
|
7
|
+
attr_accessor :data, :strategy
|
|
8
|
+
|
|
9
|
+
##
|
|
10
|
+
# == Input args:
|
|
11
|
+
# data:: Array[Numeric] (DFT values)
|
|
12
|
+
# fft_strategy(Optional):: See Digiproc::Strategies::Radix2Strategy for required protocol (This is the default value)
|
|
13
|
+
def initialize(data, fft_strategy = Digiproc::Strategies::Radix2Strategy)
|
|
14
|
+
@data = data
|
|
15
|
+
@strategy = fft_strategy.new
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
##
|
|
19
|
+
# No input args
|
|
20
|
+
# Calculate the IFFT given Discrete Fourier Transform (DFT) values
|
|
21
|
+
def calculate
|
|
22
|
+
strategy.data = conjugate(data)
|
|
23
|
+
fft_out = strategy.calculate
|
|
24
|
+
n = fft_out.length.to_f
|
|
25
|
+
conjugate(fft_out){ |real, imag| OpenStruct.new(real: (real / n), imaginary: (imag / n) ) }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def conjugate(data)
|
|
32
|
+
data.map do |value|
|
|
33
|
+
if value.is_a? Complex
|
|
34
|
+
complex_num = block_given? ? yield(value.real, value.imaginary) : OpenStruct.new(real: value.real, imaginary: value.imaginary)
|
|
35
|
+
Complex(complex_num.real, -1 * complex_num.imaginary)
|
|
36
|
+
else
|
|
37
|
+
real_num = block_given? ? yield(value, 0) : OpenStruct.new(real: value, imaginary: 0)
|
|
38
|
+
real_num.real
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
##
|
|
2
|
+
# O(n*lgn) FFT algorithm
|
|
3
|
+
# Requires that the number of datapoints be a power of 2
|
|
4
|
+
# if it isn't the calculation will zero pad to the closest
|
|
5
|
+
# power of 2 above the current data size
|
|
6
|
+
# The size of the outputted DFT will be the size of the zero-padded
|
|
7
|
+
# dataset. Zero-padding causes the "sample locations" of the DFT to
|
|
8
|
+
# be at different locations than if it was not zero padded, but
|
|
9
|
+
# The resolution of the DFT will INCREASE due to zero padding
|
|
10
|
+
class Digiproc::Strategies::Radix2Strategy
|
|
11
|
+
|
|
12
|
+
E = Math::E
|
|
13
|
+
PI = Math::PI
|
|
14
|
+
|
|
15
|
+
attr_reader :data
|
|
16
|
+
|
|
17
|
+
##
|
|
18
|
+
# == Initialize Arg
|
|
19
|
+
# data (Optional):: Array[Numeric] (defaults to nil)
|
|
20
|
+
def initialize(data = nil)
|
|
21
|
+
@data = data
|
|
22
|
+
zero_fill if not data.nil?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
##
|
|
26
|
+
# == Input arg
|
|
27
|
+
# data (Optional):: Array[Numiric] (defaults to @data)
|
|
28
|
+
def calculate(data = @data)
|
|
29
|
+
recursive_fft(data)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @data writer, and zero fills array to
|
|
33
|
+
# obtain a size of the closest power of 2 above current size
|
|
34
|
+
def data=(data)
|
|
35
|
+
@data = data
|
|
36
|
+
zero_fill
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def recursive_fft(arr)
|
|
42
|
+
n = arr.length
|
|
43
|
+
return arr if n == 1
|
|
44
|
+
w_n = E ** ((-2 * PI * Complex(0,1)) / n)
|
|
45
|
+
w = 1
|
|
46
|
+
a0 = get_even(arr)
|
|
47
|
+
a1 = get_odd(arr)
|
|
48
|
+
y0 = recursive_fft(a0)
|
|
49
|
+
y1 = recursive_fft(a1)
|
|
50
|
+
y = Array.new(n, 0)
|
|
51
|
+
for k in 0...(n/2) do
|
|
52
|
+
y[k] = (y0[k] + w * y1[k])
|
|
53
|
+
y[k + n/2] = (y0[k] - w * y1[k])
|
|
54
|
+
w = w * w_n
|
|
55
|
+
end
|
|
56
|
+
y
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def get_even(arr)
|
|
60
|
+
even = []
|
|
61
|
+
arr.each_with_index { |a, i| even << a.dup if i % 2 == 0 }
|
|
62
|
+
even
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def get_odd(arr)
|
|
66
|
+
odd = []
|
|
67
|
+
arr.each_with_index { |a, i| odd << a.dup if i % 2 == 1 }
|
|
68
|
+
odd
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def zero_fill
|
|
72
|
+
len = self.data.length
|
|
73
|
+
@data = @data.concat Array.new(closest_pow_of_2(len) - len, 0)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def power_of_two?(num)
|
|
77
|
+
lg = Math.log(num,2)
|
|
78
|
+
lg.round.to_f == lg
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def closest_pow_of_2(num)
|
|
82
|
+
2 ** Math.log(num,2).ceil
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
##
|
|
2
|
+
# Class for generating random numbers from a Gaussin Population
|
|
3
|
+
# of a given mean or standard deviation
|
|
4
|
+
class Digiproc::Strategies::GaussianGeneratorBoxMullerStrategy
|
|
5
|
+
|
|
6
|
+
##
|
|
7
|
+
# No input args
|
|
8
|
+
# Returns 2 random numbers from a gaussain population with
|
|
9
|
+
# stddev of 1 and mean of 0
|
|
10
|
+
def self.rand2
|
|
11
|
+
uniform_random = Random.new
|
|
12
|
+
r = (-2 * Math.log(1 - uniform_random.rand)) ** 0.5
|
|
13
|
+
theta = 2 * Math::PI * (1 - uniform_random.rand)
|
|
14
|
+
return r * Math.cos(theta), r * Math.sin(theta)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
attr_accessor :mean, :stddev
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# == Input Args:
|
|
21
|
+
# mean (Optional):: Numeric (default 0)
|
|
22
|
+
# stddev (Optional):: Numeric (default 1)
|
|
23
|
+
def initialize(mean = 0, stddev = 1)
|
|
24
|
+
@mean, @stddev = mean, stddev
|
|
25
|
+
@needs_gen = true
|
|
26
|
+
@next = nil
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Get a single random number from a Gaussian distribution with a
|
|
30
|
+
# mean and stddev as defined by @mean and @stddev
|
|
31
|
+
# Use the .rand2 method to get 2 random numbers. Save one, return the other
|
|
32
|
+
def rand
|
|
33
|
+
if @needs_gen
|
|
34
|
+
x,y = self.class.rand2
|
|
35
|
+
@next = y
|
|
36
|
+
@needs_gen = false
|
|
37
|
+
return self.mean + self.stddev * x
|
|
38
|
+
else
|
|
39
|
+
@needs_gen = true
|
|
40
|
+
return self.mean + self.stddev * @next
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Calls the .rand2 method
|
|
45
|
+
def rand2
|
|
46
|
+
self.class.rand2
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
##
|
|
2
|
+
# This strategy will solve a system of linear equations using the Gauss Seidel iterative strategy
|
|
3
|
+
# Constructor Inputs: (a_arr, b_arr) correspond to A and B in the equation Ax = B (a should be an nxn 2D array, and b should be a 1D array (even though in the equation it is a column vector))
|
|
4
|
+
#
|
|
5
|
+
## a = [[4,1,-1],[2,7,1],[1,-3,12]]
|
|
6
|
+
## b = [3,19,31]
|
|
7
|
+
## gs = Digiproc::Strategies::GaussSeidelStrategy.new(a,b)
|
|
8
|
+
## x = gs.calculate # => Matrix[[0.9998668946614292], [2.000021547671973], [3.000054218557957]]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Digiproc::Strategies::GaussSeidelStrategy
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
##
|
|
15
|
+
# ==Initialize args
|
|
16
|
+
# a_arr:: 2D array representing your A matrix
|
|
17
|
+
# b_arr:: 1D array representing your B matrix
|
|
18
|
+
# Where B = Ax defines your series of linear equations
|
|
19
|
+
def initialize(a_arr,b_arr)
|
|
20
|
+
# TODO: Raise exception if a_arr is not square and b_arr row_count != a_arr row count
|
|
21
|
+
@b = Matrix.column_vector(b_arr)
|
|
22
|
+
@a = Matrix.rows(a_arr, true)
|
|
23
|
+
d_arr, l_arr, u_arr = [],[],[]
|
|
24
|
+
num_cols = @a.column_count
|
|
25
|
+
@a.row_count.times do
|
|
26
|
+
d_arr << Array.new(num_cols, 0)
|
|
27
|
+
l_arr << Array.new(num_cols, 0)
|
|
28
|
+
u_arr << Array.new(num_cols, 0)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
@a.each_with_index do |el, row, col|
|
|
32
|
+
if row > col
|
|
33
|
+
l_arr[row][col] = el
|
|
34
|
+
elsif row < col
|
|
35
|
+
u_arr[row][col] = el
|
|
36
|
+
else
|
|
37
|
+
d_arr[row][col] = el
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
@d = Matrix.rows(d_arr)
|
|
41
|
+
@l = Matrix.rows(l_arr)
|
|
42
|
+
@u = Matrix.rows(u_arr)
|
|
43
|
+
#TODO: Ensure no zeros on diagonal
|
|
44
|
+
@dinv = @d.map{ |el| el == 0 ? 0 : 1.0 / el }
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
##
|
|
48
|
+
# Iteratively solves the linear system of equations using the Gauss Seidel method
|
|
49
|
+
# accepts an optional parameter which is the threshold value x(n+1) - x(n) should achieve before returning.
|
|
50
|
+
# Must be used with a key-value pair ie
|
|
51
|
+
## gs.calculate(threshold: 0.001)
|
|
52
|
+
# default threshold = 0.001
|
|
53
|
+
# Returns a column vector Matrix => access via matrix_return[row,col]
|
|
54
|
+
# If run with the option safety_net: true and the equation diverges, performs A_inverse * B to solve
|
|
55
|
+
# ie
|
|
56
|
+
## s.calculate(safety_net: true)
|
|
57
|
+
#
|
|
58
|
+
def calculate(threshold: 0.001, safety_net: false)
|
|
59
|
+
dinv, b, l, u = @dinv, @b, @l, @u
|
|
60
|
+
c = dinv * b
|
|
61
|
+
t = -1 * dinv * (l + u)
|
|
62
|
+
x_n = Matrix.column_vector(Array.new(@a.column_count, 0))
|
|
63
|
+
counter = 0
|
|
64
|
+
|
|
65
|
+
#TODO: Investigate speed difference of using
|
|
66
|
+
# x_new = c + t*x_old , where:
|
|
67
|
+
# c = (l + d).inv * b
|
|
68
|
+
# t = -1 * (l + d).inv * u
|
|
69
|
+
loop do
|
|
70
|
+
x_n_plus_1 = x_n.dup
|
|
71
|
+
for i in 0...x_n.row_count do
|
|
72
|
+
x_n_i = c.row(i).to_matrix + t.row(i).to_matrix.transpose * x_n_plus_1
|
|
73
|
+
# puts "#{c.row(i).to_matrix} + #{t.row(i).to_matrix.transpose} * #{x_n_plus_1} = #{x_n_i}"
|
|
74
|
+
x_n_plus_1[i,0] = x_n_i[0,0]
|
|
75
|
+
end
|
|
76
|
+
x_difference = (x_n_plus_1 - x_n).map{ |el| el.abs }
|
|
77
|
+
should_break = !x_difference.find{ |el| el > threshold }
|
|
78
|
+
x_n = x_n_plus_1
|
|
79
|
+
break if should_break
|
|
80
|
+
counter += 1
|
|
81
|
+
if counter > 1000000 and safety_net
|
|
82
|
+
return (@a.inv * b).map{ |el| el.to_f}
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
return (safety_net and x_n.find{ |el| el.to_f.nan? }) ? (@a.inv * b).map{ |el| el.to_f} : x_n
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
|
|
2
|
+
##
|
|
3
|
+
# This strategy will solve a system of linear equations using the Jacobi iterative strategy
|
|
4
|
+
# Constructor Inputs: (a_arr, b_arr) correspond to A and B in the equation Ax = B (a should be an nxn 2D array, and b should be a 1D array (even though in the equation it is a column vector))
|
|
5
|
+
#
|
|
6
|
+
## a = [[4,1,-1],[2,7,1],[1,-3,12]]
|
|
7
|
+
## b = [3,19,31]
|
|
8
|
+
## gs = Digiproc::Strategies::JacobiStrategy.new(a,b)
|
|
9
|
+
## x = gs.calculate # => Matrix[[0.9998668946614292], [2.000021547671973], [3.000054218557957]]
|
|
10
|
+
|
|
11
|
+
class Digiproc::Strategies::JacobiStrategy
|
|
12
|
+
|
|
13
|
+
attr_reader :a, :b, :d, :u, :l, :dinv
|
|
14
|
+
|
|
15
|
+
##
|
|
16
|
+
# ==Initialize args
|
|
17
|
+
# a_arr:: 2D array representing your A matrix
|
|
18
|
+
# b_arr:: 1D array representing your B matrix
|
|
19
|
+
# Where B = Ax defines your series of linear equations
|
|
20
|
+
def initialize(a_arr,b_arr)
|
|
21
|
+
# TODO: Raise exception if a_arr is not square and b_arr row_count != a_arr row count
|
|
22
|
+
@b = Matrix.column_vector(b_arr)
|
|
23
|
+
@a = Matrix.rows(a_arr, true)
|
|
24
|
+
d_arr, l_arr, u_arr = [],[],[]
|
|
25
|
+
num_cols = @a.column_count
|
|
26
|
+
@a.row_count.times do
|
|
27
|
+
d_arr << Array.new(num_cols, 0)
|
|
28
|
+
l_arr << Array.new(num_cols, 0)
|
|
29
|
+
u_arr << Array.new(num_cols, 0)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
@a.each_with_index do |el, row, col|
|
|
33
|
+
if row > col
|
|
34
|
+
l_arr[row][col] = el
|
|
35
|
+
elsif row < col
|
|
36
|
+
u_arr[row][col] = el
|
|
37
|
+
else
|
|
38
|
+
d_arr[row][col] = el
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
@d = Matrix.rows(d_arr)
|
|
42
|
+
@l = Matrix.rows(l_arr)
|
|
43
|
+
@u = Matrix.rows(u_arr)
|
|
44
|
+
#TODO: Ensure no zeros on diagonal
|
|
45
|
+
@dinv = @d.map{ |el| el == 0 ? 0 : 1.0 / el }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
##
|
|
49
|
+
# Iteratively solves the linear system of equations using the jacobi method
|
|
50
|
+
# accepts an optional parameter which is the threshold value x(n+1) - x(n) should achieve before returning.
|
|
51
|
+
# Must be used with a key-value pair ie
|
|
52
|
+
## gs.calculate(threshold: 0.001)
|
|
53
|
+
# default threshold = 0.001
|
|
54
|
+
# Returns a column vector Matrix => access via matrix_return[row,col]
|
|
55
|
+
# If run with the option safety_net: true and the equation diverges, performs A_inverse * B to solve
|
|
56
|
+
# ie
|
|
57
|
+
## gs.calculate(safety_net: true)
|
|
58
|
+
#
|
|
59
|
+
def calculate(threshold: 0.001, safety_net: false)
|
|
60
|
+
dinv, b, l, u = @dinv, @b, @l, @u
|
|
61
|
+
c = dinv * b
|
|
62
|
+
t = -1 * dinv * (l + u)
|
|
63
|
+
x_n = Matrix.column_vector(Array.new(@a.column_count, 0))
|
|
64
|
+
counter = 0
|
|
65
|
+
loop do
|
|
66
|
+
x_n_plus_1 = c + t * x_n
|
|
67
|
+
x_difference = (x_n_plus_1 - x_n).map{ |el| el.abs }
|
|
68
|
+
# puts x_n_plus_1
|
|
69
|
+
should_break = !x_difference.find{ |el| el > threshold }
|
|
70
|
+
x_n = x_n_plus_1
|
|
71
|
+
break if should_break
|
|
72
|
+
counter += 1
|
|
73
|
+
if counter > 10000000 and safety_net
|
|
74
|
+
return (@a.inv * b).map{ |el| el.to_f}
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
return (safety_net and x_n.find{ |el| el.to_f.nan? }) ? (@a.inv * b).map{ |el| el.to_f} : x_n
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
end
|