stick 1.3.2 → 1.3.3
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.
- data/CHANGES +3 -4
- data/README +1 -1
- data/lib/stick/constants/cgs.rb +112 -110
- data/lib/stick/constants/mks.rb +6 -4
- data/lib/stick/constants/number.rb +3 -2
- data/lib/stick/constants/typeless_cgs.rb +105 -106
- data/lib/stick/constants/typeless_mks.rb +106 -107
- data/lib/stick/currency.rb +2 -0
- data/lib/stick/mapcar.rb +36 -23
- data/lib/stick/matrix.rb +10 -395
- data/lib/stick/matrix/core.rb +1408 -0
- data/lib/stick/matrix/exception.rb +23 -0
- data/lib/stick/matrix/givens.rb +59 -0
- data/lib/stick/matrix/hessenberg.rb +63 -0
- data/lib/stick/matrix/householder.rb +106 -0
- data/lib/stick/matrix/jacobi.rb +106 -0
- data/lib/stick/matrix/lu.rb +60 -0
- data/lib/stick/quaternion.rb +10 -6
- data/lib/stick/units.rb +2 -0
- data/lib/stick/units/base.rb +75 -72
- data/lib/stick/units/currency.rb +8 -8
- data/lib/stick/units/loaders.rb +3 -2
- data/lib/stick/units/units.rb +2 -0
- data/lib/stick/vector.rb +20 -0
- data/meta/MANIFEST +23 -3
- data/meta/stick.roll +1 -1
- data/task/tests/solo +293 -0
- data/test/spec_matrix.rb +3 -0
- data/test/test_constants.rb +4 -0
- data/test/test_currency.rb +2 -2
- data/test/test_matrix.rb +7 -1
- data/test/test_units.rb +2 -2
- metadata +15 -2
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'e2mmap'
|
2
|
+
|
3
|
+
module Stick
|
4
|
+
|
5
|
+
class Matrix
|
6
|
+
|
7
|
+
module Exceptions # :nodoc:
|
8
|
+
extend Exception2MessageMapper
|
9
|
+
|
10
|
+
def_e2message(TypeError, "wrong argument type %s (expected %s)")
|
11
|
+
def_e2message(ArgumentError, "Wrong # of arguments(%d for %d)")
|
12
|
+
|
13
|
+
def_exception("ErrDimensionMismatch", "\#{self.name} dimension mismatch")
|
14
|
+
def_exception("ErrNotRegular", "Not Regular Matrix")
|
15
|
+
def_exception("ErrOperationNotDefined", "This operation(%s) can\\'t defined")
|
16
|
+
end
|
17
|
+
|
18
|
+
# extend Exception2MessageMapper
|
19
|
+
include Exceptions
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Stick
|
2
|
+
|
3
|
+
class Matrix
|
4
|
+
|
5
|
+
module Givens
|
6
|
+
|
7
|
+
# Returns the values "c and s" of a Given rotation
|
8
|
+
# MC, Golub, pg 216, Alghorithm 5.1.3
|
9
|
+
|
10
|
+
def Givens.givens(a, b)
|
11
|
+
if b == 0
|
12
|
+
c = 0; s = 0
|
13
|
+
else
|
14
|
+
if b.abs > a.abs
|
15
|
+
tau = Float(-a)/b; s = 1/Math.sqrt(1+tau**2); c = s * tau
|
16
|
+
else
|
17
|
+
tau = Float(-b)/a; c = 1/Math.sqrt(1+tau**2); s = c * tau
|
18
|
+
end
|
19
|
+
end
|
20
|
+
return c, s
|
21
|
+
end
|
22
|
+
|
23
|
+
# a QR factorization using Givens rotation
|
24
|
+
# Computes the upper triangular matrix R and the orthogonal matrix Q
|
25
|
+
# where Q^t A = R (MC, Golub, p227 algorithm 5.2.2)
|
26
|
+
|
27
|
+
def Givens.QR(mat)
|
28
|
+
r = mat.clone
|
29
|
+
m = r.row_size
|
30
|
+
n = r.column_size
|
31
|
+
q = Matrix.I(m)
|
32
|
+
n.times{|j|
|
33
|
+
m-1.downto(j+1){|i|
|
34
|
+
c, s = givens(r[i - 1, j], r[i, j])
|
35
|
+
qt = Matrix.I(m); qt[i-1..i, i-1..i] = Matrix[[c, s],[-s, c]]
|
36
|
+
q *= qt
|
37
|
+
r[i-1..i, j..n-1] = Matrix[[c, -s],[s, c]] * r[i-1..i, j..n-1]}}
|
38
|
+
return r, q
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
# Returns the upper triunghiular matrix R of a Givens QR factorization
|
44
|
+
|
45
|
+
def givensR
|
46
|
+
Givens.QR(self)[0]
|
47
|
+
end
|
48
|
+
|
49
|
+
# Returns the orthogonal matrix Q of Givens QR factorization.
|
50
|
+
# Q = G_1 * ... * G_t where G_j is the j'th Givens rotation
|
51
|
+
# and 't' is the total number of rotations
|
52
|
+
|
53
|
+
def givensQ
|
54
|
+
Givens.QR(self)[1]
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
require 'stick/matrix/householder'
|
2
|
+
|
3
|
+
module Stick
|
4
|
+
|
5
|
+
class Matrix
|
6
|
+
|
7
|
+
module Hessenberg
|
8
|
+
|
9
|
+
# The matrix must be an upper R^(n x n) Hessenberg matrix
|
10
|
+
|
11
|
+
def Hessenberg.QR(mat)
|
12
|
+
r = mat.clone
|
13
|
+
n = r.row_size
|
14
|
+
q = Matrix.I(n)
|
15
|
+
for j in (0...n-1)
|
16
|
+
c, s = Givens.givens(r[j,j], r[j+1, j])
|
17
|
+
cs = Matrix[[c, s], [-s, c]]
|
18
|
+
q *= Matrix.diag(Matrix.I(j), cs, Matrix.I(n - j - 2))
|
19
|
+
r[j..j+1, j..n-1] = cs.t * r[j..j+1, j..n-1]
|
20
|
+
end
|
21
|
+
return q, r
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Returns the orthogonal matrix Q of Hessenberg QR factorization
|
26
|
+
# Q = G_1 *...* G_(n-1) where G_j is the Givens rotation G_j = G(j, j+1, omega_j)
|
27
|
+
|
28
|
+
def hessenbergQ
|
29
|
+
Hessenberg.QR(self)[0]
|
30
|
+
end
|
31
|
+
|
32
|
+
# Returns the upper triunghiular matrix R of a Hessenberg QR factorization
|
33
|
+
|
34
|
+
def hessenbergR
|
35
|
+
Hessenberg.QR(self)[1]
|
36
|
+
end
|
37
|
+
|
38
|
+
# Return an upper Hessenberg matrix obtained with Householder reduction to Hessenberg Form algorithm
|
39
|
+
|
40
|
+
def hessenberg_form_H
|
41
|
+
Householder.toHessenberg(self)[0]
|
42
|
+
end
|
43
|
+
|
44
|
+
# The real Schur decomposition.
|
45
|
+
# The eigenvalues are aproximated in diagonal elements of the real Schur decomposition matrix
|
46
|
+
|
47
|
+
def realSchur(eps = 1.0e-10, steps = 100)
|
48
|
+
h = self.hessenberg_form_H
|
49
|
+
h1 = Matrix[]
|
50
|
+
i = 0
|
51
|
+
loop do
|
52
|
+
h1 = h.hessenbergR * h.hessenbergQ
|
53
|
+
break if Matrix.diag_in_delta?(h1, h, eps) or steps <= 0
|
54
|
+
h = h1.clone
|
55
|
+
steps -= 1
|
56
|
+
i += 1
|
57
|
+
end
|
58
|
+
h1
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Stick
|
2
|
+
|
3
|
+
class Matrix
|
4
|
+
|
5
|
+
module Householder
|
6
|
+
|
7
|
+
# a QR factorization that uses Householder transformation
|
8
|
+
# Q^T * A = R
|
9
|
+
# MC, Golub & van Loan, pg 224, 5.2.1 Householder QR
|
10
|
+
|
11
|
+
def Householder.QR(mat)
|
12
|
+
h = []
|
13
|
+
a = mat.clone
|
14
|
+
m = a.row_size
|
15
|
+
n = a.column_size
|
16
|
+
n.times{|j|
|
17
|
+
v, beta = a[j..m - 1, j].house
|
18
|
+
|
19
|
+
h[j] = Matrix.diag(Matrix.I(j), Matrix.I(m-j)- beta * (v * v.t))
|
20
|
+
|
21
|
+
a[j..m-1, j..n-1] = (Matrix.I(m-j) - beta * (v * v.t)) * a[j..m-1, j..n-1]
|
22
|
+
a[(j+1)..m-1,j] = v[2..(m-j)] if j < m - 1 }
|
23
|
+
h
|
24
|
+
end
|
25
|
+
|
26
|
+
# From the essential part of Householder vector
|
27
|
+
# it returns the coresponding upper(U_j)/lower(V_j) matrix
|
28
|
+
|
29
|
+
def Householder.bidiagUV(essential, dim, beta)
|
30
|
+
v = Vector.concat(Vector[1], essential)
|
31
|
+
dimv = v.size
|
32
|
+
Matrix.diag(Matrix.I(dim - dimv), Matrix.I(dimv) - beta * (v * v.t) )
|
33
|
+
end
|
34
|
+
|
35
|
+
# Householder Bidiagonalization algorithm. MC, Golub, pg 252, Algorithm 5.4.2
|
36
|
+
# Returns the matrices U_B and V_B such that: U_B^T * A * V_B = B,
|
37
|
+
# where B is upper bidiagonal.
|
38
|
+
|
39
|
+
def Householder.bidiag(mat)
|
40
|
+
a = mat.clone
|
41
|
+
m = a.row_size
|
42
|
+
n = a.column_size
|
43
|
+
ub = Matrix.I(m)
|
44
|
+
vb = Matrix.I(n)
|
45
|
+
n.times{|j|
|
46
|
+
v, beta = a[j..m-1,j].house
|
47
|
+
a[j..m-1, j..n-1] = (Matrix.I(m-j) - beta * (v * v.t)) * a[j..m-1, j..n-1]
|
48
|
+
a[j+1..m-1, j] = v[1..(m-j-1)]
|
49
|
+
ub *= bidiagUV(a[j+1..m-1,j], m, beta) #Ub = U_1 * U_2 * ... * U_n
|
50
|
+
if j < n - 2
|
51
|
+
v, beta = (a[j, j+1..n-1]).house
|
52
|
+
a[j..m-1, j+1..n-1] = a[j..m-1, j+1..n-1] * (Matrix.I(n-j-1) - beta * (v * v.t))
|
53
|
+
a[j, j+2..n-1] = v[1..n-j-2]
|
54
|
+
vb *= bidiagUV(a[j, j+2..n-1], n, beta) #Vb = V_1 * U_2 * ... * V_n-2
|
55
|
+
end }
|
56
|
+
return ub, vb
|
57
|
+
end
|
58
|
+
|
59
|
+
# Householder Reduction to Hessenberg Form
|
60
|
+
|
61
|
+
def Householder.toHessenberg(mat)
|
62
|
+
h = mat.clone
|
63
|
+
n = h.row_size
|
64
|
+
u0 = Matrix.I(n)
|
65
|
+
for k in (0...n - 2)
|
66
|
+
v, beta = h[k+1..n-1, k].house #the householder matrice part
|
67
|
+
houseV = Matrix.I(n-k-1) - beta * (v * v.t)
|
68
|
+
u0 *= Matrix.diag(Matrix.I(k+1), houseV)
|
69
|
+
h[k+1..n-1, k..n-1] = houseV * h[k+1..n-1, k..n-1]
|
70
|
+
h[0..n-1, k+1..n-1] = h[0..n-1, k+1..n-1] * houseV
|
71
|
+
end
|
72
|
+
return h, u0
|
73
|
+
end
|
74
|
+
|
75
|
+
end #end of Householder module
|
76
|
+
|
77
|
+
# Returns the upper bidiagonal matrix obtained with Householder Bidiagonalization algorithm
|
78
|
+
|
79
|
+
def bidiagonal
|
80
|
+
ub, vb = Householder.bidiag(self)
|
81
|
+
ub.t * self * vb
|
82
|
+
end
|
83
|
+
|
84
|
+
# Returns the orthogonal matrix Q of Householder QR factorization
|
85
|
+
# where Q = H_1 * H_2 * H_3 * ... * H_n,
|
86
|
+
|
87
|
+
def houseQ
|
88
|
+
h = Householder.QR(self)
|
89
|
+
q = h[0]
|
90
|
+
(1...h.size).each{|i| q *= h[i]}
|
91
|
+
q
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the matrix R of Householder QR factorization
|
95
|
+
# R = H_n * H_n-1 * ... * H_1 * A is an upper triangular matrix
|
96
|
+
|
97
|
+
def houseR
|
98
|
+
h = Householder.QR(self)
|
99
|
+
r = self.clone
|
100
|
+
h.size.times{|i| r = h[i] * r}
|
101
|
+
r
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,106 @@
|
|
1
|
+
module Stick
|
2
|
+
|
3
|
+
class Matrix
|
4
|
+
|
5
|
+
module Jacobi
|
6
|
+
|
7
|
+
# Returns the nurm of the off-diagonal element
|
8
|
+
|
9
|
+
def Jacobi.off(a)
|
10
|
+
n = a.row_size
|
11
|
+
sum = 0
|
12
|
+
n.times{|i| n.times{|j| sum += a[i, j]**2 if j != i}}
|
13
|
+
Math.sqrt(sum)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the index pair (p, q) with 1<= p < q <= n and A[p, q] is the maximum in absolute value
|
17
|
+
|
18
|
+
def Jacobi.max(a)
|
19
|
+
n = a.row_size
|
20
|
+
max = 0
|
21
|
+
p = 0
|
22
|
+
q = 0
|
23
|
+
n.times{|i|
|
24
|
+
((i+1)...n).each{|j|
|
25
|
+
val = a[i, j].abs
|
26
|
+
if val > max
|
27
|
+
max = val
|
28
|
+
p = i
|
29
|
+
q = j
|
30
|
+
end }}
|
31
|
+
return p, q
|
32
|
+
end
|
33
|
+
|
34
|
+
# Compute the cosine-sine pair (c, s) for the element A[p, q]
|
35
|
+
|
36
|
+
def Jacobi.sym_schur2(a, p, q)
|
37
|
+
if a[p, q] != 0
|
38
|
+
tau = Float(a[q, q] - a[p, p])/(2 * a[p, q])
|
39
|
+
if tau >= 0
|
40
|
+
t = 1./(tau + Math.sqrt(1 + tau ** 2))
|
41
|
+
else
|
42
|
+
t = -1./(-tau + Math.sqrt(1 + tau ** 2))
|
43
|
+
end
|
44
|
+
c = 1./Math.sqrt(1 + t ** 2)
|
45
|
+
s = t * c
|
46
|
+
else
|
47
|
+
c = 1
|
48
|
+
s = 0
|
49
|
+
end
|
50
|
+
return c, s
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns the Jacobi rotation matrix
|
54
|
+
|
55
|
+
def Jacobi.J(p, q, c, s, n)
|
56
|
+
j = Matrix.I(n)
|
57
|
+
j[p,p] = c; j[p, q] = s
|
58
|
+
j[q,p] = -s; j[q, q] = c
|
59
|
+
j
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
# Classical Jacobi 8.4.3 Golub & van Loan
|
65
|
+
|
66
|
+
def cJacobi(tol = 1.0e-10)
|
67
|
+
a = self.clone
|
68
|
+
n = row_size
|
69
|
+
v = Matrix.I(n)
|
70
|
+
eps = tol * a.normF
|
71
|
+
while Jacobi.off(a) > eps
|
72
|
+
p, q = Jacobi.max(a)
|
73
|
+
c, s = Jacobi.sym_schur2(a, p, q)
|
74
|
+
#print "\np:#{p} q:#{q} c:#{c} s:#{s}\n"
|
75
|
+
j = Jacobi.J(p, q, c, s, n)
|
76
|
+
a = j.t * a * j
|
77
|
+
v = v * j
|
78
|
+
end
|
79
|
+
return a, v
|
80
|
+
end
|
81
|
+
|
82
|
+
# Returns the aproximation matrix computed with Classical Jacobi algorithm.
|
83
|
+
# The aproximate eigenvalues values are in the diagonal of the matrix A.
|
84
|
+
|
85
|
+
def cJacobiA(tol = 1.0e-10)
|
86
|
+
cJacobi(tol)[0]
|
87
|
+
end
|
88
|
+
|
89
|
+
# Returns a Vector with the eigenvalues aproximated values.
|
90
|
+
# The eigenvalues are computed with the Classic Jacobi Algorithm.
|
91
|
+
|
92
|
+
def eigenvaluesJacobi
|
93
|
+
a = cJacobiA
|
94
|
+
Vector[*(0...row_size).collect{|i| a[i, i]}]
|
95
|
+
end
|
96
|
+
|
97
|
+
# Returns the orthogonal matrix obtained with the Jacobi eigenvalue algorithm.
|
98
|
+
# The columns of V are the eigenvector.
|
99
|
+
|
100
|
+
def cJacobiV(tol = 1.0e-10)
|
101
|
+
cJacobi(tol)[1]
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Stick
|
2
|
+
|
3
|
+
class Matrix
|
4
|
+
|
5
|
+
module LU
|
6
|
+
|
7
|
+
# Return the Gauss vector, MC, Golub, 3.2.1 Gauss Transformation, p94
|
8
|
+
|
9
|
+
def LU.gauss_vector(mat, k)
|
10
|
+
t = mat.column2matrix(k)
|
11
|
+
tk = t[k, 0]
|
12
|
+
(0..k).each{|i| t[i, 0] = 0}
|
13
|
+
return t if tk == 0
|
14
|
+
(k+1...mat.row_size).each{|i| t[i, 0] = t[i, 0].to_f / tk}
|
15
|
+
t
|
16
|
+
end
|
17
|
+
|
18
|
+
# Return the Gauss transformation matrix: M_k = I - tau * e_k^T
|
19
|
+
|
20
|
+
def LU.gauss(mat, k)
|
21
|
+
i = Matrix.I(mat.column_size)
|
22
|
+
tau = gauss_vector(mat, k)
|
23
|
+
e = i.row2matrix(k)
|
24
|
+
i - tau * e
|
25
|
+
end
|
26
|
+
|
27
|
+
# LU factorization: A = LU
|
28
|
+
# where L is lower triangular and U is upper triangular
|
29
|
+
|
30
|
+
def LU.factorization(mat)
|
31
|
+
u = mat.clone
|
32
|
+
n = u.column_size
|
33
|
+
i = Matrix.I(n)
|
34
|
+
l = i.clone
|
35
|
+
(n-1).times {|k|
|
36
|
+
mk = gauss(u, k)
|
37
|
+
u = mk * u # M_{n-1} * ... * M_1 * A = U
|
38
|
+
l += i - mk # L = M_1^{-1} * ... * M_{n-1}^{-1} = I + sum_{k=1}^{n-1} tau * e
|
39
|
+
}
|
40
|
+
return l, u
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Return the upper triangular matrix of LU factorization
|
45
|
+
# M_{n-1} * ... * M_1 * A = U
|
46
|
+
|
47
|
+
def U
|
48
|
+
LU.factorization(self)[1]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return the lower triangular matrix of LU factorization
|
52
|
+
# L = M_1^{-1} * ... * M_{n-1}^{-1} = I + sum_{k=1}^{n-1} tau * e
|
53
|
+
|
54
|
+
def L
|
55
|
+
LU.factorization(self)[0]
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
end
|
data/lib/stick/quaternion.rb
CHANGED
@@ -3,7 +3,7 @@
|
|
3
3
|
# Quaternion
|
4
4
|
#
|
5
5
|
# Copyright:
|
6
|
-
#
|
6
|
+
#
|
7
7
|
# Copyright (c) 2002 K. Kodama
|
8
8
|
#
|
9
9
|
# Authors:
|
@@ -23,9 +23,9 @@ require "complex"
|
|
23
23
|
# NOTE This Quaternion class is still very experimental.
|
24
24
|
#
|
25
25
|
# Quaternions are attributed to Sir William Rowan Hamilton
|
26
|
-
# who find it in 1843, and published a major analysis in 1844 called
|
26
|
+
# who find it in 1843, and published a major analysis in 1844 called
|
27
27
|
# "On a Species of Imaginary Quantities Connected with a Theory of Quaternions"
|
28
|
-
# in the Proceedings of the Royal Irish Academ. (2, pp 424-434).
|
28
|
+
# in the Proceedings of the Royal Irish Academ. (2, pp 424-434).
|
29
29
|
#
|
30
30
|
# Typical quaternion number q is of the form q = r + a i + b j + c k.
|
31
31
|
# Bases i j k behaves as follows:
|
@@ -42,7 +42,7 @@ require "complex"
|
|
42
42
|
#
|
43
43
|
# A Quaternion q = r + a i + b j + k c have 1st level polar form such that
|
44
44
|
#
|
45
|
-
# q = |q|(cos t1 + sin t1 u1) , where u1 is unit vector of u1 = a1 i + b1 j + c1 k.
|
45
|
+
# q = |q|(cos t1 + sin t1 u1) , where u1 is unit vector of u1 = a1 i + b1 j + c1 k.
|
46
46
|
#
|
47
47
|
# u1 have 2nd level
|
48
48
|
#
|
@@ -126,7 +126,7 @@ Quaternian::vector(v)
|
|
126
126
|
Quaternion::rotation(v,t)
|
127
127
|
# t-rotatin along the 3-D vector v
|
128
128
|
q.rotate(r)
|
129
|
-
rotate by r = q r^(-1)
|
129
|
+
rotate by r = q r^(-1)
|
130
130
|
q.rotate_angle
|
131
131
|
# = q.amplitude/2
|
132
132
|
|
@@ -189,7 +189,7 @@ q.sinh
|
|
189
189
|
q.cosh
|
190
190
|
q.tanh
|
191
191
|
|
192
|
-
* Trigonometric functions
|
192
|
+
* Trigonometric functions
|
193
193
|
q.sin
|
194
194
|
q.cos
|
195
195
|
q.tan
|
@@ -211,6 +211,7 @@ q.hash
|
|
211
211
|
q.inspect
|
212
212
|
=end
|
213
213
|
|
214
|
+
module Stick
|
214
215
|
|
215
216
|
def Quaternion(a=0, b=0,c=0, d=0)
|
216
217
|
if a.kind_of?(Quaternion);
|
@@ -224,6 +225,8 @@ def Quaternion(a=0, b=0,c=0, d=0)
|
|
224
225
|
end
|
225
226
|
end
|
226
227
|
|
228
|
+
module_function :Quaternion
|
229
|
+
|
227
230
|
class Quaternion < Numeric
|
228
231
|
attr :re
|
229
232
|
attr :im
|
@@ -549,3 +552,4 @@ class Quaternion < Numeric
|
|
549
552
|
end
|
550
553
|
|
551
554
|
end # Quaternion
|
555
|
+
end # Stick
|