flash_math 0.0.1
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/.coveralls.yml +1 -0
 - data/.gitignore +17 -0
 - data/.rspec +4 -0
 - data/.travis.yml +13 -0
 - data/Gemfile +4 -0
 - data/LICENSE.txt +22 -0
 - data/README.md +187 -0
 - data/Rakefile +6 -0
 - data/flash_math.gemspec +27 -0
 - data/lib/flash_math.rb +3 -0
 - data/lib/flash_math/modules/geometry.rb +10 -0
 - data/lib/flash_math/modules/geometry/geometric_bounding_box.rb +16 -0
 - data/lib/flash_math/modules/geometry/geometric_distance.rb +20 -0
 - data/lib/flash_math/modules/geometry/geometric_line.rb +87 -0
 - data/lib/flash_math/modules/geometry/geometric_point.rb +19 -0
 - data/lib/flash_math/modules/geometry/geometric_point_in_polygon.rb +63 -0
 - data/lib/flash_math/modules/geometry/geometric_polygon.rb +36 -0
 - data/lib/flash_math/modules/geometry/geometric_segment.rb +128 -0
 - data/lib/flash_math/modules/geometry/geometric_vector.rb +46 -0
 - data/lib/flash_math/modules/statistics.rb +1 -0
 - data/lib/flash_math/modules/statistics/statistical_spread.rb +129 -0
 - data/lib/flash_math/version.rb +3 -0
 - data/spec/lib/geometry/geometric_bounding_box_spec.rb +67 -0
 - data/spec/lib/geometry/geometric_distance_spec.rb +55 -0
 - data/spec/lib/geometry/geometric_line_spec.rb +287 -0
 - data/spec/lib/geometry/geometric_point_spec.rb +53 -0
 - data/spec/lib/geometry/geometric_polygon_spec.rb +148 -0
 - data/spec/lib/geometry/geometric_segment_spec.rb +231 -0
 - data/spec/lib/geometry/geometric_vector_spec.rb +110 -0
 - data/spec/lib/statistics/statistics_spread_spec.rb +219 -0
 - data/spec/spec_helper.rb +4 -0
 - metadata +168 -0
 
| 
         @@ -0,0 +1,19 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class GeometricPoint < Struct.new(:x, :y)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              def self.new_by_array(array)
         
     | 
| 
      
 4 
     | 
    
         
            +
                self.new(array[0], array[1])
         
     | 
| 
      
 5 
     | 
    
         
            +
              end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              def to_vector
         
     | 
| 
      
 8 
     | 
    
         
            +
                GeometricVector.new(x, y)
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def advance_by(vector)
         
     | 
| 
      
 12 
     | 
    
         
            +
                GeometricPoint.new(x + vector.x, y + vector.y)
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def ==(another_point)
         
     | 
| 
      
 16 
     | 
    
         
            +
                x === another_point.x && y === another_point.y
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,63 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class GeometricPointInPolygon < Struct.new(:point, :polygon)
         
     | 
| 
      
 2 
     | 
    
         
            +
              extend Memoist
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
              def inside?
         
     | 
| 
      
 5 
     | 
    
         
            +
                point_location == :inside
         
     | 
| 
      
 6 
     | 
    
         
            +
              end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
              def outside?
         
     | 
| 
      
 9 
     | 
    
         
            +
                point_location == :outside
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              def on_the_boundary?
         
     | 
| 
      
 13 
     | 
    
         
            +
                point_location == :on_the_boundary
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              def point_location
         
     | 
| 
      
 17 
     | 
    
         
            +
                return :outside unless bounding_box.contains?(point)
         
     | 
| 
      
 18 
     | 
    
         
            +
                return :on_the_boundary if point_is_vertex? || point_on_edge?
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                intersection_count(choose_good_ray).odd? ? :inside : :outside
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              delegate :vertices, :edges, :bounding_box, to: :polygon
         
     | 
| 
      
 24 
     | 
    
         
            +
              memoize  :point_location, :edges, :bounding_box
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              private
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
              def choose_good_ray
         
     | 
| 
      
 29 
     | 
    
         
            +
                ray = random_ray
         
     | 
| 
      
 30 
     | 
    
         
            +
                while ! good_ray?(ray) do
         
     | 
