Formal power series: Difference between revisions

Added a Ruby implementation.
m (→‎{{header|Phix}}: added syntax colouring, marked p2js compatible)
(Added a Ruby implementation.)
Line 2,890:
cos(x) ≈ 1 - 1/2∙x² + 1/24∙x⁴ - 1/720∙x⁶ + 1/40320∙x⁸ - 1/3628800∙x¹⁰
tan(x) ≈ 0 + 1/1∙x¹ + 1/3∙x³ + 2/15∙x⁵ + 17/315∙x⁷ + 62/2835∙x⁹</pre>
=={{header|Ruby}}==
{{works with|Ruby|2.3}}
'''Implementation'''
<br />
This uses Ruby's Lazy Enumerators to generate the indefinite length sequences.
<br />
Implements the '<code>+</code>', '<code>-</code>', '<code>*</code>', and
'<code>/</code>' operators, and the <code>deriv()</code> and
<code>integ()</code> methods. Also implements coercion and conversion with
standard Numeric types, and formatting as a string for display. Provides for
evaluating the series at a point.
<br />
Inspirations from the Schame and Raku implementations.
<lang ruby># Class implementing the Formal Power Series type.
 
class Fps
 
# Initialize the FPS instance.
# When nothing specified, all coefficients are 0.
# When const: specifies n, all coefficients are n.
# When delta: specifies n, a[0] = n, then all higher coefficients are zero.
# When iota: specifies n, coefficients are consecutive integers, beginning with a[0] = n.
# When init: specifies an array, coefficients are the array elements, padded with zeroes.
# When enum: specifies a lazy enumerator, that is used for the internal coefficients enum.
def initialize(const: nil, delta: nil, iota: nil, init: nil, enum: nil)
# Create (or save) the specified coefficient enumerator.
case
when const
@coeffenum = make_const(const)
when delta
@coeffenum = make_delta(delta)
when iota
@coeffenum = make_iota(iota)
when init
@coeffenum = make_init(init)
when enum
@coeffenum = enum
else
@coeffenum = make_const(0)
end
# Extend the coefficient enumerator instance with an element accessor.
@coeffenum.instance_eval do
def [](index)
self.drop(index).first
end
end
end
 
# Return the coefficient at the given index.
def [](index)
@coeffenum.drop(index).first
end
 
# Return sum: this FPS plus the given FPS.
def +(other)
other = convert(other)
Fps.new(enum:
Enumerator.new do |yielder, inx: 0|
loop do
yielder.yield(@coeffenum[inx] + other[inx])
inx += 1
end
end.lazy)
end
 
# Return difference: this FPS minus the given FPS.
def -(other)
other = convert(other)
Fps.new(enum:
Enumerator.new do |yielder, inx: 0|
loop do
yielder.yield(@coeffenum[inx] - other[inx])
inx += 1
end
end.lazy)
end
 
# Return product: this FPS multiplied by the given FPS.
def *(other)
other = convert(other)
Fps.new(enum:
Enumerator.new do |yielder, inx: 0|
loop do
coeff = (0..inx).reduce(0) { |sum, i| sum + (@coeffenum[i] * other[inx - i]) }
yielder.yield(coeff)
inx += 1
end
end.lazy)
end
 
# Return quotient: this FPS divided by the given FPS.
def /(other)
other = convert(other)
Fps.new(enum:
Enumerator.new do |yielder, inx: 1|
coeffs = [ Rational(@coeffenum[0], other[0]) ]
yielder.yield(coeffs[-1])
loop do
coeffs <<
Rational(
@coeffenum[inx] -
(1..inx).reduce(0) { |sum, i| sum + (other[i] * coeffs[inx - i]) },
other[0])
yielder.yield(coeffs[-1])
inx += 1
end
end.lazy)
end
 
# Return the derivative of this FPS.
def deriv()
Fps.new(enum:
Enumerator.new do |yielder, inx: 0|
iota = Fps.new(iota: 1)
loop do
yielder.yield(@coeffenum[inx + 1] * iota[inx])
inx += 1
end
end.lazy)
end
 
