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