Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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

486

487

488

489

490

491

492

493

494

495

496

497

498

499

500

501

502

503

504

505

506

507

508

509

510

511

512

513

514

515

516

517

518

519

520

521

522

523

524

525

526

527

528

529

530

531

532

533

534

535

536

537

538

539

540

541

542

543

544

545

546

547

548

549

550

551

552

553

554

555

556

557

558

559

560

561

562

563

564

565

566

567

568

569

570

571

572

573

574

575

576

577

578

579

580

581

582

583

584

585

586

587

588

589

590

591

592

593

594

595

596

597

598

599

600

601

602

603

604

605

606

607

608

609

610

611

612

613

614

615

616

617

618

619

620

621

622

623

624

625

626

627

628

# coding: utf-8 

# Copyright (c) Pymatgen Development Team. 

# Distributed under the terms of the MIT License. 

""" 

This module provides objects to inspect the status of the Abinit tasks at run-time. 

by extracting information from the main output file (text format). 

""" 

from __future__ import unicode_literals, division, print_function 

 

import collections 

import numpy as np 

import yaml 

import six 

 

from six.moves import cStringIO, map, zip 

from tabulate import tabulate 

from monty.collections import AttrDict 

from pymatgen.util.plotting_utils import add_fig_kwargs 

 

 

def straceback(): 

"""Returns a string with the traceback.""" 

import traceback 

return traceback.format_exc() 

 

 

def _magic_parser(stream, magic): 

""" 

Parse the section with the SCF cycle 

 

Returns: 

dict where the key are the name of columns and 

the values are list of numbers. Note if no section was found. 

 

.. warning:: 

 

The parser is very fragile and should be replaced by YAML. 

""" 

#Example (SCF cycle, similar format is used for phonons): 

# 

# iter Etot(hartree) deltaE(h) residm vres2 

# ETOT 1 -8.8604027880849 -8.860E+00 2.458E-02 3.748E+00 

 

# At SCF step 5 vres2 = 3.53E-08 < tolvrs= 1.00E-06 =>converged. 

in_doc, fields = 0, None 

 

for line in stream: 

line = line.strip() 

 

if line.startswith(magic): 

keys = line.split() 

fields = collections.OrderedDict((k, []) for k in keys) 

 

if fields is not None: 

#print(line) 

in_doc += 1 

if in_doc == 1: 

continue 

 

# End of the section. 

if not line: break 

 

tokens = list(map(float, line.split()[1:])) 

assert len(tokens) == len(keys) 

for l, v in zip(fields.values(), tokens): 

l.append(v) 

 

# Convert values to numpy arrays. 

if fields: 

return collections.OrderedDict([(k, np.array(v)) for k, v in fields.items()]) 

else: 

return None 

 

 

def plottable_from_outfile(filepath): 

""" 

Factory function that returns a plottable object by inspecting the main output file of abinit 

Returns None if it is not able to detect the class to instantiate. 

""" 

# TODO 

# Figure out how to detect the type of calculations 

# without having to parse the input. Possible approach: YAML doc 

#with YamlTokenizer(filepath) as r: 

# doc = r.next_doc_with_tag("!CalculationType") 

# d = yaml.load(doc.text_notag) 

# calc_type = d["calculation_type"] 

 

#ctype2class = { 

# "Ground State": GroundStateScfCycle, 

# "Phonon": PhononScfCycle, 

# "Relaxation": Relaxation, 

#} 

#obj = ctype2class.get(calc_type, None) 

 

obj = GroundStateScfCycle 

if obj is not None: 

return obj.from_file(filepath) 

else: 

return None 

 

 

class ScfCycle(collections.Mapping): 

""" 

It essentially consists of a dictionary mapping string 

to list of floats containing the data at the different iterations. 

 

.. attributes:: 

 

num_iterations: Number of iterations performed. 

""" 

def __init__(self, fields): 

self.fields = fields 

#print(fields) 

 

all_lens = [len(lst) for lst in self.values()] 

self.num_iterations = all_lens[0] 

assert all(n == self.num_iterations for n in all_lens) 

 

def __getitem__(self, slice): 

return self.fields.__getitem__(slice) 

 

def __iter__(self): 

return self.fields.__iter__() 

 

def __len__(self): 

return len(self.fields) 

 

def __str__(self): 

"""String representation.""" 

rows = [list(map(str, (self[k][it] for k in self.keys()))) 

for it in range(self.num_iterations)] 

stream = cStringIO() 

print(tabulate(rows, headers=list(self.keys())), file=stream) 

stream.seek(0) 

 

return "".join(stream) 

 