# Return the integral of this FPS.
def integ()
Fps.new(enum:
Enumerator.new do |yielder, inx: 0|
iota = Fps.new(iota: 1)
yielder.yield(Rational(0, 1))
loop do
yielder.yield(Rational(@coeffenum[inx], iota[inx]))
inx += 1
end
end.lazy)
end
 
# Assign a new value to an existing FPS instance.
def assign(other)
other = convert(other)
@coeffenum = other.get_enum
end
 
# Coerce a Numeric into an FPS instance.
def coerce(other)
if other.kind_of?(Numeric)
[ Fps.new(delta: other), self ]
else
raise TypeError 'non-numeric can\'t be coerced into FPS type'
end
end
 
# Convert to Integer. (Truncates to 0th coefficient.)
def to_i()
@coeffenum[0].to_i
end
 
# Convert to Float. (Truncates to 0th coefficient.)
def to_f()
@coeffenum[0].to_f
end
 
# Convert to Rational. (Truncates to 0th coefficient.)
def to_r()
@coeffenum[0].to_r
end
 
# Convert to String the first count terms of the FPS.
def to_s(count = 0)
if count <= 0
super()
else
retstr = ''
count.times do |inx|
coeff = (@coeffenum[inx].to_r.denominator == 1) ? @coeffenum[inx].to_i : @coeffenum[inx]
if !(coeff.zero?)
prefix = (retstr != '') ? ' ' : ''
coeffstr =
((coeff.abs == 1) && (inx != 0)) ? '' : "#{coeff.abs.to_s}#{(inx == 0) ? '' : '*'}"
suffix = (inx == 0) ? '' : (inx == 1) ? 'x' : "x^#{inx}"
if coeff < 0
prefix << ((retstr != '') ? '- ' : '-')
else
prefix << ((retstr != '') ? '+ ' : '')
end
retstr << "#{prefix}#{coeffstr}#{suffix}"
end
end
(retstr == '') ? '0' : retstr
end
end
 
# Evaluate this FPS at the given x value to the given count of terms.
def eval(x, count)
@coeffenum.first(count).each_with_index.reduce(0) { |sum, (coeff, inx) | sum + coeff * x**inx }
end
 
# Forward method calls to the @coeffenum instance.
def method_missing(name, *args, &block)
@coeffenum.send(name, *args, &block)
end
 
# Forward respond_to? to the @coeffenum instance.
def respond_to_missing?(name, incl_priv)
@coeffenum.respond_to?(name, incl_priv)
end
 
protected
 
# Return reference to the underlying coefficient enumeration.
def get_enum()
@coeffenum
end
 
private
 
# Create a "const" lazy enumerator with the given n.
# All elements are n.
def make_const(n)
Enumerator.new do |yielder|
loop { yielder.yield(n) }
end.lazy
end
 
# Create a "delta" lazy enumerator with the given n.
# First element is n, then all subsequent elements are zero.
def make_delta(n)
Enumerator.new do |yielder|
yielder.yield(n)
loop { yielder.yield(0) }
end.lazy
end
 
# Create an "iota" lazy enumerator with the given n.
# Elements are consecutive integers, beginning with n.
def make_iota(n)
Enumerator.new do |yielder, i: n|
loop { yielder.yield(i); i += 1 }
end.lazy
end
 
# Create an "init" lazy enumerator with the given array.
# Elements are the array elements, padded with zeroes.
def make_init(array)
Enumerator.new do |yielder, inx: -1|
loop { yielder.yield((inx < (array.length - 1)) ? array[inx += 1] : 0) }
end.lazy
end
 
# Convert a Numeric to an FPS instance, if needed.
def convert(other)
if other.kind_of?(Fps)
other
else
if other.kind_of?(Numeric)
Fps.new(delta: other)
else
raise TypeError 'non-numeric can\'t be converted to FPS type'
end
end
end
 
