Coverage for /usr/share/miniconda3/envs/dolfin/lib/python3.8/site-packages/block/testing.py: 64%
92 statements
« prev ^ index » next coverage.py v7.2.1, created at 2023-03-20 13:03 +0000
« prev ^ index » next coverage.py v7.2.1, created at 2023-03-20 13:03 +0000
1import atexit
2import inspect
3import os
4import pathlib
5import pickle
6from urllib.parse import quote_plus
8import numpy as np
9from dolfin import MPI
12class RegressionFailure(Exception):
13 pass
16def _regr_root():
17 return pathlib.Path(__file__).parent.absolute() / 'data/regression'
20_errs = 0
23def _log_or_raise(msg):
24 if os.environ.get('BLOCK_REGRESSION_ABORT'):
25 raise RegressionFailure(msg)
26 else:
27 print('!', msg)
28 global _errs
29 _errs += 1
30 if _errs == 1:
31 @atexit.register
32 def print_msg():
33 print(f'! {_errs} expected test(s) failed')
34 print(
35 f' Remove file(s) in {_regr_root()}/ and re-run to store new values as reference')
38def check_expected(name, vec, show=False, rtol=1e-6, itol=0.1, prefix=None, expected=None):
39 itol += 1
40 if prefix is None: 40 ↛ 43line 40 didn't jump to line 43, because the condition on line 40 was never false
41 prefix = pathlib.Path(inspect.stack()[1].filename).stem
43 def _decimate(v):
44 # Pickle not supported for block_vec/dolfin_vec, convert to numpy
45 if hasattr(v, 'get_local'): 45 ↛ 47line 45 didn't jump to line 47, because the condition on line 45 was never false
46 v = v.get_local()
47 if not isinstance(v, np.ndarray):
48 v = np.concatenate(v)
49 # To save disk&repo space, we decimate the vector and calculate mean+rms
50 # within each chunk. This is quite arbitrary, but is intended to be robust
51 # decimation which still catches most regression scenarios in practice.
52 chunks = np.arange(0, len(v), 10)
53 divisor = np.add.reduceat(np.ones_like(v), chunks)
54 v_mean = np.add.reduceat(v, chunks) / divisor
55 v_rms = np.sqrt(np.add.reduceat(v**2, chunks) / divisor)
56 return (v_mean + v_rms) / 2
58 def _l2(v):
59 if np.isscalar(v):
60 return v
61 elif hasattr(v, 'norm'):
62 return v.norm('l2')
63 else:
64 return np.sqrt(np.sum(v**2))
66 fname = _regr_root() / f'{quote_plus(prefix)}.{quote_plus(name)}.pickle'
67 if fname.exists(): 67 ↛ 71line 67 didn't jump to line 71, because the condition on line 67 was never false
68 with open(fname, 'rb') as f:
69 data = pickle.load(f)
70 else:
71 data = {}
73 cur_norm = _l2(vec)
74 cur_iter = getattr(vec, '_regr_test_niter', None)
75 ref_iter = data.get('iter')
76 if expected is None:
77 cur_vec = _decimate(vec)
78 ref_vec = data.get('vec')
79 ref_norm = data.get('norm')
80 else:
81 cur_vec = vec
82 ref_vec = expected
83 ref_norm = _l2(expected)
84 is_serial = (MPI.size(MPI.comm_world) == 1)
86 if is_serial and ref_vec is not None:
87 try:
88 err_norm = _l2(cur_vec - ref_vec)
89 except Exception as e:
90 _log_or_raise(f'Failed to compute error norm: {e}')
91 else:
92 rdiff = err_norm / max(ref_norm, len(cur_vec))
93 if show:
94 print(f'Norm of {name}: {cur_norm:.4f}, error: {err_norm:.4g}')
95 if rdiff > rtol: 95 ↛ 96line 95 didn't jump to line 96, because the condition on line 95 was never true
96 _log_or_raise(
97 f'Error in {name}: {err_norm:.4g} ({rdiff:.3g} > rtol {rtol:.3g}) ({prefix})')
98 else:
99 if show:
100 print(f'Norm of {name}: {cur_norm:.4f}')
101 if ref_norm is not None: 101 ↛ 106line 101 didn't jump to line 106, because the condition on line 101 was never false
102 rdiff = abs(cur_norm - ref_norm) / max(ref_norm, len(cur_vec))
103 if rdiff > rtol: 103 ↛ 104line 103 didn't jump to line 104, because the condition on line 103 was never true
104 _log_or_raise(
105 f'Norm of {name} {cur_norm:.6g} != {ref_norm:.6g} ({rdiff:.3g} > rtol {rtol:.3g})')
106 if is_serial and ref_iter is not None:
107 if cur_iter is None or not ref_iter/itol <= cur_iter <= ref_iter*itol: 107 ↛ 108line 107 didn't jump to line 108, because the condition on line 107 was never true
108 _log_or_raise(
109 f'Solver for {name} used {cur_iter} != ({ref_iter}/{itol}--{ref_iter}*{itol}) iterations ({prefix})')
111 if not fname.exists(): 111 ↛ 112line 111 didn't jump to line 112, because the condition on line 111 was never true
112 if is_serial:
113 data = {}
114 if cur_iter is not None:
115 data['iter'] = cur_iter
116 if expected is None:
117 data['norm'] = cur_norm
118 data['vec'] = cur_vec
119 if data:
120 with open(fname, 'wb') as f:
121 pickle.dump(data, f)
122 else:
123 if MPI.rank(MPI.comm_world) == 0:
124 print('Not saving regression results in parallel')
126 return vec