| 
      
 31 
     | 
    
         
            +
                  ray = random_ray
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
                ray
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
              def good_ray?(ray)
         
     | 
| 
      
 37 
     | 
    
         
            +
                edges.none? { |edge| edge.parallel_to?(ray) } && vertices.none? { |vertex| ray.contains_point?(vertex) }
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              def intersection_count(ray)
         
     | 
| 
      
 41 
     | 
    
         
            +
                edges.select { |edge| edge.intersects_with?(ray) }.size
         
     | 
| 
      
 42 
     | 
    
         
            +
              end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
              def point_is_vertex?
         
     | 
| 
      
 45 
     | 
    
         
            +
                vertices.any? { |vertex| vertex == point }
         
     | 
| 
      
 46 
     | 
    
         
            +
              end
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
              def point_on_edge?
         
     | 
| 
      
 49 
     | 
    
         
            +
                edges.any? { |edge| edge.contains_point?(point) }
         
     | 
| 
      
 50 
     | 
    
         
            +
              end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
              def random_ray
         
     | 
| 
      
 53 
     | 
    
         
            +
                random_direction = rand * (2 * Math::PI)
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
                ray_endpoint = GeometricPoint.new(sufficient_ray_radius * Math.cos(random_direction), sufficient_ray_radius * Math.sin(random_direction))
         
     | 
| 
      
 56 
     | 
    
         
            +
                GeometricSegment.new(point, ray_endpoint)
         
     | 
| 
      
 57 
     | 
    
         
            +
              end
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
              def sufficient_ray_radius
         
     | 
| 
      
 60 
     | 
    
         
            +
                @sufficient_ray_radius ||= bounding_box.diagonal.length * 2
         
     | 
| 
      
 61 
     | 
    
         
            +
              end
         
     | 
| 
      
 62 
     | 
    
         
            +
             
     | 
| 
      
 63 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,36 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class GeometricPolygon < Struct.new(:vertices)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              def bounding_box
         
     | 
| 
      
 4 
     | 
    
         
            +
                leftbottom = GeometricPoint.new(vertices.map(&:x).min, vertices.map(&:y).min)
         
     | 
| 
      
 5 
     | 
    
         
            +
                righttop = GeometricPoint.new(vertices.map(&:x).max, vertices.map(&:y).max)
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
                GeometricBoundingBox.new(leftbottom, righttop)
         
     | 
| 
      
 8 
     | 
    
         
            +
              end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
              def area
         
     | 
| 
      
 11 
     | 
    
         
            +
                sum = 0.0
         
     | 
| 
      
 12 
     | 
    
         
            +
                (0..vertices.length-1).each do |i|
         
     | 
| 
      
 13 
     | 
    
         
            +
                  a = vertices[i-1]
         
     | 
| 
      
 14 
     | 
    
         
            +
                  b = vertices[i]
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
                  sum = sum + ((a.x * b.y) - (a.y * b.x))
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
                (sum / 2).abs
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              def contains?(point)
         
     | 
| 
      
 22 
     | 
    
         
            +
                point_in_polygon = GeometricPointInPolygon.new(point, self)
         
     | 
| 
      
 23 
     | 
    
         
            +
                point_in_polygon.inside? || point_in_polygon.on_the_boundary?
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
              def edges
         
     | 
| 
      
 27 
     | 
    
         
            +
                edges = []
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                1.upto(vertices.length - 1) do |vertex_index|
         
     | 
| 
      
 30 
     | 
    
         
            +
                  edges << GeometricSegment.new(vertices[vertex_index - 1], vertices[vertex_index])
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                edges << GeometricSegment.new(vertices.last, vertices.first)
         
     | 
| 
      
 34 
     | 
    
         
            +
              end
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,128 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class GeometricSegmentsDoNotIntersect < Exception; end
         
     | 
| 
      
 2 
     | 
    
         
            +
            class GeometricSegmentsOverlap < Exception; end
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            class GeometricSegment < Struct.new(:point1, :point2)
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
      
 6 
     | 
    
         
            +
              def self.new_by_arrays(point1_coordinates, point2_coordinates)
         
     | 