end</lang>
'''Testing'''
<lang ruby>puts
puts('FPS Creation:')
a = Fps.new(iota: 3)
puts("iota: a = #{a.first(10)}")
b = Fps.new(init: [12, 24, 36, 48])
puts("init: b = #{b.first(10)}")
 
puts
puts('FPS Arithmetic:')
puts("(a + b) = #{(a + b).first(10)}")
puts("(a - b) = #{(a - b).first(10)}")
puts("(a * b) = #{(a * b).first(10)}")
puts("(a / b) = #{(a / b).first(10)}")
puts("((a + b) - b) = #{((a + b) - b).first(10)}")
puts("((a / b) * b) = #{((a / b) * b).first(10)}")
 
puts
puts('FPS w/ Other Numerics:')
puts("(a + 3) = #{(a + 3).first(10)}")
puts("(a - 3) = #{(a - 3).first(10)}")
puts("(a * 3) = #{(a * 3).first(10)}")
puts("(a / 3) = #{(a / 3).first(10)}")
puts("(3 + a) = #{(3 + a).first(10)}")
puts("(3 - a) = #{(3 - a).first(10)}")
puts("(3 * a) = #{(3 * a).first(10)}")
puts("(3 / a) = #{(3 / a).first(10)}")
puts("(a + 4.4) = #{(a + 4.5).first(10)}")
puts("(a + Rational(11, 3)) = #{(a + Rational(11, 3)).first(10)}")
puts("(4.4 + a) = #{(4.5 + a).first(10)}")
puts("(Rational(11, 3) + a) = #{(Rational(11, 3) + a).first(10)}")
 
puts
puts('FPS Differentiation and Integration:')
puts("b.deriv = #{b.deriv.first(10)}")
puts("b.integ = #{b.integ.first(10)}")
 
puts
puts('Define sin(x) and cos(x) FPSs in terms of each other:')
fpssin = Fps.new
fpscos = 1 - fpssin.integ
fpssin.assign(fpscos.integ)
puts("fpssin = #{fpssin.first(10)}")
puts("fpscos = #{fpscos.first(10)}")
 
puts
puts('Display sin(x) and cos(x) FPSs as strings:')
puts("sin(x) = #{fpssin.to_s(10)}")
puts("cos(x) = #{fpscos.to_s(10)}")
 
puts
puts('Define tan(x) FPS as sin(x) / cos(x) from above:')
fpstan = fpssin / fpscos
puts("tan(x) = #{fpstan.to_s(10)}")
 
puts
puts('Compute sin^2(x)+cos^2(x) FPS from above:')
puts("sin^2(x)+cos^2(x) = #{((fpssin * fpssin) + (fpscos * fpscos)).to_s(5)}")
 
puts
puts('Define exp(x) in terms of its own integral:')
fpsexp = Fps.new
fpsexp.assign(1 + fpsexp.integ)
puts("exp(x) = #{fpsexp.to_s(10)}")
 
puts
puts('Evaluate the above at a few points using a few terms:')
puts("sin(0) = #{fpssin.eval(0, 8).to_f}")
puts("cos(0) = #{fpscos.eval(0, 8).to_f}")
puts("sin(pi/2) = #{fpssin.eval(Math::PI / 2, 16).to_f}")
puts("cos(pi/2) = #{fpscos.eval(Math::PI / 2, 16).to_f}")
puts("sin(pi) = #{fpssin.eval(Math::PI, 16).to_f}")
puts("cos(pi) = #{fpscos.eval(Math::PI, 16).to_f}")
puts("tan(0) = #{fpstan.eval(0, 8).to_f}")
puts("exp(1) = #{fpsexp.eval(1, 14).to_f}")</lang>
{{out}}
<pre>FPS Creation:
iota: a = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
init: b = [12, 24, 36, 48, 0, 0, 0, 0, 0, 0]
 
