diff --git a/.rspec b/.rspec index 83e16f8..4e1e0d2 100644 --- a/.rspec +++ b/.rspec @@ -1,2 +1 @@ --color ---require spec_helper diff --git a/lib/multilateration.rb b/lib/multilateration.rb index e7650a9..8fb569f 100644 --- a/lib/multilateration.rb +++ b/lib/multilateration.rb @@ -1,8 +1,11 @@ +require "rantly" + require "matrix" require "multilateration/version" +require "multilateration/signal_event_data_generator" +require "multilateration/signal_event" require "multilateration/solver" -require "multilateration/time_of_arrival_strategies/source_vector_distance" module Multilateration # Your code goes here... diff --git a/lib/multilateration/signal_event.rb b/lib/multilateration/signal_event.rb new file mode 100644 index 0000000..7fa2c4a --- /dev/null +++ b/lib/multilateration/signal_event.rb @@ -0,0 +1,46 @@ +module Multilateration + class SignalEvent + include Comparable + + attr_reader :coordinate, :time + private :coordinate, :time + + def initialize(coordinate:, time:) + @coordinate = coordinate + @time = time + end + + def to_h + {coordinate: coordinate, time: time} + end + + def <=>(other) + time_of_arrival <=> other.time_of_arrival + end + + def -(other) + vector - other.vector + end + + def inner_product_sq + vector.inner_product(vector) + end + + def time_difference_of_arrival(other) + time_of_arrival - other.time_of_arrival + end + + def time_of_arrival + time + end + + def distance_between(coordinate) + Math.sqrt((vector - Vector.elements(coordinate)).map { |component| component.abs**2 }.reduce(&:+)) + end + + protected def vector + @vector ||= Vector.elements(coordinate) + end + + end +end diff --git a/lib/multilateration/signal_event_data_generator.rb b/lib/multilateration/signal_event_data_generator.rb new file mode 100644 index 0000000..abace4c --- /dev/null +++ b/lib/multilateration/signal_event_data_generator.rb @@ -0,0 +1,76 @@ +module Multilateration + class SignalEventDataGenerator + + def self.generate + new( + receiver_count: 5, + coordinate_num_dimensions: 3, + coordinate_max_area: 1000, + coordinate_max_precision: 4, + signal_propagation_max_speed: 500, + signal_propagation_max_precision: 4, + ).generate + end + + attr_reader :receiver_count, :coordinate_num_dimensions, :coordinate_max_area, :coordinate_max_precision, :signal_propagation_max_speed, :signal_propagation_max_precision + private :receiver_count, :coordinate_num_dimensions, :coordinate_max_area, :coordinate_max_precision, :signal_propagation_max_speed, :signal_propagation_max_precision + + def initialize(receiver_count:, coordinate_num_dimensions:, coordinate_max_area:, coordinate_max_precision:, signal_propagation_max_speed:, signal_propagation_max_precision:) + @receiver_count = receiver_count + @coordinate_num_dimensions = coordinate_num_dimensions + @coordinate_max_area = coordinate_max_area + @coordinate_max_precision = coordinate_max_precision + @signal_propagation_max_speed = signal_propagation_max_speed + @signal_propagation_max_precision = signal_propagation_max_precision + end + + def generate(rc: receiver_count) + signal_propagation_speed = random_signal_propagation_speed + emission_timestamp = random_high_resolution_timestamp + emitter_coordinate = random_coordinate + receiver_coordinates = rc.times.map { random_coordinate } + + { + signal_propagation_speed: signal_propagation_speed, + emitter_event: signal_event_for(emitter_coordinate, emission_timestamp), + receiver_events: receiver_coordinates.map { |coordinate| + signal_event_for(coordinate, propagation_timestamp(signal_propagation_speed, emission_timestamp, emitter_coordinate, coordinate)) + }, + } + end + + private + + def signal_event_for(coordinate, timestamp) + {coordinate: coordinate, time: timestamp} + end + + def propagation_timestamp(signal_propagation_speed, emission_timestamp, emitter_coordinate, coordinate) + propagation_duration = distance_between_coordinates(emitter_coordinate, coordinate) / signal_propagation_speed + propagation_time = emission_timestamp - propagation_duration + end + + def distance_between_coordinates(coordinate1, coordinate2) + vector1 = coordinate_to_vector(coordinate1) + vector2 = coordinate_to_vector(coordinate2) + Math.sqrt((vector1 - vector2).map { |component| component.abs**2 }.reduce(&:+)) + end + + def random_signal_propagation_speed(spms: signal_propagation_max_speed, spmp: signal_propagation_max_precision) + Rantly { range(1, spms) + float.round(range(0, spmp)) } + end + + def random_high_resolution_timestamp + Time.at(Rantly { integer(1_000_000_000) + float }) + end + + def random_coordinate(cnd: coordinate_num_dimensions, cma: coordinate_max_area, cmp: coordinate_max_precision) + Rantly(cnd) { range(-cma, cma) + float.round(range(0, cmp)) } + end + + def coordinate_to_vector(coordinate) + Vector.elements(coordinate) + end + + end +end diff --git a/lib/multilateration/solver.rb b/lib/multilateration/solver.rb index 46f8dd3..0df835e 100644 --- a/lib/multilateration/solver.rb +++ b/lib/multilateration/solver.rb @@ -1,20 +1,25 @@ module Multilateration class Solver - attr_reader :receivers, :wave_speed, :time_of_arrival_strategy - private :receivers, :wave_speed, :time_of_arrival_strategy + attr_reader :receivers, :signal_propagation_speed + private :receivers, :signal_propagation_speed - def initialize(unsorted_receivers, time_of_arrival_strategy) - @receivers = unsorted_receivers.sort_by { |r| time_of_arrival_strategy.toa(r) } - @wave_speed = time_of_arrival_strategy.wave_speed - @time_of_arrival_strategy = time_of_arrival_strategy + def initialize(receiver_events:, signal_propagation_speed:) + @receivers = receiver_events.map {|e| SignalEvent.new(e) }.sort + @signal_propagation_speed = signal_propagation_speed end - def solved_vector - Vector.elements (ai_matrix * bi_matrix).flat_map.to_a + def solved_signal_event_emitter + time_between = first_receiver.distance_between(solved_coordinate) / signal_propagation_speed + solved_time = (first_receiver.time_of_arrival + time_between) + SignalEvent.new(coordinate: solved_coordinate, time: solved_time).to_h end private + def solved_coordinate + (ai_matrix * bi_matrix).flat_map.to_a + end + def ai_matrix Matrix.rows(middle_receivers.map { |i| ai(i) }).inverse end @@ -29,9 +34,9 @@ def ai(i) end def bi(i) - ( distance(tdoa_between_receivers_first_and(i)) * ( distance_sq(tdoa_between_receivers_first_and_last) - inner_product_sq(last_receiver) )) \ - + ( inner_product_sq(first_receiver) * ( distance(tdoa_between_receivers_first_and(i)) - distance(tdoa_between_receivers_first_and_last) )) \ - + ( distance(tdoa_between_receivers_first_and_last) * ( inner_product_sq(i) - distance_sq(tdoa_between_receivers_first_and(i)) )) + ( distance(tdoa_between_receivers_first_and(i)) * ( distance_sq(tdoa_between_receivers_first_and_last) - last_receiver.inner_product_sq )) \ + + ( first_receiver.inner_product_sq * ( distance(tdoa_between_receivers_first_and(i)) - distance(tdoa_between_receivers_first_and_last) )) \ + + ( distance(tdoa_between_receivers_first_and_last) * ( i.inner_product_sq - distance_sq(tdoa_between_receivers_first_and(i)) )) end def middle_receivers @@ -51,20 +56,16 @@ def tdoa_between_receivers_first_and_last end def tdoa_between_receivers_first_and(other_receiver) - time_of_arrival_strategy.tdoa(other_receiver, first_receiver) + first_receiver.time_difference_of_arrival(other_receiver) end def distance(time, exp=1) - (wave_speed**exp) * (time**exp) + (signal_propagation_speed**exp) * (time**exp) end def distance_sq(time) distance(time, 2) end - def inner_product_sq(vector) - vector.inner_product(vector) - end - end end diff --git a/lib/multilateration/time_of_arrival_strategies/source_vector_distance.rb b/lib/multilateration/time_of_arrival_strategies/source_vector_distance.rb deleted file mode 100644 index 242a3c8..0000000 --- a/lib/multilateration/time_of_arrival_strategies/source_vector_distance.rb +++ /dev/null @@ -1,27 +0,0 @@ -module Multilateration - module TimeOfArrivalStrategies - class SourceVectorDistance - attr_reader :source_vector, :wave_speed - private :source_vector - - def initialize(source_vector, wave_speed=1) - @source_vector = source_vector - @wave_speed = wave_speed - end - - def tdoa(vector_a, vector_b) - toa(vector_a) - toa(vector_b) - end - - def toa(target_vector) - distance_between_source_vector_and(target_vector) / wave_speed - end - - private - - def distance_between_source_vector_and(target_vector) - Math.sqrt((target_vector - source_vector).map { |component| component.abs**2 }.reduce(&:+)) - end - end - end -end diff --git a/multilateration.gemspec b/multilateration.gemspec index 45d6c2d..a13b77e 100644 --- a/multilateration.gemspec +++ b/multilateration.gemspec @@ -20,5 +20,10 @@ Gem::Specification.new do |spec| spec.add_development_dependency "bundler", "~> 1.3" spec.add_development_dependency "rake" + spec.add_development_dependency "pry" spec.add_development_dependency "rspec", "~> 3.0" + spec.add_development_dependency "generative", "~> 0.2" + + spec.add_dependency "rantly", "~> 0.3" + end diff --git a/spec/multilateration/signal_event_data_generator_spec.rb b/spec/multilateration/signal_event_data_generator_spec.rb new file mode 100644 index 0000000..ff5f8cf --- /dev/null +++ b/spec/multilateration/signal_event_data_generator_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Multilateration::SignalEventDataGenerator do + describe '.generate' do + subject(:generate) { described_class.generate } + + let(:signal_event_matcher) do + { + coordinate: [be_a(Numeric), be_a(Numeric), be_a(Numeric)], # [368, -454, 978.9] + # timestamp: match(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{9}(\+|\-)\d{4}$/) # "1945-12-22T18:13:16.144102423+0000" + time: be_a(Time), + } + end + + specify { expect(generate).to match( + signal_propagation_speed: be_a(Numeric), + emitter_event: signal_event_matcher, + receiver_events: [signal_event_matcher,signal_event_matcher,signal_event_matcher,signal_event_matcher,signal_event_matcher], + )} + end +end diff --git a/spec/multilateration/solver_spec.rb b/spec/multilateration/solver_spec.rb index d16f933..56a5aba 100644 --- a/spec/multilateration/solver_spec.rb +++ b/spec/multilateration/solver_spec.rb @@ -1,18 +1,27 @@ require 'spec_helper' describe Multilateration::Solver do - describe "#solved_vector" do - let(:source_vector) { Vector[-20,5,120.2] } - let(:reciver_vector_0) { Vector[0,25,1] } - let(:reciver_vector_1) { Vector[50,20.34,2] } - let(:reciver_vector_2) { Vector[50,50,33] } - let(:reciver_vector_3) { Vector[23,75,4] } - let(:reciver_vector_4) { Vector[100,-100,-5.5] } - let(:time_of_arrival_strategy) { Multilateration::TimeOfArrivalStrategies::SourceVectorDistance.new(source_vector) } - let(:recivers) { [reciver_vector_0, reciver_vector_1, reciver_vector_2, reciver_vector_3, reciver_vector_4] } + describe "#solved_signal_event_emitter" do + generative do + let(:tolerance) { 0.0000001 } - subject(:solved_vector) { described_class.new(recivers, time_of_arrival_strategy).solved_vector } + let(:signal_event_data) { Multilateration::SignalEventDataGenerator.generate } + let(:signal_event_emiter) { signal_event_data.fetch(:emitter_event) } + let(:signal_event_emiter_time) { signal_event_emiter[:time] } + let(:signal_event_emiter_coordinate) { signal_event_emiter[:coordinate] } + let(:signal_event_data_without_emiter) { signal_event_data.reject { |k,v| v == signal_event_emiter } } - specify { expect( solved_vector.magnitude ).to be_within(0.0000000000001).of(source_vector.magnitude) } + subject(:solved_signal_event_emitter) { described_class.new(signal_event_data_without_emiter).solved_signal_event_emitter } + + specify { expect(solved_signal_event_emitter).to match({ + coordinate: [ + be_within(tolerance).of(signal_event_emiter_coordinate[0]), + be_within(tolerance).of(signal_event_emiter_coordinate[1]), + be_within(tolerance).of(signal_event_emiter_coordinate[2]), + ], + time: be_within(tolerance).of(signal_event_emiter_time), + })} + end end + end diff --git a/spec/multilateration/time_of_arrival_strategies/source_vector_distance_spec.rb b/spec/multilateration/time_of_arrival_strategies/source_vector_distance_spec.rb deleted file mode 100644 index 3fa80ee..0000000 --- a/spec/multilateration/time_of_arrival_strategies/source_vector_distance_spec.rb +++ /dev/null @@ -1,4 +0,0 @@ -require 'spec_helper' - -describe Multilateration::TimeOfArrivalStrategies::SourceVectorDistance do -end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2752f9f..4822d34 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,3 +1,6 @@ +require 'pry' +require 'generative' + require 'multilateration' # This file was generated by the `rspec --init` command. Conventionally, all