| 
      
 7 
     | 
    
         
            +
                self.new(GeometricPoint.new_by_array(point1_coordinates),
         
     | 
| 
      
 8 
     | 
    
         
            +
                         GeometricPoint.new_by_array(point2_coordinates))
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def bottommost_endpoint
         
     | 
| 
      
 12 
     | 
    
         
            +
                ((point1.y <=> point2.y) == -1) ? point1 : point2
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def leftmost_endpoint
         
     | 
| 
      
 16 
     | 
    
         
            +
                ((point1.x <=> point2.x) == -1) ? point1 : point2
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def rightmost_endpoint
         
     | 
| 
      
 20 
     | 
    
         
            +
                ((point1.x <=> point2.x) == 1) ? point1 : point2
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              def topmost_endpoint
         
     | 
| 
      
 24 
     | 
    
         
            +
                ((point1.y <=> point2.y) == 1) ? point1 : point2
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              def contains_point?(point)
         
     | 
| 
      
 28 
     | 
    
         
            +
                GeometricDistance.new(point1, point2).distance ===
         
     | 
| 
      
 29 
     | 
    
         
            +
                  GeometricDistance.new(point1, point).distance + GeometricDistance.new(point, point2).distance
         
     | 
| 
      
 30 
     | 
    
         
            +
              end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
              def distance_to(point)
         
     | 
| 
      
 33 
     | 
    
         
            +
                q = point.to_vector
         
     | 
| 
      
 34 
     | 
    
         
            +
                p1 = point1.to_vector
         
     | 
| 
      
 35 
     | 
    
         
            +
                p2 = point2.to_vector
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                u = p2 - p1
         
     | 
| 
      
 38 
     | 
    
         
            +
                v = q - p1
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                a = u.scalar_product(v)
         
     | 
| 
      
 41 
     | 
    
         
            +
                if a < 0
         
     | 
| 
      
 42 
     | 
    
         
            +
                  p = p1
         
     | 
| 
      
 43 
     | 
    
         
            +
                else
         
     | 
| 
      
 44 
     | 
    
         
            +
                  b = u.scalar_product(u)
         
     | 
| 
      
 45 
     | 
    
         
            +
                  if a > b
         
     | 
| 
      
 46 
     | 
    
         
            +
                    p = p2
         
     | 
| 
      
 47 
     | 
    
         
            +
                  else
         
     | 
| 
      
 48 
     | 
    
         
            +
                    p = p1 + (a.to_f / b * u)
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                end
         
     | 
| 
      
 51 
     | 
    
         
            +
             
     | 
| 
      
 52 
     | 
    
         
            +
                return GeometricDistance.new(q, p).distance
         
     | 
| 
      
 53 
     | 
    
         
            +
              end
         
     | 
| 
      
 54 
     | 
    
         
            +
             
     | 
| 
      
 55 
     | 
    
         
            +
              def intersection_point_with(segment)
         
     | 
| 
      
 56 
     | 
    
         
            +
                raise GeometricSegmentsDoNotIntersect unless intersects_with?(segment)
         
     | 
| 
      
 57 
     | 
    
         
            +
                raise GeometricSegmentsOverlap if overlaps?(segment)
         
     | 
| 
      
 58 
     | 
    
         
            +
             
     | 
| 
      
 59 
     | 
    
         
            +
                numerator = (segment.point1.y - point1.y) * (segment.point1.x - segment.point2.x) -
         
     | 
| 
      
 60 
     | 
    
         
            +
                  (segment.point1.y - segment.point2.y) * (segment.point1.x - point1.x);
         
     | 
| 
      
 61 
     | 
    
         
            +
                denominator = (point2.y - point1.y) * (segment.point1.x - segment.point2.x) -
         
     | 
| 
      
 62 
     | 
    
         
            +
                  (segment.point1.y - segment.point2.y) * (point2.x - point1.x);
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                t = numerator.to_f / denominator;
         
     | 
| 
      
 65 
     | 
    
         
            +
             
     | 
| 
      
 66 
     | 
    
         
            +
                x = point1.x + t * (point2.x - point1.x)
         
     | 
| 
      
 67 
     | 
    
         
            +
                y = point1.y + t * (point2.y - point1.y)
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                GeometricPoint.new(x, y)
         
     | 
