/
jfsj.go
141 lines (120 loc) · 2.88 KB
/
jfsj.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
package nonlin
import (
"math"
)
// Problem to be solved
type Problem struct {
// F is a function where we seek the solution of F(x) = 0. The value of the function
// should be given to out. out will always be the same length as x. Note that F must
// not modify x.
F func(out, x []float64)
}
// Result is a result type returned by the solvers.
type Result struct {
// X is the solution vector at termination
X []float64
// F is the value the function at termination
F []float64
// MaxF is the infinity norm of F
MaxF float64
// Converges is True if the convergence criteria are met, otherwise it is False
Converged bool
}
// Settings is a type that holds parameters required by jfsj algorithm
type Settings struct {
Tol float64
Maxiter int
}
func defaultSettings() *Settings {
return &Settings{
Tol: 1e-10,
Maxiter: 100000,
}
}
// identify returns the diagonal of the identity matrix
func identity(n int) []float64 {
I := make([]float64, n)
for i := range I {
I[i] = 1.0
}
return I
}
func updateG(G []float64, dF []float64, dx []float64) {
dFDotDx := 0.0
dFGdF := 0.0
dF4 := 0.0
for i := range dx {
dFDotDx += dF[i] * dx[i]
dFGdF += dF[i] * G[i] * dF[i]
dF4 += dF[i] * dF[i] * dF[i] * dF[i]
}
for i := range G {
G[i] += (dFDotDx - dFGdF) * dF[i] * dF[i] / dF4
}
}
// JFSJ applies the jacobian free singular jacobian algorithm to solve
// the non-linear system of equations
func JFSJ(p Problem, x []float64, settings *Settings) Result {
G := identity(len(x))
if settings == nil {
settings = defaultSettings()
}
work := make([]float64, 3*len(x))
p.F(work[:len(x)], x)
var result Result
for i := 0; i < settings.Maxiter; i++ {
// On even iterations F is placed in work[:len(x)], on odd iterations
// F is placed in work[len(x):2*len(x)]
F := work[:len(x)]
if i%2 == 1 {
F = work[len(x) : 2*len(x)]
}
infNormDx := 0.0
for j := range x {
dx := -G[j] * F[j]
x[j] += dx
work[2*len(x)+j] = dx
if math.Abs(dx) > infNormDx {
infNormDx = math.Abs(dx)
}
}
dFArray := work[:len(x)]
if i%2 == 0 {
F = work[len(x) : 2*len(x)]
p.F(F, x)
} else {
F = work[:len(x)]
dFArray = work[len(x) : 2*len(x)]
p.F(F, x)
}
infNormDF := 0.0
infNormF := 0.0
for j := range x {
dF := math.Abs(work[j] - work[j+len(x)])
if dF > infNormDF {
infNormDF = dF
}
if math.Abs(F[j]) > infNormF {
infNormF = math.Abs(F[j])
}
}
if infNormDx+infNormF < settings.Tol {
result.X = x
result.Converged = true
result.F = F
return result
}
if infNormDF > settings.Tol {
sign := math.Pow(-1.0, float64(i))
// Overwrite the part of the work array where F_k is stored with dF_k = F_{k+1} - F_k
for j := range x {
dFArray[j] = sign * (work[len(x)+j] - work[j])
}
updateG(G, dFArray, work[2*len(x):])
}
}
result.X = x
result.Converged = false
result.F = work[:len(x)]
return result
}