PLaSK library
Loading...
Searching...
No Matches
broyden.cpp
Go to the documentation of this file.
1
/*
2
* This file is part of PLaSK (https://plask.app) by Photonics Group at TUL
3
* Copyright (c) 2022 Lodz University of Technology
4
*
5
* This program is free software: you can redistribute it and/or modify
6
* it under the terms of the GNU General Public License as published by
7
* the Free Software Foundation, version 3.
8
*
9
* This program is distributed in the hope that it will be useful,
10
* but WITHOUT ANY WARRANTY; without even the implied warranty of
11
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
* GNU General Public License for more details.
13
*/
14
#include "
broyden.hpp
"
15
#include "
rootdigger-impl.hpp
"
16
using namespace
std
;
17
18
namespace
plask
{
namespace
optical {
namespace
modal {
19
20
//**************************************************************************
22
dcomplex
RootBroyden::find
(dcomplex start)
23
{
24
writelog(
LOG_DETAIL
,
"Searching for the root with Broyden method starting from "
+
str
(start));
25
log_value
.
resetCounter
();
26
dcomplex x = Broyden(start);
27
writelog(
LOG_RESULT
,
"Found root at "
+
str
(x));
28
return
x;
29
}
30
31
//**************************************************************************
32
//**************************************************************************
33
//******** The Broyden globally convergent method for root finding *********
34
//**************************************************************************
35
36
//**************************************************************************
37
// Return Jacobian of F(x)
38
void
RootBroyden::fdjac(dcomplex x, dcomplex
F
, dcomplex&
Jr
, dcomplex&
Ji
)
39
{
40
double
xr0
=
real
(x),
xi0
=
imag
(x);
41
double
hr
= EPS*abs(
xr0
), hi = EPS*abs(
xi0
);
42
if
(
hr
== 0.0)
hr
= EPS;
43
if
(hi == 0.0) hi = EPS;
44
45
double
xr1
=
xr0
+
hr
,
xi1
=
xi0
+ hi;
46
hr
=
xr1
-
xr0
; hi =
xi1
-
xi0
;
// trick to reduce finite precision error
47
48
dcomplex
xr
= dcomplex(
xr1
,
xi0
),
xi
= dcomplex(
xr0
,
xi1
);
49
dcomplex
Fr
=
valFunction
(
xr
);
log_value
(
xr
,
Fr
);
50
dcomplex
Fi
=
valFunction
(
xi
);
log_value
(
xi
,
Fi
);
51
52
Jr
= (
Fr
-
F
) /
hr
;
53
Ji
= (
Fi
-
F
) / hi;
54
}
55
56
//**************************************************************************
57
// Search for the new point x along direction p for which
58
// functional f decreased sufficiently
59
// g - (approximate) gradient of 1/2(F*F), stpmax - maximum allowed step
60
// return true if performed step or false if could not find sufficient function decrease
61
bool
RootBroyden::lnsearch(dcomplex& x, dcomplex&
F
, dcomplex g, dcomplex p,
double
stpmax
)
62
{
63
if
(
double
absp=abs(p) >
stpmax
) p *=
stpmax
/absp;
// Ensure step <= stpmax
64
65
double
slope
=
real
(g)*
real
(p) +
imag
(g)*
imag
(p);
// slope = grad(f)*p
66
67
// Compute the functional
68
double
f = 0.5 * (
real
(
F
)*
real
(
F
) +
imag
(
F
)*
imag
(
F
));
69
70
// Remember original values
71
dcomplex
x0
= x;
72
double
f0
= f;
73
74
double
lambda = 1.0;
// lambda parameter x = x0 + lambda*p
75
double
lambda1
,
lambda2
= 0.,
f2
= 0.;
76
77
bool
first =
true
;
78
79
while
(
true
) {
80
if
(lambda <
params
.
lambda_min
) {
// we have (possible) convergence of x
81
x =
x0
;
// f = f0;
82
return
false
;
83
}
84
85
x =
x0
+ lambda*p;
86
F
=
valFunction
(x);
87
log_value
.
count
(x,
F
);
88
89
f = 0.5 * (
real
(
F
)*
real
(
F
) +
imag
(
F
)*
imag
(
F
));
90
if
(
std::isnan
(f))
throw
ComputationError
(
solver
.
getId
(),
"computed value is NaN"
);
91
92
if
(f <
f0
+
params
.
alpha
*lambda*
slope
)
// sufficient function decrease
93
return
true
;
94
95
lambda1
= lambda;
96
97
if
(first) {
// first backtrack
98
lambda = -
slope
/ (2. * (f-
f0
-
slope
));
99
first =
false
;
100
}
else
{
// subsequent backtracks
101
double
rsh1
= f -
f0
-
lambda1
*
slope
;
102
double
rsh2
=
f2
-
f0
-
lambda2
*
slope
;
103
double
a
= (
rsh1
/ (
lambda1
*
lambda1
) -
rsh2
/ (
lambda2
*
lambda2
)) / (
lambda1
-
lambda2
);
104
double
b
= (-
lambda2
*
rsh1
/ (
lambda1
*
lambda1
) +
lambda1
*
rsh2
/ (
lambda2
*
lambda2
))
105
/ (
lambda1
-
lambda2
);
106
107
if
(
a
== 0.0)
108
lambda = -
slope
/(2.*
b
);
109
else
{
110
double
delta =
b
*
b
- 3.*
a
*
slope
;
111
if
(delta < 0.0)
throw
ComputationError(
solver
.
getId
(),
"broyden lnsearch: roundoff problem"
);
112
lambda = (-
b
+
sqrt
(delta)) / (3.0*
a
);
113
}
114
}
115
116
lambda2
=
lambda1
;
f2
= f;
// store the second last parameters
117
118
lambda =
max
(lambda, 0.1*
lambda1
);
// guard against too fast decrease of lambda
119
120
writelog(
LOG_DETAIL
,
"Broyden step decreased to the fraction "
+
str
(lambda) +
" of the original step"
);
121
}
122
123
}
124
125
//**************************************************************************
126
// Search for the root of char_val using globally convergent Broyden method
127
// starting from point x
128
dcomplex RootBroyden::Broyden(dcomplex x)
129
{
130
// Compute the initial guess of the function (and check for the root)
131
dcomplex
F
=
valFunction
(x);
132
double
absF
= abs(
F
);
133
log_value
.
count
(x,
F
);
134
if
(
absF
<
params
.
tolf_min
)
return
x
;
135
136
bool
restart
=
true
;
// do we have to recompute Jacobian?
137
bool
trueJacobian
;
// did we recently update Jacobian?
138
139
dcomplex
Br
, Bi;
// Broyden matrix columns
140
dcomplex
dF
,
dx
;
// Computed dist
141
142
dcomplex
oldx
,
oldF
;
143
144
// Main loop
145
for
(
int
i = 0;
i
<
params
.
maxiter
;
i
++) {
146
oldx
=
x
;
oldF
=
F
;
147
148
if
(
restart
) {
// compute Broyden matrix as a Jacobian
149
fdjac(x,
F
,
Br
, Bi);
150
restart
=
false
;
151
trueJacobian
=
true
;
152
}
else
{
// update Broyden matrix
153
dcomplex
dB
=
dF
- dcomplex(
real
(
Br
)*
real
(
dx
)+
real
(Bi)*
imag
(
dx
),
imag
(
Br
)*
real
(
dx
)+
imag
(Bi)*
imag
(
dx
));
154
double
m = (
real
(
dx
)*
real
(
dx
) +
imag
(
dx
)*
imag
(
dx
));
155
Br
+= (
dB
*
real
(
dx
)) / m;
156
Bi += (
dB
*
imag
(
dx
)) / m;
157
trueJacobian
=
false
;
158
}
159
160
// compute g ~= B^T * F
161
dcomplex g = dcomplex(
real
(
Br
)*
real
(
F
)+
imag
(
Br
)*
imag
(
F
),
real
(Bi)*
real
(
F
)+
imag
(Bi)*
imag
(
F
));
162
163
// compute p = - B**(-1) * F
164
double
M =
real
(
Br
)*
imag
(Bi) -
imag
(
Br
)*
real
(Bi);
165
if
(M == 0)
throw
ComputationError(
solver
.
getId
(),
"singular Jacobian in Broyden method"
);
166
dcomplex
p
= - dcomplex(
real
(
F
)*
imag
(Bi)-
imag
(
F
)*
real
(Bi),
real
(
Br
)*
imag
(
F
)-
imag
(
Br
)*
real
(
F
)) / M;
167
168
// find the right step
169
if
(lnsearch(x,
F
, g, p,
params
.
maxstep
)) {
// found sufficient functional decrease
170
dx
=
x
-
oldx
;
171
dF
=
F
-
oldF
;
172
if
((abs(
dx
) <
params
.
tolx
&& abs(
F
) <
params
.
tolf_max
) || abs(
F
) <
params
.
tolf_min
)
173
return
x
;
// convergence!
174
}
else
{
175
if
(abs(
F
) <
params
.
tolf_max
)
// convergence!
176
return
x
;
177
else
if
(!
trueJacobian
) {
// first try reinitializing the Jacobian
178
writelog(
LOG_DETAIL
,
"Reinitializing Jacobian"
);
179
restart
=
true
;
180
}
else
{
// either spurious convergence (local minimum) or failure
181
throw
ComputationError(
solver
.
getId
(),
"Broyden method failed to converge"
);
182
}
183
}
184
}
185
186
throw
ComputationError(
solver
.
getId
(),
"Broyden: maximum number of iterations reached"
);
187
}
188
189
}}}
// namespace plask::optical::modal
solvers
optical
modal
broyden.cpp
Generated by
1.9.8