| 
      
 70 
     | 
    
         
            +
              end
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
              def intersects_with?(segment)
         
     | 
| 
      
 73 
     | 
    
         
            +
                GeometricSegment.have_intersecting_bounds?(self, segment) &&
         
     | 
| 
      
 74 
     | 
    
         
            +
                  lies_on_line_intersecting?(segment) &&
         
     | 
| 
      
 75 
     | 
    
         
            +
                  segment.lies_on_line_intersecting?(self)
         
     | 
| 
      
 76 
     | 
    
         
            +
              end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
              def length
         
     | 
| 
      
 79 
     | 
    
         
            +
                GeometricDistance.new(point1, point2).distance
         
     | 
| 
      
 80 
     | 
    
         
            +
              end
         
     | 
| 
      
 81 
     | 
    
         
            +
             
     | 
| 
      
 82 
     | 
    
         
            +
              def lies_on_one_line_with?(segment)
         
     | 
| 
      
 83 
     | 
    
         
            +
                GeometricSegment.new(point1, segment.point1).parallel_to?(self) &&
         
     | 
| 
      
 84 
     | 
    
         
            +
                  GeometricSegment.new(point1, segment.point2).parallel_to?(self)
         
     | 
| 
      
 85 
     | 
    
         
            +
              end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
              def overlaps?(segment)
         
     | 
| 
      
 88 
     | 
    
         
            +
                GeometricSegment.have_intersecting_bounds?(self, segment) &&
         
     | 
| 
      
 89 
     | 
    
         
            +
                  lies_on_one_line_with?(segment)
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              def parallel_to?(segment)
         
     | 
| 
      
 93 
     | 
    
         
            +
                to_vector.collinear_with?(segment.to_vector)
         
     | 
| 
      
 94 
     | 
    
         
            +
              end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
              def to_vector
         
     | 
| 
      
 97 
     | 
    
         
            +
                GeometricVector.new(point2.x - point1.x, point2.y - point1.y)
         
     | 
| 
      
 98 
     | 
    
         
            +
              end
         
     | 
| 
      
 99 
     | 
    
         
            +
             
     | 
| 
      
 100 
     | 
    
         
            +
              protected
         
     | 
| 
      
 101 
     | 
    
         
            +
             
     | 
| 
      
 102 
     | 
    
         
            +
              def self.have_intersecting_bounds?(segment1, segment2)
         
     | 
| 
      
 103 
     | 
    
         
            +
                intersects_on_x_axis =
         
     | 
| 
      
 104 
     | 
    
         
            +
                  (segment1.leftmost_endpoint.x < segment2.rightmost_endpoint.x ||
         
     | 
| 
      
 105 
     | 
    
         
            +
                  segment1.leftmost_endpoint.x == segment2.rightmost_endpoint.x) &&
         
     | 
| 
      
 106 
     | 
    
         
            +
                  (segment2.leftmost_endpoint.x < segment1.rightmost_endpoint.x ||
         
     | 
| 
      
 107 
     | 
    
         
            +
                  segment2.leftmost_endpoint.x == segment1.rightmost_endpoint.x)
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
                intersects_on_y_axis =
         
     | 
| 
      
 110 
     | 
    
         
            +
                  (segment1.bottommost_endpoint.y < segment2.topmost_endpoint.y ||
         
     | 
| 
      
 111 
     | 
    
         
            +
                  segment1.bottommost_endpoint.y == segment2.topmost_endpoint.y) &&
         
     | 
| 
      
 112 
     | 
    
         
            +
                  (segment2.bottommost_endpoint.y < segment1.topmost_endpoint.y ||
         
     | 
| 
      
 113 
     | 
    
         
            +
                  segment2.bottommost_endpoint.y == segment1.topmost_endpoint.y)
         
     | 
| 
      
 114 
     | 
    
         
            +
             
     | 
| 
      
 115 
     | 
    
         
            +
                intersects_on_x_axis && intersects_on_y_axis
         
     | 
| 
      
 116 
     | 
    
         
            +
              end
         
     | 
| 
      
 117 
     | 
    
         
            +
             
     | 
