lib/numerical_integration.rb
module Silicium
class IntegralDoesntExistError < RuntimeError; end
class NumberofIterOutofRangeError < RuntimeError; end
##
# A class providing numerical integration methods
class NumericalIntegration
##
# Computes integral by the 3/8 rule
# from +a+ to +b+ of +block+ with accuracy +eps+
def self.three_eights_integration(a, b, eps = 0.0001, &block)
wrapper_method([a, b], eps, :three_eights_integration_n, &block)
end
##
# Computes integral by the 3/8 rule
# from +a+ to +b+ of +block+ with +n+ segmentations
def self.three_eights_integration_n(a, b, n, &block)
dx = (b - a) / n.to_f
result = 0
x = a
n.times do
result +=
(block.call(x) + 3 * block.call((2 * x + x + dx) / 3.0) +
3 * block.call((x + 2 * (x + dx)) / 3.0) + block.call(x + dx)) / 8.0 * dx
x += dx
end
result
end
##
# Computes integral by the Simpson's rule
# from +a+ to +b+ of +block+ with +n+ segmentations
def self.simpson_integration_with_a_segment(a, b, n, &block)
dx = (b - a) / n.to_f
result = 0
i = 0
while i < n
result += (block.call(a + i * dx) + 4 * block.call(((a + i * dx) +
(a + (i + 1) * dx)) / 2.0) + block.call(a + (i + 1) * dx)) / 6.0 * dx
i += 1
end
result
end
##
# Computes integral by the Simpson's rule
# from +a+ to +b+ of +block+ with accuracy +eps+
def self.simpson_integration(a, b, eps = 0.0001, &block)
wrapper_method([a, b], eps, :simpson_integration_with_a_segment, &block)
end
##
# Computes integral by the Left Rectangles method
# from +a+ to +b+ of +block+ with accuracy +eps+
def self.left_rect_integration(a, b, eps = 0.0001, &block)
wrapper_method([a, b], eps, :left_rect_integration_n, &block)
end
##
# Computes integral by the Left Rectangles method
# from +a+ to +b+ of +block+ with +n+ segmentations
def self.left_rect_integration_n(a, b, n, &block)
dx = (b - a) / n.to_f
amount_calculation(a, [0, n], dx, &block)
end
##
# Computes integral by the Right Rectangles method
# from +a+ to +b+ of +block+ with accuracy +eps+
def self.right_rect_integration(a, b, eps = 0.0001, &block)
wrapper_method([a, b], eps, :right_rect_integration_n, &block)
end
##
# Computes integral by the Right Rectangles method
# from +a+ to +b+ of +block+ with +n+ segmentations
def self.right_rect_integration_n(a, b, n, &block)
dx = (b - a) / n.to_f
amount_calculation(a, [1, n + 1], dx, &block)
end
##
# Computes integral by the Middle Rectangles method
# from +a+ to +b+ of +block+ with +n+ segmentations
def self.middle_rectangles_with_a_segment(a, b, n, &block)
dx = (b - a) / n.to_f
result = 0
i = 0
n.times do
result += block.call(a + dx * (i + 1 / 2)) * dx
i += 1
end
result
end
##
# Computes integral by the Middle Rectangles method
# from +a+ to +b+ of +block+ with accuracy +eps+
def self.middle_rectangles(a, b, eps = 0.0001, &block)
wrapper_method([a, b], eps, :middle_rectangles_with_a_segment, &block)
end
##
# Computes integral by the Trapezoid method
# from +a+ to +b+ of +block+ with +n+ segmentations
def self.trapezoid_with_a_segment(a, b, n, &block)
dx = (b - a) / n.to_f
result = 0
i = 1
(n - 1).times do
result += block.call(a + dx * i)
i += 1
end
result += (block.call(a) + block.call(b)) / 2.0
result * dx
end
##
# Computes integral by the Trapezoid method
# from +a+ to +b+ of +block+ with accuracy +eps+
def self.trapezoid(a, b, eps = 0.0001, &block)
wrapper_method([a, b], eps, :trapezoid_with_a_segment ,&block)
end
private
##
# Wrapper method for num_integratons methods
# @param [Array] a_b integration range
# @param [Numeric] eps
# @param [Proc] proc - integration Proc
# @param [Block] block - integrated function as Block
def self.wrapper_method(a_b, eps, method_name, &block)
n = 1
max_it = 10_000
begin
begin
result = send(method_name, a_b[0], a_b[1], n, &block)
check_value(result)
n *= 5
raise NumberofIterOutofRangeError if n > max_it
result1 = send(method_name, a_b[0], a_b[1], n, &block)
check_value(result1)
end until (result - result1).abs < eps
rescue Math::DomainError
raise IntegralDoesntExistError, 'Domain error in math function'
rescue ZeroDivisionError
raise IntegralDoesntExistError, 'Divide by zero'
end
(result + result1) / 2.0
end
def self.check_value(value)
if value.nan?
raise IntegralDoesntExistError, 'We have not-a-number result :('
end
if value == Float::INFINITY
raise IntegralDoesntExistError, 'We have infinity :('
end
end
##
# Computes the sum of n rectangles on a segment
# of length dx at points of the form a + i * dx
# @param [Numeric] a - first division point
# @param [Array] i_n number of divisions
# @param [Numeric] dx - length of integration segment
# @param [Block] block - integrated function as Block
def self.amount_calculation(a, i_n, dx, &block)
result = 0
while i_n[0] < i_n[1]
result += block.call(a + i_n[0] * dx)
i_n[0] += 1
end
result * dx
end
end
end