/
petsc_ksp.py
485 lines (394 loc) · 13.2 KB
/
petsc_ksp.py
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
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
"""LinearSolver that uses PetSC KSP to solve for a system's derivatives."""
import numpy as np
import os
import sys
from openmdao.solvers.solver import LinearSolver
from openmdao.utils.mpi import check_mpi_env
use_mpi = check_mpi_env()
if use_mpi is not False:
try:
import petsc4py
from petsc4py import PETSc
except ImportError:
PETSc = None
if use_mpi is True:
raise ImportError("Importing petsc4py failed and OPENMDAO_USE_MPI is true.")
else:
PETSc = None
KSP_TYPES = [
"richardson",
"chebyshev",
"cg",
"groppcg",
"pipecg",
"pipecgrr",
"cgne",
"nash",
"stcg",
"gltr",
"fcg",
"pipefcg",
"gmres",
"pipefgmres",
"fgmres",
"lgmres",
"dgmres",
"pgmres",
"tcqmr",
"bcgs",
"ibcgs",
"fbcgs",
"fbcgsr",
"bcgsl",
"cgs",
"tfqmr",
"cr",
"pipecr",
"lsqr",
"preonly",
"qcg",
"bicg",
"minres",
"symmlq",
"lcd",
"python",
"gcr",
"pipegcr",
"tsirm",
"cgls"
]
def _get_petsc_vec_array_new(vec):
"""
Get the array of values for the given PETSc vector.
Helper function to handle a petsc backwards incompatibility.
Parameters
----------
vec : petsc vector
Vector whose data is being requested.
Returns
-------
ndarray
A readonly copy of the array of values from vec.
"""
return vec.getArray(readonly=True)
def _get_petsc_vec_array_old(vec):
"""
Get the array of values for the given PETSc vector.
Helper function to handle a petsc backwards incompatibility.
Parameters
----------
vec : petsc vector
Vector whose data is being requested.
Returns
-------
ndarray
An array of values from vec.
"""
return vec.getArray()
if PETSc:
try:
petsc_version = petsc4py.__version__
except AttributeError: # hack to fix doc-tests
petsc_version = "3.5"
if PETSc and int((petsc_version).split('.')[1]) >= 6:
_get_petsc_vec_array = _get_petsc_vec_array_new
else:
_get_petsc_vec_array = _get_petsc_vec_array_old
class Monitor(object):
"""
Prints output from PETSc's KSP solvers.
Callable object given to KSP as a callback for printing the residual.
Parameters
----------
solver : object
The openmdao solver.
Attributes
----------
_solver : _solver
The openmdao solver.
_norm : float
The current norm.
_norm0 : float
The norm for the first iteration.
"""
def __init__(self, solver):
"""
Store pointer to the openmdao solver and initialize norms.
"""
self._solver = solver
self._norm = 1.0
self._norm0 = 1.0
def __call__(self, ksp, counter, norm):
"""
Store norm if first iteration, and print norm.
Parameters
----------
ksp : object
the KSP solver.
counter : int
the counter.
norm : float
the norm.
"""
if counter == 0 and norm != 0.0:
self._norm0 = norm
self._norm = norm
self._solver._mpi_print(counter, norm, norm / self._norm0)
self._solver._iter_count += 1
class PETScKrylov(LinearSolver):
"""
LinearSolver that uses PetSC KSP to solve for a system's derivatives.
Parameters
----------
**kwargs : dict
Dictionary of options set by the instantiating class/script.
Attributes
----------
precon : Solver
Preconditioner for linear solve. Default is None for no preconditioner.
_ksp : dist
Dictionary of KSP instances (keyed on vector name).
"""
SOLVER = 'LN: PETScKrylov'
def __init__(self, **kwargs):
"""
Declare the solver options.
"""
super().__init__(**kwargs)
if PETSc is None:
raise RuntimeError(f"{self.msginfo}: PETSc is not available. "
"Set shell variable OPENMDAO_USE_MPI=1 to detect earlier.")
# initialize dictionary of KSP instances (keyed on vector name)
self._ksp = None
# initialize preconditioner to None
self.precon = None
def _declare_options(self):
"""
Declare options before kwargs are processed in the init method.
"""
super()._declare_options()
self.options.declare('ksp_type', default='fgmres', values=KSP_TYPES,
desc="KSP algorithm to use. Default is 'fgmres'.")
self.options.declare('restart', default=1000, types=int,
desc='Number of iterations between restarts. Larger values increase '
'iteration cost, but may be necessary for convergence')
self.options.declare('precon_side', default='right', values=['left', 'right'],
desc='Preconditioner side, default is right.')
# changing the default maxiter from the base class
self.options['maxiter'] = 100
def _assembled_jac_solver_iter(self):
"""
Return a generator of linear solvers using assembled jacs.
"""
if self.options['assemble_jac']:
yield self
if self.precon is not None:
for s in self.precon._assembled_jac_solver_iter():
yield s
def use_relevance(self):
"""
Return True if relevance should be active.
Returns
-------
bool
True if relevance should be active.
"""
return False
def _setup_solvers(self, system, depth):
"""
Assign system instance, set depth, and optionally perform setup.
Parameters
----------
system : <System>
pointer to the owning system.
depth : int
depth of the current system (already incremented).
"""
super()._setup_solvers(system, depth)
if self.precon is not None:
self.precon._setup_solvers(self._system(), self._depth + 1)
def _set_solver_print(self, level=2, type_='all'):
"""
Control printing for solvers and subsolvers in the model.
Parameters
----------
level : int
iprint level. Set to 2 to print residuals each iteration; set to 1
to print just the iteration totals; set to 0 to disable all printing
except for failures, and set to -1 to disable all printing including failures.
type_ : str
Type of solver to set: 'LN' for linear, 'NL' for nonlinear, or 'all' for all.
"""
super()._set_solver_print(level=level, type_=type_)
if self.precon is not None and type_ != 'NL':
self.precon._set_solver_print(level=level, type_=type_)
def mult(self, mat, in_vec, result):
"""
Apply Jacobian matrix (KSP Callback).
The following attributes must be defined when solve is called to
provide information used in this callback:
_system : System
pointer to the owning system.
_vec_name : str
the right-hand-side (RHS) vector name.
_mode : str
'fwd' or 'rev'.
Parameters
----------
mat : PETSc.Mat
PETSc matrix object.
in_vec : PetSC Vector
Incoming vector.
result : PetSC Vector
Empty array into which we place the matrix-vector product.
"""
# assign x and b vectors based on mode
system = self._system()
if self._mode == 'fwd':
x_vec = system._doutputs
b_vec = system._dresiduals
else: # rev
x_vec = system._dresiduals
b_vec = system._doutputs
# set value of x vector to KSP provided value
x_vec.set_val(_get_petsc_vec_array(in_vec))
# apply linear
scope_out, scope_in = system._get_matvec_scope()
system._apply_linear(self._assembled_jac, self._mode, scope_out, scope_in)
# stuff resulting value of b vector into result for KSP
result.array[:] = b_vec.asarray()
def _linearize_children(self):
"""
Return a flag that is True when we need to call linearize on our subsystems' solvers.
Returns
-------
bool
Flag for indicating child linerization
"""
precon = self.precon
return (precon is not None) and (precon._linearize_children())
def _linearize(self):
"""
Perform any required linearization operations such as matrix factorization.
"""
if self.precon is not None:
self.precon._linearize()
def solve(self, mode, rel_systems=None):
"""
Solve the linear system for the problem in self._system().
The full solution vector is returned.
Parameters
----------
mode : str
Derivative mode, can be 'fwd' or 'rev'.
rel_systems : set of str
Names of systems relevant to the current solve. Deprecated.
"""
self._mode = mode
system = self._system()
options = self.options
if system.under_complex_step:
raise RuntimeError('{}: PETScKrylov solver is not supported under '
'complex step.'.format(self.msginfo))
maxiter = options['maxiter']
atol = options['atol']
rtol = options['rtol']
# assign x and b vectors based on mode
if self._mode == 'fwd':
x_vec = system._doutputs
b_vec = system._dresiduals
else: # rev
x_vec = system._dresiduals
b_vec = system._doutputs
# create numpy arrays to interface with PETSc
sol_array = x_vec.asarray(copy=True)
rhs_array = b_vec.asarray(copy=True)
# create PETSc vectors from numpy arrays
sol_petsc_vec = PETSc.Vec().createWithArray(sol_array, comm=system.comm)
rhs_petsc_vec = PETSc.Vec().createWithArray(rhs_array, comm=system.comm)
# run PETSc solver
self._iter_count = 0
ksp = self._get_ksp_solver(system)
ksp.setTolerances(max_it=maxiter, atol=atol, rtol=rtol)
ksp.solve(rhs_petsc_vec, sol_petsc_vec)
# stuff the result into the x vector
x_vec.set_val(sol_array)
# as of petsc4py v3.20, the 'converged' attribute has been renamed to 'is_converged'
if hasattr(self._ksp, 'is_converged'):
if not self._ksp.is_converged:
self._convergence_failure()
elif not self._ksp.converged:
self._convergence_failure()
sol_petsc_vec = rhs_petsc_vec = None
def apply(self, mat, in_vec, result):
"""
Apply preconditioner.
Parameters
----------
mat : PETSc.Mat
PETSc matrix object.
in_vec : PETSc.Vector
Incoming vector.
result : PETSc.Vector
Empty vector in which the preconditioned in_vec is stored.
"""
if self.precon:
system = self._system()
mode = self._mode
# Need to clear out any junk from the inputs.
system._dinputs.set_val(0.0)
# assign x and b vectors based on mode
if mode == 'fwd':
x_vec = system._doutputs
b_vec = system._dresiduals
else: # rev
x_vec = system._dresiduals
b_vec = system._doutputs
# set value of b vector to KSP provided value
b_vec.set_val(_get_petsc_vec_array(in_vec))
# call the preconditioner
self._solver_info.append_precon()
self.precon.solve(mode)
self._solver_info.pop()
# stuff resulting value of x vector into result for KSP
result.array[:] = x_vec.asarray()
else:
# no preconditioner, just pass back the incoming vector
result.array[:] = _get_petsc_vec_array(in_vec)
def _get_ksp_solver(self, system):
"""
Get an instance of the KSP solver in `system`.
Instances will be created on first request and cached for future use.
Parameters
----------
system : `System`
Parent `System` object.
Returns
-------
KSP
the KSP solver instance.
"""
# use cached instance if available
if self._ksp:
return self._ksp
iproc = system.comm.rank
lsize = np.sum(system._var_sizes['output'][iproc, :])
size = np.sum(system._var_sizes['output'])
jac_mat = PETSc.Mat().createPython([(lsize, size), (lsize, size)],
comm=system.comm)
jac_mat.setPythonContext(self)
jac_mat.setUp()
ksp = self._ksp = PETSc.KSP().create(comm=system.comm)
ksp.setOperators(jac_mat)
ksp.setType(self.options['ksp_type'])
ksp.setGMRESRestart(self.options['restart'])
if self.options['precon_side'] == 'left':
ksp.setPCSide(PETSc.PC.Side.LEFT)
else:
ksp.setPCSide(PETSc.PC.Side.RIGHT)
ksp.setMonitor(Monitor(self))
ksp.setInitialGuessNonzero(True)
pc_mat = ksp.getPC()
pc_mat.setType('python')
pc_mat.setPythonContext(self)
return ksp