Coverage for gpaw/io/logger.py: 94%

125 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-07-08 00:17 +0000

1import os 

2import sys 

3import time 

4 

5import numpy as np 

6import ase 

7from ase import __version__ as ase_version 

8from ase.utils import search_current_git_hash, IOContext 

9 

10import gpaw 

11import gpaw.cgpaw as cgpaw 

12from gpaw.utilities.memory import maxrss 

13 

14 

15class GPAWLogger: 

16 """Class for handling all text output.""" 

17 def __init__(self, world): 

18 self.world = world 

19 

20 self.verbose = False 

21 self._fd = None 

22 self.oldfd = 42 

23 self.iocontext = IOContext() 

24 self.use_colors = False 

25 self.green = '' 

26 self.reset = '' 

27 

28 @property 

29 def fd(self): 

30 return self._fd 

31 

32 @fd.setter 

33 def fd(self, fd): 

34 """Set the stream for text output. 

35 

36 If `txt` is not a stream-object, then it must be one of: 

37 

38 * None: Throw output away. 

39 * '-': Use stdout (``sys.stdout``) on master, elsewhere throw away. 

40 * A filename: Open a new file on master, elsewhere throw away. 

41 """ 

42 if fd == self.oldfd: 

43 return 

44 self.oldfd = fd 

45 self._fd = self.iocontext.openfile(fd, self.world) 

46 self.header() 

47 

48 def __call__(self, *args, **kwargs): 

49 flush = kwargs.pop('flush', False) 

50 print(*args, file=self._fd, **kwargs) 

51 if flush: 

52 self._fd.flush() 

53 

54 def flush(self): 

55 self._fd.flush() 

56 

57 def header(self): 

58 self() 

59 self(' ___ ___ ___ _ _ _ ') 

60 self(' | | |_ | | | | ') 

61 self(' | | | | | . | | | | ') 

62 self(' |__ | _|___|_____| ', gpaw.__version__) 

63 self(' |___|_| ') 

64 self() 

65 

66 write_header(self, self.world) 

67 

68 def print_dict(self, dct, sep=' '): 

69 options = np.get_printoptions() 

70 try: 

71 np.set_printoptions(threshold=4, linewidth=50) 

72 for key, value in sorted(dct.items()): 

73 if hasattr(value, 'todict'): 

74 value = value.todict() 

75 if isinstance(value, dict): 

76 sep = ',\n ' + ' ' * len(key) 

77 keys = sorted(value, key=lambda k: (str(type(k)), k)) 

78 s = sep.join(f'{k}: {value[k]}' for k in keys) 

79 self(f' {key}: {{{s}}}') 

80 elif hasattr(value, '__len__'): 

81 value = np.asarray(value) 

82 sep = ',\n ' + ' ' * len(key) 

83 s = sep.join(str(value).splitlines()) 

84 self(f' {key}: {s}') 

85 else: 

86 self(f' {key}: {value}') 

87 finally: 

88 np.set_printoptions(**options) 

89 

90 def __del__(self): 

91 """Destructor: Write timing output before closing.""" 

92 self.close() 

93 

94 def close(self): 

95 if gpaw.dry_run or self._fd.closed: 

96 return 

97 

98 try: 

99 mr = maxrss() 

100 except (LookupError, TypeError, NameError, AttributeError): 

101 # Thing can get weird during interpreter shutdown ... 

102 mr = 0 

103 

104 if mr > 0: 

105 if mr < 1024**3: 

106 self('Memory usage: %.2f MiB' % (mr / 1024**2)) 

107 else: 

108 self('Memory usage: %.2f GiB' % (mr / 1024**3)) 

109 

110 self('Date: ' + time.asctime()) 

111 

112 self.iocontext.close() 

113 

114 

115def write_header(log, world): 

116 # We use os.uname() here bacause platform.uname() starts a subprocess, 

117 # which MPI may not like! 

118 # This might not work on Windows. We will see ... 

119 nodename, machine = os.uname()[1::3] 

120 

121 log('User: ', os.getenv('USER', '???') + '@' + nodename) 

122 log('Date: ', time.asctime()) 

123 log('Arch: ', machine) 

124 log('Pid: ', os.getpid()) 

125 log('CWD: ', os.getcwd()) 

126 log('Python: {}.{}.{}'.format(*sys.version_info[:3])) 

127 # GPAW 

128 line = os.path.dirname(gpaw.__file__) 

129 githash = search_current_git_hash(gpaw, world) 

130 if githash is not None: 

131 line += f' ({githash:.10})' 

132 log('gpaw: ', line) 

133 

134 # Find C-code: 

135 c = getattr(cgpaw._gpaw, '__file__', '') 

136 if not c: 

137 c = sys.executable 

138 line = os.path.normpath(c) 

139 if hasattr(cgpaw, 'githash'): 

140 line += f' ({cgpaw.githash():.10})' 

141 log('_gpaw: ', cut(line)) 

142 

143 # ASE 

144 line = f'{os.path.dirname(ase.__file__)} (version {ase_version}' 

145 githash = search_current_git_hash(ase, world) 

146 if githash is not None: 

147 line += f'-{githash:.10}' 

148 line += ')' 

149 log('ase: ', line) 

150 

151 log('numpy: %s (version %s)' % 

152 (os.path.dirname(np.__file__), np.version.version)) 

153 import scipy as sp 

154 log('scipy: %s (version %s)' % 

155 (os.path.dirname(sp.__file__), sp.version.version)) 

156 # Explicitly deleting SciPy seems to remove garbage collection 

157 # problem of unknown cause 

158 del sp 

159 log('libxc: ', getattr(cgpaw, 'libxc_version', '2.x.y')) 

160 log('units: Angstrom and eV') 

161 log('cores:', world.size) 

162 log('OpenMP:', cgpaw.have_openmp) 

163 log('OMP_NUM_THREADS:', os.environ['OMP_NUM_THREADS']) 

164 

165 if gpaw.debug: 

166 log('DEBUG-MODE: true') 

167 

168 log() 

169 

170 

171def cut(s, indent=' '): 

172 if len(s) + len(indent) < 80: 

173 return s 

174 s1, s2 = s.rsplit('/', 1) 

175 return s1 + '/\n' + indent + s2 

176 

177 

178def indent(s, level=1, tab=' '): 

179 return ''.join(tab * level + l for l in s.splitlines(True))