| 
      
 118 
     | 
    
         
            +
              def lies_on_line_intersecting?(segment)
         
     | 
| 
      
 119 
     | 
    
         
            +
                vector_to_first_endpoint = GeometricSegment.new(self.point1, segment.point1).to_vector
         
     | 
| 
      
 120 
     | 
    
         
            +
                vector_to_second_endpoint = GeometricSegment.new(self.point1, segment.point2).to_vector
         
     | 
| 
      
 121 
     | 
    
         
            +
             
     | 
| 
      
 122 
     | 
    
         
            +
                #FIXME: '>=' and '<=' method of Fixnum and Float should be overriden too (take precision into account)
         
     | 
| 
      
 123 
     | 
    
         
            +
                # there is a rare case, when this method is wrong due to precision
         
     | 
| 
      
 124 
     | 
    
         
            +
                self.to_vector.cross_product(vector_to_first_endpoint) *
         
     | 
| 
      
 125 
     | 
    
         
            +
                  self.to_vector.cross_product(vector_to_second_endpoint) <= 0
         
     | 
| 
      
 126 
     | 
    
         
            +
              end
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,46 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class GeometricVector < Struct.new(:x, :y)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              def self.new_by_array(array)
         
     | 
| 
      
 4 
     | 
    
         
            +
                self.new(array[0], array[1])
         
     | 
| 
      
 5 
     | 
    
         
            +
              end
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              def ==(vector)
         
     | 
| 
      
 8 
     | 
    
         
            +
                x === vector.x && y === vector.y
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def +(vector)
         
     | 
| 
      
 12 
     | 
    
         
            +
                GeometricVector.new(x + vector.x, y + vector.y)
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
              def -(vector)
         
     | 
| 
      
 16 
     | 
    
         
            +
                self + (-1) * vector
         
     | 
| 
      
 17 
     | 
    
         
            +
              end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
              def *(scalar)
         
     | 
| 
      
 20 
     | 
    
         
            +
                GeometricVector.new(x * scalar, y * scalar)
         
     | 
| 
      
 21 
     | 
    
         
            +
              end
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
              def coerce(scalar)
         
     | 
| 
      
 24 
     | 
    
         
            +
                if scalar.is_a?(Numeric)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  [self, scalar]
         
     | 
| 
      
 26 
     | 
    
         
            +
                else
         
     | 
| 
      
 27 
     | 
    
         
            +
                  raise ArgumentError, "GeometricVector: cannot coerce #{scalar.inspect}"
         
     | 
| 
      
 28 
     | 
    
         
            +
                end
         
     | 
| 
      
 29 
     | 
    
         
            +
              end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
              def cross_product(vector)
         
     | 
| 
      
 32 
     | 
    
         
            +
                x * vector.y - y * vector.x
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
              def scalar_product(vector)
         
     | 
| 
      
 36 
     | 
    
         
            +
                x * vector.x + y * vector.y
         
     | 
| 
      
 37 
     | 
    
         
            +
              end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
              def collinear_with?(vector)
         
     | 
| 
      
 40 
     | 
    
         
            +
                cross_product(vector) === 0
         
     | 
| 
      
 41 
     | 
    
         
            +
              end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
              def modulus
         
     | 
| 
      
 44 
     | 
    
         
            +
                Math.hypot(x ,y)
         
     | 
| 
      
 45 
     | 
    
         
            +
              end
         
     | 
| 
      
 46 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'flash_math/modules/statistics/statistical_spread'
         
     | 
| 
         @@ -0,0 +1,129 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            class StatisticalSpread < Struct.new(:values)
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
              def sum(identity=0, &block)
         
     | 
| 
      
 4 
     | 
    
         
            +
                if block_given?
         
     | 
| 
      
 5 
     | 
    
         
            +
                  StatisticalSpread.new(values.map(&block)).sum(identity)
         
     | 
| 
      
 6 
     | 
    
         
            +
                else
         
     | 
| 
      
 7 
     | 
    
         
            +
                  values.inject(:+) || identity
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
              def mean
         
     | 
| 
      
 12 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 13 
     | 
    
         
            +
                sum / values.length.to_f
         
     | 
| 
      
 14 
     | 
    
         
            +
              end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
              def median
         
     | 
