Roots of a cubic polynomial: Difference between revisions

Content added Content deleted
m (→‎{{header|J}}: punctuation)
Line 96: Line 96:


We could instead use [[j:Vocabulary/LAPACK|lapack]]. The lapack approach would be faster and more stable for very large matrices, but that's not an issue here. Also, the approach we use here better fits the current title of this task.
We could instead use [[j:Vocabulary/LAPACK|lapack]]. The lapack approach would be faster and more stable for very large matrices, but that's not an issue here. Also, the approach we use here better fits the current title of this task.

=={{header|jq}}==
'''rootPoly is adapted from [[#Wren|Wren]]'''

The first few functions are taken from
[[Polynomial_long_division#jq]] but are reproduced here for clarity.

In the following, a method for computing the roots of a polynomial
of arbitrary degree is used, and so no restriction to polynomials of
degree 3 is imposed.

Note that the polynomial of degree n, P = SIGMA (c[i] * x^i), is
represented by the array [ c[0] ... c[n] ], and the estimated error
in a root, r, is computed using the formula (dY / (dP/dX)(r)) except
when the denominator or the expression as a whole is computed as 0,
in which case a very small value (almost indistinguishable from 0.0)
is used.

<syntaxhighlight lang="jq">
# Input: a possibly empty numeric array [c0, ... ]
# Emit the canonical form of the polynomial: SIGMA c[i] * x^i
def canonical:
if length == 0 then [0]
elif .[-1] == 0 then .[:-1]|canonical
else .
end;

# string representation
def poly2s: "Polynomial(\(join(",")))";

# Polynomial division
# Output [ quotient, remainder]
def divrem($divisor):
($divisor|canonical) as $divisor
| { curr: canonical}
| .base = ((.curr|length) - ($divisor|length))
| until( .base < 0;
(.curr[-1] / $divisor[-1]) as $res
| .result += [$res]
| .curr |= .[0:-1]
| reduce range (0;$divisor|length-1) as $i (.;
.curr[.base + $i] += (- $res * $divisor[$i]) )
| .base += -1
)
| [(.result | reverse), (.curr | canonical)] ;

# Evaluate a polynomial at $x
# Input: the array representation of a polynomial
def evalPoly($x):
length as $c
| . as $coeffs
| reduce range(1;length+1) as $i (0; . * $x + $coeffs[$c - $i]) ;

# Differentiate a polynomial
# Input: the array representation of the polynomial to be differentiated
def diffPoly:
(length - 1) as $c
| if $c == 0 then [0]
else . as $coefs
| reduce range(0; $c) as $i ([]; .[$i] = ($i+1) * $coefs[$i+1])
end;

# Emit a stream
# No check is made that the input represents a quadratic polynomial
def quadraticRoots:
. as $coefs
| ($coefs[1]*$coefs[1] - 4*$coefs[0]*$coefs[2]) as $d
| select($d >= 0)
| ($d|sqrt) as $s
| -($s + $coefs[1]), ($s - $coefs[1])
| . / (2 * $coefs[2]);

# Attempt to find a real root of a polynomial using Newton's method from
# an initial guess, a given tolerance, a maximum number of iterations
# and a given multiplicity (usually 1).
# If a root is found, it is returned, otherwise 'null' is returned.
# If the root is near an integer, check if the integer is in fact the root.
# If the polynomical degree is 0, return null.
# Input: [c0, c1 ... ]
def rootPoly($guess; $tol; $maxIter; $mult):
. as $coefs
| (length - 1) as $deg
| if $deg == 0 then null
elif $deg == 1 then -$coefs[0]/$coefs[1]
elif $deg == 2 then first(quadraticRoots)
elif evalPoly(0) == 0 then 0
else 0.001 as $eps
| diffPoly as $deriv
| { x0: $guess, iter: 1, return: null }
| until(.return;
.x0 as $x0
| ($deriv|evalPoly($x0)) as $den
| if $den == 0
then .x0 |= (if . >= 0 then . + $eps else . - $eps end)
else ($coefs|evalPoly($x0)) as $num
| (.x0 - ($num/$den) * $mult) as $x1
| if (($x1 - .x0)|length) <= $tol # abs
then ($x1 | round) as $r
| if (($r - $x1)|length) <= $eps and ($coefs|evalPoly($r)) == 0
then .return = $r
else .return = $x1
end
else .x0 = $x1
end
| if .iter == $maxIter then .return = true
else .iter += 1
end
end)
| if .return != true then .return
else (.x0|round) as $x0
| if ($coefs | evalPoly($x0)) == 0 then $x0
else null
end
end
end;

# Convenience versions of rootPoly/4:
def rootPoly($guess):
rootPoly($guess; 1e-15; 100; 1);
def rootPoly:
rootPoly(0.001; 1e-15; 100; 1);

# Emit a stream of real roots
def roots:
if length==3 then quadraticRoots
else rootPoly as $root
| select($root)
| $root,
# divide by (x - $root)
( divrem( [- $root, 1] ) as [$div, $rem]
| $div
| roots )
end ;

# Given $root is an estimated root of the input polynomial,
# estimate the error dx from deriv = (dY / $root)
# except that if $root or deiv is 0, then use a very small value:
# since 1E-324 == 1E-325 but 1E-323 != 1E-324, we choose 1E-323
def estimatedDeltaX($root):
1E-323 as $tiny
| evalPoly($root) as $dY
| (diffPoly | evalPoly($root)) as $deriv
| if $deriv == 0 then $tiny
else (($dY / $deriv) | length) as $dx
| if $dx == 0 then $tiny
else $dx
end
end ;

def roots_with_errors:
rootPoly as $root
| select($root)
| estimatedDeltaX($root) as $dx
| [$root, $dx],
# divide by (x - $root)
( divrem( [- $root, 1] ) as [$div, $rem]
| $div
| roots_with_errors );

def illustration($text; $poly):
$text,
($poly | roots_with_errors),
"";

"Each root and corresponding estimated error is presented in an array.",
illustration("X^3 = 1"; [-1, 0, 0, 1] ),
illustration("X^3 - 4X^2 - 27X + 90 = 0"; # [-5, 3 ,6]
[90, -27, -4, 1] ),
illustration("(X - 1)^3"; [-1,3,-3,1] ),
illustration("X^2 = 2"; [-2,0,1] ),
illustration("X^3 = 2"; [-2,0,0,1] )
</syntaxhighlight>
{{out}}
<pre>
Each root and corresponding estimated error is presented in an array.
X^3 = 1
[1,1e-323]

X^3 - 4X^2 - 27X + 90 = 0
[3,1e-323]
[-5,1e-323]
[6,1e-323]

(X - 1) ^3
[1,1e-323]
[1,1e-323]
[1,1e-323]

X^2 = 2
[-1.4142135623730951,1.570092458683775e-16]
[1.4142135623730951,1e-323]

X^3 = 2
[1.2599210498948732,1e-323]
</pre>



=={{header|Julia}}==
=={{header|Julia}}==