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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
use crate::error::OptimizeResult;
use crate::result::OptimizeResults;
use crate::roots::Options;
use scirs2_core::ndarray::{Array1, Array2, ArrayBase, Data, Ix1};
/// Implements Anderson mixing method for root finding
///
/// Anderson mixing is an acceleration technique that combines the current and previous
/// iterates to accelerate convergence. It can be viewed as a type of multisecant quasi-Newton
/// method that uses information from multiple previous iterations.
#[allow(dead_code)]
pub fn root_anderson<F, J, S>(
func: F,
x0: &ArrayBase<S, Ix1>,
_jacobian_fn: Option<J>,
options: &Options,
) -> OptimizeResult<OptimizeResults<f64>>
where
F: Fn(&[f64]) -> Array1<f64>,
J: Fn(&[f64]) -> Array2<f64>,
S: Data<Elem = f64>,
{
// Get options or use defaults
let xtol = options.xtol.unwrap_or(1e-8);
let ftol = options.ftol.unwrap_or(1e-8);
let maxfev = options.maxfev.unwrap_or(100 * x0.len());
// Anderson specific parameters
let m = options.m_anderson.unwrap_or(5).min(x0.len()); // Number of previous iterations to use
let beta = options.beta_anderson.unwrap_or(0.5); // Damping parameter
// Initialize variables
let n = x0.len();
let mut x = x0.to_owned();
let mut f = func(x.as_slice().expect("Operation failed"));
let mut nfev = 1;
// Storage for previous residuals and step differences
let mut residuals: Vec<Array1<f64>> = Vec::with_capacity(m + 1);
let mut steps: Vec<Array1<f64>> = Vec::with_capacity(m + 1);
// Main iteration loop
let mut iter = 0;
let mut converged = false;
while iter < maxfev {
// Check if we've converged in function values
let f_norm = f.iter().map(|&fi| fi.powi(2)).sum::<f64>().sqrt();
if f_norm < ftol {
converged = true;
break;
}
// Simple step (fixed-point iteration): x_{n+1} = x_n - f(x_n)
let mut x_simple = x.clone();
for i in 0..n {
x_simple[i] = x[i] - f[i];
}
// Apply Anderson mixing if we have previous iterations
let x_new = if iter >= 1 {
// Add current residual to the history
residuals.push(f.clone());
// Use only m most recent iterations
if residuals.len() > m {
residuals.remove(0);
steps.remove(0);
}
// Number of previous iterations to use (limited by available history)
let mk = residuals.len() - 1;
if mk >= 1 {
// Construct the matrix of residual differences
let mut df = Array2::zeros((n, mk));
for j in 0..mk {
for i in 0..n {
df[[i, j]] = residuals[j + 1][i] - residuals[0][i];
}
}
// Solve the least squares problem: min ||residuals[0] - df * alpha||
// To find coefficients alpha that best combine previous iterations
let alpha = match solve_least_squares(&df, &(-&residuals[0])) {
Some(a) => a,
None => {
// If the least squares problem fails, use simple iteration
let mut x_next = x.clone();
for i in 0..n {
x_next[i] = (1.0 - beta) * x[i] + beta * x_simple[i];
}
x_next
}
};
// Apply the mixing to compute the next iterate
let mut x_next = x.clone();
// Start with weighted current point
for i in 0..n {
x_next[i] = x_simple[i] * (1.0 - alpha.iter().sum::<f64>());
}
// Add weighted previous steps
for j in 0..mk {
for i in 0..n {
x_next[i] += alpha[j] * steps[j][i];
}
}
// Apply damping
for i in 0..n {
x_next[i] = (1.0 - beta) * x[i] + beta * x_next[i];
}
x_next
} else {
// Use simple iteration if we don't have enough history
let mut x_next = x.clone();
for i in 0..n {
x_next[i] = (1.0 - beta) * x[i] + beta * x_simple[i];
}
x_next
}
} else {
// For the first step, just use damped fixed-point iteration
let mut x_next = x.clone();
for i in 0..n {
x_next[i] = (1.0 - beta) * x[i] + beta * x_simple[i];
}
x_next
};
// Save step for next iteration
steps.push(x_simple);
// Evaluate function at the new point
let f_new = func(x_new.as_slice().expect("Operation failed"));
nfev += 1;
// Check convergence on parameters
let step_norm = (0..n)
.map(|i| (x_new[i] - x[i]).powi(2))
.sum::<f64>()
.sqrt();
let x_norm = (0..n).map(|i| x[i].powi(2)).sum::<f64>().sqrt();
if step_norm < xtol * (1.0 + x_norm) {
converged = true;
x = x_new;
f = f_new;
break;
}
// Update variables for next iteration
x = x_new;
f = f_new;
iter += 1;
}
// Create and return result
let mut result = OptimizeResults::default();
result.x = x;
result.fun = f.iter().map(|&fi| fi.powi(2)).sum::<f64>();
result.nfev = nfev;
result.nit = iter;
result.success = converged;
if converged {
result.message = "Root finding converged successfully".to_string();
} else {
result.message = "Maximum number of function evaluations reached".to_string();
}
Ok(result)
}
/// Solves a least squares problem ||Ax - b|| using QR decomposition
#[allow(dead_code)]
pub fn solve_least_squares(a: &Array2<f64>, b: &Array1<f64>) -> Option<Array1<f64>> {
let (m, n) = (a.nrows(), a.ncols());
if m < n {
return None; // Underdetermined system
}
// Simple QR decomposition using Householder reflections
let mut q: Array2<f64> = Array2::eye(m);
let mut r = a.clone();
for k in 0..n {
// Householder transformation to introduce zeros below the diagonal
let mut x = Array1::zeros(m - k);
for i in k..m {
x[i - k] = r[[i, k]];
}
let x_norm = x.iter().map(|&xi| xi.powi(2)).sum::<f64>().sqrt();
if x_norm < 1e-10 {
continue; // Skip if column is already zeroed
}
// Compute Householder vector
let mut v = x.clone();
v[0] += if x[0] >= 0.0 { x_norm } else { -x_norm };
let v_norm = v.iter().map(|&vi| vi.powi(2)).sum::<f64>().sqrt();
if v_norm < 1e-10 {
continue;
}
// Normalize v
for i in 0..v.len() {
v[i] /= v_norm;
}
// Apply Householder transformation to R
for j in k..n {
let mut dot_product = 0.0;
for i in 0..v.len() {
dot_product += v[i] * r[[i + k, j]];
}
let factor = 2.0 * dot_product;
for i in 0..v.len() {
r[[i + k, j]] -= factor * v[i];
}
}
// Apply Householder transformation to Q
for j in 0..m {
let mut dot_product = 0.0;
for i in 0..v.len() {
dot_product += v[i] * q[[j, i + k]];
}
let factor = 2.0 * dot_product;
for i in 0..v.len() {
q[[j, i + k]] -= factor * v[i];
}
}
}
// Now we have R and Q (actually Q^T) such that A = QR
// To solve least squares, we compute x = R^-1 Q^T b
// Apply Q^T to b: y = Q^T b
let mut y = Array1::zeros(m);
for i in 0..m {
for j in 0..m {
y[i] += q[[i, j]] * b[j];
}
}
// Back-substitution to solve Rx = y
let mut x = Array1::zeros(n);
for i in (0..n).rev() {
let mut sum: f64 = y[i];
for j in (i + 1)..n {
sum -= r[[i, j]] * x[j];
}
if r[[i, i]].abs() < 1e-10 {
return None; // Singular matrix
}
x[i] = sum / r[[i, i]];
}
Some(x)
}