| 
      
 17 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 18 
     | 
    
         
            +
                sorted = values.sort
         
     | 
| 
      
 19 
     | 
    
         
            +
             
     | 
| 
      
 20 
     | 
    
         
            +
                if values.length % 2 == 0
         
     | 
| 
      
 21 
     | 
    
         
            +
                  (sorted[(values.length / 2) -1] + sorted[values.length / 2]) / 2.0
         
     | 
| 
      
 22 
     | 
    
         
            +
                else
         
     | 
| 
      
 23 
     | 
    
         
            +
                  sorted[values.length / 2]
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
             
     | 
| 
      
 27 
     | 
    
         
            +
              def mode
         
     | 
| 
      
 28 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 29 
     | 
    
         
            +
                frequency_distribution = values.inject(Hash.new(0)) { |hash, value| hash[value] += 1; hash }
         
     | 
| 
      
 30 
     | 
    
         
            +
                top_2 = frequency_distribution.sort { |a,b| b[1] <=> a[1] } .take(2)
         
     | 
| 
      
 31 
     | 
    
         
            +
                if top_2.length == 1
         
     | 
| 
      
 32 
     | 
    
         
            +
                  top_2.first.first
         
     | 
| 
      
 33 
     | 
    
         
            +
                elsif top_2.first.last == top_2.last.last
         
     | 
| 
      
 34 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 35 
     | 
    
         
            +
                else
         
     | 
| 
      
 36 
     | 
    
         
            +
                  top_2.first.first
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
              end
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
              def range
         
     | 
| 
      
 41 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 42 
     | 
    
         
            +
                sorted = values.sort
         
     | 
| 
      
 43 
     | 
    
         
            +
                sorted.last - sorted.first
         
     | 
| 
      
 44 
     | 
    
         
            +
              end
         
     | 
| 
      
 45 
     | 
    
         
            +
             
     | 
| 
      
 46 
     | 
    
         
            +
              def max
         
     | 
| 
      
 47 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 48 
     | 
    
         
            +
                values.sort.last
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
              def min
         
     | 
| 
      
 52 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 53 
     | 
    
         
            +
                values.sort.first
         
     | 
| 
      
 54 
     | 
    
         
            +
              end
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
              def percentile_from_value(value)
         
     | 
| 
      
 57 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 58 
     | 
    
         
            +
                (values.sort.index(value) / values.length.to_f * 100).ceil
         
     | 
| 
      
 59 
     | 
    
         
            +
              end
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
              def value_from_percentile(percentile)
         
     | 
| 
      
 62 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 63 
     | 
    
         
            +
                value_index = (percentile.to_f / 100 * values.length).ceil
         
     | 
| 
      
 64 
     | 
    
         
            +
                values.sort[value_index]
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
              def variance
         
     | 
| 
      
 68 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 69 
     | 
    
         
            +
                precalculated_mean = StatisticalSpread.new(values).mean
         
     | 
| 
      
 70 
     | 
    
         
            +
                sum = values.inject(0) { |accumulator, value| accumulator + (value - precalculated_mean) ** 2 }
         
     | 
| 
      
 71 
     | 
    
         
            +
                sum / (values.length.to_f - 1)
         
     | 
| 
      
 72 
     | 
    
         
            +
              end
         
     | 
| 
      
 73 
     | 
    
         
            +
             
     | 
| 
      
 74 
     | 
    
         
            +
              def population_variance
         
     | 
| 
      
 75 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 76 
     | 
    
         
            +
                precalculated_mean = StatisticalSpread.new(values).mean
         
     | 
| 
      
 77 
     | 
    
         
            +
                sum = values.inject(0) { |accumulator, value| accumulator + (value - precalculated_mean) ** 2 }
         
     | 
| 
      
 78 
     | 
    
         
            +
                sum / values.length.to_f
         
     | 
| 
      
 79 
     | 
    
         
            +
              end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
              def standard_deviation
         
     | 
| 
      
 82 
     | 
    
         
            +
                return if values.length < 2
         
     | 
| 
      
 83 
     | 
    
         
            +
                Math.sqrt(StatisticalSpread.new(values).variance)
         
     | 