@property 

def last_iteration(self): 

"""Returns a dictionary with the values of the last iteration.""" 

return {k: v[-1] for k, v in self.items()} 

 

@classmethod 

def from_file(cls, filepath): 

"""Read the first occurrence of ScfCycle from file.""" 

with open(filepath, "r") as stream: 

return cls.from_stream(stream) 

 

@classmethod 

def from_stream(cls, stream): 

""" 

Read the first occurrence of ScfCycle from stream. 

 

Returns: 

None if no `ScfCycle` entry is found. 

""" 

fields = _magic_parser(stream, magic=cls.MAGIC) 

 

if fields: 

fields.pop("iter") 

return cls(fields) 

else: 

return None 

 

@add_fig_kwargs 

def plot(self, fig=None, **kwargs): 

""" 

Uses matplotlib to plot the evolution of the SCF cycle. Return `matplotlib` figure 

 

Args: 

fig: matplotlib figure. If None a new figure is produced. 

""" 

import matplotlib.pyplot as plt 

 

# Build grid of plots. 

num_plots, ncols, nrows = len(self), 1, 1 

if num_plots > 1: 

ncols = 2 

nrows = (num_plots//ncols) + (num_plots % ncols) 

 

if fig is None: 

fig, ax_list = plt.subplots(nrows=nrows, ncols=ncols, sharex=True, squeeze=False) 

ax_list = ax_list.ravel() 

else: 

ax_list = list(fig.axes) 

 

# Use log scale for these variables. 

use_logscale = set(["residm", "vres2"]) 

 

# Hard-coded y-range for selected variables. 

has_yrange = { 

"deltaE(h)": (-1e-3, +1e-3), 

"deltaE(Ha)": (-1e-3, +1e-3), 

} 

 

iter_num = np.array(list(range(self.num_iterations))) 

for ((key, values), ax) in zip(self.items(), ax_list): 

ax.grid(True) 

ax.set_xlabel('Iteration') 

ax.set_xticks(iter_num, minor=False) 

ax.set_ylabel(key) 

 

xx, yy = iter_num, values 

if self.num_iterations > 1: 

# Don't show the first iteration since it's not very useful. 

xx, yy = xx[1:] + 1, values[1:] 

 

ax.plot(xx, yy, "-o", lw=2.0) 

 

if key in use_logscale and np.all(yy > 1e-22): 

ax.set_yscale("log") 

 

if key in has_yrange: 

ymin, ymax = has_yrange[key] 

val_min, val_max = np.min(yy), np.max(yy) 

if abs(val_max - val_min) > abs(ymax - ymin): 

ax.set_ylim(ymin, ymax) 

 

# Get around a bug in matplotlib. 

if (num_plots % ncols) != 0: 

ax_list[-1].plot(xx, yy, lw=0.0) 

ax_list[-1].axis('off') 

 

#plt.legend(loc="best") 

return fig 

 

 

class GroundStateScfCycle(ScfCycle): 

"""Result of the Ground State self-consistent cycle.""" 

MAGIC = "iter Etot(hartree)" 

 

@property 

def last_etotal(self): 

"""The total energy at the last iteration.""" 

return self["Etot(hartree)"][-1] 

 

 

class D2DEScfCycle(ScfCycle): 

"""Result of the Phonon self-consistent cycle.""" 

MAGIC = "iter 2DEtotal(Ha)" 

 

@property 

def last_etotal(self): 

"""The 2-nd order derivative of the energy at the last iteration.""" 

return self["2DEtotal(Ha)"][-1] 

 

 

class PhononScfCycle(D2DEScfCycle): 

"""Iterations of the DFPT SCF cycle for phonons.""" 

 

 

class Relaxation(collections.Iterable): 

""" 

A list of :class:`GroundStateScfCycle` objects. 

 

.. note:: 

 

Forces, stresses and crystal structures are missing. 

Solving this problem would require the standardization 

of the Abinit output file (YAML). 

""" 

def __init__(self, cycles): 

self.cycles = cycles 

 

def __iter__(self): 

return self.cycles.__iter__() 

 

def __len__(self): 

return self.cycles.__len__() 

 

def __getitem__(self, slice): 

return self.cycles[slice] 

 

def __str__(self): 

"""String representation.""" 

lines = [] 

app = lines.append 

 

for i, cycle in enumerate(self): 

app("") 

app("RELAXATION STEP: %d" % i) 

app(str(cycle)) 

app("") 

 

return "\n".join(lines) 

 

@classmethod 

def from_file(cls, filepath): 

"""Initialize the object from the Abinit main output file.""" 

with open(filepath, "r") as stream: 

return cls.from_stream(stream) 

 

@classmethod 

def from_stream(cls, stream): 

""" 

Extract data from stream. Returns None if some error occurred. 

""" 

cycles = [] 

while True: 

scf_cycle = GroundStateScfCycle.from_stream(stream) 

if scf_cycle is None: break 

cycles.append(scf_cycle) 

 

return cls(cycles) if cycles else None 

 

@property 

def history(self): 

""" 

Dictionary of lists with the evolution of the data as function of the relaxation step. 

""" 

try: 

return self._history 

except AttributeError: 

self._history = history = collections.defaultdict(list) 

for cycle in self: 

d = cycle.last_iteration 

for k, v in d.items(): 

history[k].append(v) 

 

return self._history 

 

@add_fig_kwargs 

def plot(self, **kwargs): 

""" 

Uses matplotlib to plot the evolution of the structural relaxation. 

 

Returns: 

`matplotlib` figure 

""" 

import matplotlib.pyplot as plt 

 

history = self.history 

#print(history) 

 

relax_step = list(range(len(self))) 

 

# Build grid of plots. 

num_plots, ncols, nrows = len(list(history.keys())), 1, 1 

if num_plots > 1: 

ncols = 2 

nrows = (num_plots//ncols) + (num_plots % ncols) 

 

fig, ax_list = plt.subplots(nrows=nrows, ncols=ncols, sharex=True, squeeze=False) 

ax_list = ax_list.ravel() 

 

if (num_plots % ncols) != 0: 

ax_list[-1].axis('off') 

 

for (key, values), ax in zip(history.items(), ax_list): 

ax.grid(True) 

ax.set_xlabel('Relaxation Step') 

ax.set_xticks(relax_step, minor=False) 

ax.set_ylabel(key) 

 

ax.plot(relax_step, values, "-o", lw=2.0) 

 

return fig 

 

 

# TODO 

#class DfptScfCycle(collections.Iterable): 

 

# TODO 

#class HaydockIterations(collections.Iterable): 

# """This object collects info on the different steps of the Haydock technique used in the Bethe-Salpeter code""" 

# @classmethod 

# def from_file(cls, filepath): 

# """Initialize the object from file.""" 

# with open(filepath, "r") as stream: 

# return cls.from_stream(stream) 

# 

# @classmethod 

# def from_stream(cls, stream): 

# """Extract data from stream. Returns None if some error occurred.""" 

# cycles = [] 

# while True: 

# scf_cycle = GroundStateScfCycle.from_stream(stream) 

# if scf_cycle is None: break 

# cycles.append(scf_cycle) 

# 

# return cls(cycles) if cycles else None 

# 

# #def __init__(self): 

# 

# def plot(self, **kwargs): 

# """ 

# Uses matplotlib to plot the evolution of the structural relaxation. 

# ============== ============================================================== 

# kwargs Meaning 

# ============== ============================================================== 

# title Title of the plot (Default: None). 

# how True to show the figure (Default). 

# savefig 'abc.png' or 'abc.eps'* to save the figure to a file. 

# ============== ============================================================== 

# Returns: 

# `matplotlib` figure 

# """ 

# import matplotlib.pyplot as plt 

# title = kwargs.pop("title", None) 

# show = kwargs.pop("show", True) 

# savefig = kwargs.pop("savefig", None) 

# if title: fig.suptitle(title) 

# if savefig is not None: fig.savefig(savefig) 

# if show: plt.show() 

# return fig 

 

 

 

################## 

## Yaml parsers. 

################## 

 

class YamlTokenizerError(Exception): 

"""Exceptions raised by :class:`YamlTokenizer`.""" 

 

 

class YamlTokenizer(collections.Iterator): 

""" 

Provides context-manager support so you can use it in a with statement. 

""" 

Error = YamlTokenizerError 

 

def __init__(self, filename): 

# The position inside the file. 

self.linepos = 0 

self.stream = open(filename, "r") 

 

def __iter__(self): 

return self 

 

def __enter__(self): 

return self 

 

def __exit__(self, type, value, traceback): 

self.close() 

 

def __del__(self): 

self.close() 

 

def close(self): 

try: 

self.stream.close() 

except: 

print("Exception in YAMLTokenizer.close()") 

print(straceback()) 

 

def seek(self, offset, whence=0): 

""" 

seek(offset[, whence]) -> None. Move to new file position. 

 

Argument offset is a byte count. Optional argument whence defaults to 

0 (offset from start of file, offset should be >= 0); other values are 1 

(move relative to current position, positive or negative), and 2 (move 

relative to end of file, usually negative, although many platforms allow 

seeking beyond the end of a file). If the file is opened in text mode, 

only offsets returned by tell() are legal. Use of other offsets causes 

undefined behavior. 

Note that not all file objects are seekable. 

""" 

assert offset == 0 

self.linepos = 0 

return self.stream.seek(offset, whence) 

 

# Python 3 compatibility 

def __next__(self): 

return self.next() 

 

def next(self): 

""" 

Returns the first YAML document in stream. 

 

.. warning:: 

 

Assume that the YAML document are closed explicitely with the sentinel '...' 

""" 

in_doc, lines, doc_tag = None, [], None 

 

for i, line in enumerate(self.stream): 

self.linepos += 1 

#print(i, line) 

 

if line.startswith("---"): 

# Include only lines in the form: 

# "--- !tag" 

# "---" 

# Other lines are spurious. 

in_doc = False 

l = line[3:].strip().lstrip() 

 

if l.startswith("!"): 

# "--- !tag" 

doc_tag = l 

in_doc = True 

elif not l: 

# "---" 

in_doc = True 

doc_tag = None 

 

if in_doc: 

lineno = self.linepos 

 

if in_doc: 

lines.append(line) 

 

if in_doc and line.startswith("..."): 

return YamlDoc(text="".join(lines), lineno=lineno, tag=doc_tag) 

 

raise StopIteration("Cannot find next YAML document") 

 

def all_yaml_docs(self): 

""" 

Returns a list with all the YAML docs found in stream. 

Seek the stream before returning. 

 

.. warning:: 

 

Assume that all the YAML docs (with the exception of the last one) 

are closed explicitely with the sentinel '...' 

""" 

docs = [doc for doc in self] 

self.seek(0) 

return docs 

 

def next_doc_with_tag(self, doc_tag): 

""" 

Returns the next document with the specified tag. Empty string is no doc is found. 

""" 

while True: 

try: 

doc = six.advance_iterator(self) 

if doc.tag == doc_tag: 

return doc 

 

except StopIteration: 

raise 

 

def all_docs_with_tag(self, doc_tag): 

""" 

Returns all the documents with the specified tag. 

""" 

docs = [] 

 

while True: 

try: 

doc = self.next_doc_with(doc_tag) 

docs.append(doc) 

 

except StopIteration: 

break 

 

self.seek(0) 

return docs 

 

 

def yaml_read_kpoints(filename, doc_tag="!Kpoints"): 

"""Read the K-points from file.""" 

with YamlTokenizer(filename) as r: 

doc = r.next_doc_with_tag(doc_tag) 

d = yaml.load(doc.text_notag) 

 

return np.array(d["reduced_coordinates_of_qpoints"]) 

 

 

def yaml_read_irred_perts(filename, doc_tag="!IrredPerts"): 

"""Read the list of irreducible perturbations from file.""" 

with YamlTokenizer(filename) as r: 

doc = r.next_doc_with_tag(doc_tag) 

d = yaml.load(doc.text_notag) 

 

return [AttrDict(**pert) for pert in d["irred_perts"]] 

#return d["irred_perts"] 

 

 

class YamlDoc(object): 

""" 

Handy object that stores that YAML document, its main tag and the 

position inside the file. 

""" 

__slots__ = [ 

"text", 

"lineno", 

"tag", 

] 

 

def __init__(self, text, lineno, tag=None): 

""" 

Args: 

text: String with the YAML document. 

lineno: The line number where the document is located. 

tag: The YAML tag associate to the document. 

""" 

# Sanitize strings: use "ignore" to skip invalid characters in .encode/.decode like 

if isinstance(text, bytes): 

text = text.decode("utf-8", "ignore") 

text = text.rstrip().lstrip() 

self.text = text 

self.lineno = lineno 

if isinstance(tag, bytes): 

tag = tag.decode("utf-8", "ignore") 

self.tag = tag 

 

def __str__(self): 

return self.text 

 

def __eq__(self, other): 

if other is None: return False 

return (self.text == other.text and 

self.lineno == other.lineno and 

self.tag == other.tag) 

 

def __ne__(self, other): 

return not self == other 

 

@property 

def text_notag(self): 

""" 

Returns the YAML text without the tag. 

Useful if we don't have any constructor registered for the tag 

(we used the tag just to locate the document). 

""" 

if self.tag is not None: 

return self.text.replace(self.tag, "") 

else: 

return self.text 

 

def as_dict(self): 

"""Use Yaml to parse the text (without the tag) and returns a dictionary.""" 

return yaml.load(self.text_notag)