FPS Arithmetic:
(a + b) = [15, 28, 41, 54, 7, 8, 9, 10, 11, 12]
(a - b) = [-9, -20, -31, -42, 7, 8, 9, 10, 11, 12]
(a * b) = [36, 120, 264, 480, 600, 720, 840, 960, 1080, 1200]
(a / b) = [(1/4), (-1/6), (0/1), (0/1), (5/4), (-11/6), (2/3), (0/1), (25/4), (-85/6)]
((a + b) - b) = [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
((a / b) * b) = [(3/1), (4/1), (5/1), (6/1), (7/1), (8/1), (9/1), (10/1), (11/1), (12/1)]
 
FPS w/ Other Numerics:
(a + 3) = [6, 4, 5, 6, 7, 8, 9, 10, 11, 12]
(a - 3) = [0, 4, 5, 6, 7, 8, 9, 10, 11, 12]
(a * 3) = [9, 12, 15, 18, 21, 24, 27, 30, 33, 36]
(a / 3) = [(1/1), (4/3), (5/3), (2/1), (7/3), (8/3), (3/1), (10/3), (11/3), (4/1)]
(3 + a) = [6, 4, 5, 6, 7, 8, 9, 10, 11, 12]
(3 - a) = [0, -4, -5, -6, -7, -8, -9, -10, -11, -12]
(3 * a) = [9, 12, 15, 18, 21, 24, 27, 30, 33, 36]
(3 / a) = [(1/1), (-4/3), (1/9), (2/27), (4/81), (8/243), (16/729), (32/2187), (64/6561), (128/19683)]
(a + 4.4) = [7.5, 4, 5, 6, 7, 8, 9, 10, 11, 12]
(a + Rational(11, 3)) = [(20/3), 4, 5, 6, 7, 8, 9, 10, 11, 12]
(4.4 + a) = [7.5, 4, 5, 6, 7, 8, 9, 10, 11, 12]
(Rational(11, 3) + a) = [(20/3), 4, 5, 6, 7, 8, 9, 10, 11, 12]
 
FPS Differentiation and Integration:
b.deriv = [24, 72, 144, 0, 0, 0, 0, 0, 0, 0]
b.integ = [(0/1), (12/1), (12/1), (12/1), (12/1), (0/1), (0/1), (0/1), (0/1), (0/1)]
 
Define sin(x) and cos(x) FPSs in terms of each other:
fpssin = [(0/1), (1/1), (0/1), (-1/6), (0/1), (1/120), (0/1), (-1/5040), (0/1), (1/362880)]
fpscos = [(1/1), (0/1), (-1/2), (0/1), (1/24), (0/1), (-1/720), (0/1), (1/40320), (0/1)]
 
Display sin(x) and cos(x) FPSs as strings:
sin(x) = x - 1/6*x^3 + 1/120*x^5 - 1/5040*x^7 + 1/362880*x^9
cos(x) = 1 - 1/2*x^2 + 1/24*x^4 - 1/720*x^6 + 1/40320*x^8
 
Define tan(x) FPS as sin(x) / cos(x) from above:
tan(x) = x + 1/3*x^3 + 2/15*x^5 + 17/315*x^7 + 62/2835*x^9
 
Compute sin^2(x)+cos^2(x) FPS from above:
sin^2(x)+cos^2(x) = 1
 
Define exp(x) in terms of its own integral:
exp(x) = 1 + x + 1/2*x^2 + 1/6*x^3 + 1/24*x^4 + 1/120*x^5 + 1/720*x^6 + 1/5040*x^7 + 1/40320*x^8 + 1/362880*x^9
 
Evaluate the above at a few points using a few terms:
sin(0) = 0.0
cos(0) = 1.0
sin(pi/2) = 0.9999999999939768
cos(pi/2) = -6.513362355903657e-11
sin(pi) = -7.727858894168152e-07
cos(pi) = -1.0000041678091434
tan(0) = 0.0
exp(1) = 2.718281828446759</pre>
 
=={{header|Scheme}}==