| 
      
 84 
     | 
    
         
            +
              end
         
     | 
| 
      
 85 
     | 
    
         
            +
             
     | 
| 
      
 86 
     | 
    
         
            +
              def relative_standard_deviation
         
     | 
| 
      
 87 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 88 
     | 
    
         
            +
                precalculated_mean = StatisticalSpread.new(values).mean
         
     | 
| 
      
 89 
     | 
    
         
            +
                (StatisticalSpread.new(values).population_standard_deviation / precalculated_mean) * 100.0
         
     | 
| 
      
 90 
     | 
    
         
            +
              end
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
              def population_standard_deviation
         
     | 
| 
      
 93 
     | 
    
         
            +
                return if values.length < 1
         
     | 
| 
      
 94 
     | 
    
         
            +
                Math.sqrt(StatisticalSpread.new(values).population_variance)
         
     | 
| 
      
 95 
     | 
    
         
            +
              end
         
     | 
| 
      
 96 
     | 
    
         
            +
             
     | 
| 
      
 97 
     | 
    
         
            +
              def skewness
         
     | 
| 
      
 98 
     | 
    
         
            +
                return if values.length == 0
         
     | 
| 
      
 99 
     | 
    
         
            +
                return 0 if values.length == 1
         
     | 
| 
      
 100 
     | 
    
         
            +
                StatisticalSpread.new(values).sum_cubed_deviation / ((values.length - 1) * StatisticalSpread.new(values).cubed_standard_deviation.to_f)
         
     | 
| 
      
 101 
     | 
    
         
            +
              end
         
     | 
| 
      
 102 
     | 
    
         
            +
             
     | 
| 
      
 103 
     | 
    
         
            +
              def kurtosis
         
     | 
| 
      
 104 
     | 
    
         
            +
                return if values.length == 0
         
     | 
| 
      
 105 
     | 
    
         
            +
                return 0 if values.length == 1
         
     | 
| 
      
 106 
     | 
    
         
            +
                StatisticalSpread.new(values).sum_quarted_deviation / ((values.length - 1) * StatisticalSpread.new(values).quarted_standard_deviation.to_f)
         
     | 
| 
      
 107 
     | 
    
         
            +
              end
         
     | 
| 
      
 108 
     | 
    
         
            +
             
     | 
| 
      
 109 
     | 
    
         
            +
              protected
         
     | 
| 
      
 110 
     | 
    
         
            +
             
     | 
| 
      
 111 
     | 
    
         
            +
              def sum_cubed_deviation
         
     | 
| 
      
 112 
     | 
    
         
            +
                precalculated_mean = StatisticalSpread.new(values).mean
         
     | 
| 
      
 113 
     | 
    
         
            +
                values.inject(0) { |sum, value| sum + (value - precalculated_mean) ** 3}
         
     | 
| 
      
 114 
     | 
    
         
            +
              end
         
     | 
| 
      
 115 
     | 
    
         
            +
             
     | 
| 
      
 116 
     | 
    
         
            +
              def cubed_standard_deviation
         
     | 
| 
      
 117 
     | 
    
         
            +
                StatisticalSpread.new(values).standard_deviation ** 3
         
     | 
| 
      
 118 
     | 
    
         
            +
              end
         
     | 
| 
      
 119 
     | 
    
         
            +
             
     | 
| 
      
 120 
     | 
    
         
            +
              def sum_quarted_deviation
         
     | 
| 
      
 121 
     | 
    
         
            +
                precalculated_mean = StatisticalSpread.new(values).mean
         
     | 
| 
      
 122 
     | 
    
         
            +
                values.inject(0) { |sum, value| sum + (value - precalculated_mean) ** 4}
         
     | 
| 
      
 123 
     | 
    
         
            +
              end
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
              def quarted_standard_deviation
         
     | 
| 
      
 126 
     | 
    
         
            +
                StatisticalSpread.new(values).standard_deviation ** 4
         
     | 
| 
      
 127 
     | 
    
         
            +
              end
         
     | 
| 
      
 128 
     | 
    
         
            +
             
     | 
| 
      
 129 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,67 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'spec_helper'
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe GeometricBoundingBox do
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
              describe '#initialize_by_points' do
         
     | 
| 
      
 6 
     | 
    
         
            +
                point1 = GeometricPoint.new(1, 2)
         
     | 
| 
      
 7 
     | 
    
         
            +
                point2 = GeometricPoint.new(3, 4)
         
     | 
| 
      
 8 
     | 
    
         
            +
                bounding_box = GeometricBoundingBox.new(point1, point2)
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
                it 'to be equal' do
         
     | 
| 
      
 11 
     | 
    
         
            +
                  expect(bounding_box.leftbottom).to eq(point1)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  expect(bounding_box.righttop).to eq(point2)
         
     | 
| 
      
 13 
     | 
    
         
            +
                end
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                it 'to be not equal' do
         
     | 
| 
      
 16 
     | 
    
         
            +
                  expect(bounding_box.leftbottom).not_to eq(point2)
         
     | 
| 
      
 17 
     | 
    
         
            +
                  expect(bounding_box.righttop).not_to eq(point1)
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              describe '#initialize_by_arrays' do
         
     | 
| 
      
 22 
     | 
    
         
            +
                point1 = GeometricPoint.new(1, 2)
         
     | 
| 
      
 23 
     | 
    
         
            +
                point2 = GeometricPoint.new(3, 4)
         
     | 
| 
      
 24 
     | 
    
         
            +
                bounding_box = GeometricBoundingBox.new_by_arrays([1, 2], [3, 4])
         
     | 
| 
      
 25 
     | 
    
         
            +
             
     | 
| 
      
 26 
     | 
    
         
            +
                it 'to be equal' do
         
     | 
| 
      
 27 
     | 
    
         
            +
                  expect(bounding_box.leftbottom).to eq(point1)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  expect(bounding_box.righttop).to eq(point2)
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
      
 31 
     | 
    
         
            +
                it 'to be not equal' do
         
     | 
| 
      
 32 
     | 
    
         
            +
                  expect(bounding_box.leftbottom).not_to eq(point2)
         
     | 
| 
      
 33 
     | 
    
         
            +
                  expect(bounding_box.righttop).not_to eq(point1)
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
              end
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
              describe '#contains' do
         
     | 
| 
      
 38 
     | 
    
         
            +
                bounding_box = GeometricBoundingBox.new(GeometricPoint.new(-1, -1), GeometricPoint.new(1, 1))
         
     | 
| 
      
 39 
     | 
    
         
            +
             
     | 
| 
      
 40 
     | 
    
         
            +
                it 'to be containing point' do
         
     | 
| 
      
 41 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(0, 0))).to eq(true)
         
     | 
| 
      
 42 
     | 
    
         
            +
                end
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                it 'to be not containing point' do
         
     | 
| 
      
 45 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(2, 2))).to eq(false)
         
     | 
| 
      
 46 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(-2, -2))).to eq(false)
         
     | 
| 
      
 47 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(1, 2))).to eq(false)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(0, -1.1))).to eq(false)
         
     | 
| 
      
 49 
     | 
    
         
            +
                end
         
     | 
| 
      
 50 
     | 
    
         
            +
             
     | 
| 
      
 51 
     | 
    
         
            +
                it 'to be not containing point' do
         
     | 
| 
      
 52 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(2, 2))).to eq(false)
         
     | 
| 
      
 53 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(-2, -2))).to eq(false)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(1, 2))).to eq(false)
         
     | 
| 
      
 55 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(0, -1.1))).to eq(false)
         
     | 
| 
      
 56 
     | 
    
         
            +
                end
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                it 'to be containing point at edge' do
         
     | 
| 
      
 59 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(1, 0))).to eq(true)
         
     | 
| 
      
 60 
     | 
    
         
            +
                end
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                it 'to be containing point at vertex' do
         
     | 
| 
      
 63 
     | 
    
         
            +
                  expect(bounding_box.contains?(GeometricPoint.new(1, 1))).to eq(true)
         
     | 
| 
      
 64 
     | 
    
         
            +
                end
         
     | 
| 
      
 65 
     | 
    
         
            +
              end
         
     | 
| 
      
 66 
     | 
    
         
            +
             
     | 
| 
      
 67 
     | 
    
         
            +
            end
         
     |