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# -*- coding: utf-8 -*- 

2# 

3# Copyright (c) 2018, the cclib development team 

4# 

5# This file is part of cclib (http://cclib.github.io) and is distributed under 

6# the terms of the BSD 3-Clause License. 

7 

8"""Generic file writer and related tools""" 

9 

10import logging 

11from abc import ABC, abstractmethod 

12from collections.abc import Iterable 

13 

14import numpy 

15 

16from cclib.parser.utils import PeriodicTable 

17from cclib.parser.utils import find_package 

18 

19_has_openbabel = find_package("openbabel") 

20if _has_openbabel: 

21 from cclib.bridge import makeopenbabel 

22 try: 

23 from openbabel import openbabel as ob 

24 import openbabel.pybel as pb 

25 except: 

26 import openbabel as ob 

27 import pybel as pb 

28 

29 

30class MissingAttributeError(Exception): 

31 pass 

32 

33 

34class Writer(ABC): 

35 """Abstract class for writer objects.""" 

36 

37 required_attrs = () 

38 

39 def __init__(self, ccdata, jobfilename=None, indices=None, terse=False, 

40 *args, **kwargs): 

41 """Initialize the Writer object. 

42 

43 This should be called by a subclass in its own __init__ method. 

44 

45 Inputs: 

46 ccdata - An instance of ccData, parsed from a logfile. 

47 jobfilename - The filename of the parsed logfile. 

48 indices - One or more indices for extracting specific geometries/etc. (zero-based) 

49 terse - Whether to print the terse version of the output file - currently limited to cjson/json formats 

50 """ 

51 

52 self.ccdata = ccdata 

53 self.jobfilename = jobfilename 

54 self.indices = indices 

55 self.terse = terse 

56 self.ghost = kwargs.get("ghost") 

57 

58 self.pt = PeriodicTable() 

59 

60 self._check_required_attributes() 

61 

62 # Open Babel isn't necessarily present. 

63 if _has_openbabel: 

64 # Generate the Open Babel/Pybel representation of the molecule. 

65 # Used for calculating SMILES/InChI, formula, MW, etc. 

66 self.obmol, self.pbmol = self._make_openbabel_from_ccdata() 

67 self.bond_connectivities = self._make_bond_connectivity_from_openbabel(self.obmol) 

68 

69 self._fix_indices() 

70 

71 @abstractmethod 

72 def generate_repr(self): 

73 """Generate the written representation of the logfile data.""" 

74 

75 def _calculate_total_dipole_moment(self): 

76 """Calculate the total dipole moment.""" 

77 

78 # ccdata.moments may exist, but only contain center-of-mass coordinates 

79 if len(getattr(self.ccdata, 'moments', [])) > 1: 

80 return numpy.linalg.norm(self.ccdata.moments[1]) 

81 

82 def _check_required_attributes(self): 

83 """Check if required attributes are present in ccdata.""" 

84 missing = [x for x in self.required_attrs 

85 if not hasattr(self.ccdata, x)] 

86 if missing: 

87 missing = ' '.join(missing) 

88 raise MissingAttributeError( 

89 'Could not parse required attributes to write file: ' + missing) 

90 

91 def _make_openbabel_from_ccdata(self): 

92 """Create Open Babel and Pybel molecules from ccData.""" 

93 if not hasattr(self.ccdata, 'charge'): 

94 logging.warning("ccdata object does not have charge, setting to 0") 

95 _charge = 0 

96 else: 

97 _charge = self.ccdata.charge 

98 if not hasattr(self.ccdata, 'mult'): 

99 logging.warning("ccdata object does not have spin multiplicity, setting to 1") 

100 _mult = 1 

101 else: 

102 _mult = self.ccdata.mult 

103 obmol = makeopenbabel(self.ccdata.atomcoords, 

104 self.ccdata.atomnos, 

105 charge=_charge, 

106 mult=_mult) 

107 if self.jobfilename is not None: 

108 obmol.SetTitle(self.jobfilename) 

109 return (obmol, pb.Molecule(obmol)) 

110 

111 def _make_bond_connectivity_from_openbabel(self, obmol): 

112 """Based upon the Open Babel/Pybel molecule, create a list of tuples 

113 to represent bonding information, where the three integers are 

114 the index of the starting atom, the index of the ending atom, 

115 and the bond order. 

116 """ 

117 bond_connectivities = [] 

118 for obbond in ob.OBMolBondIter(obmol): 

119 bond_connectivities.append((obbond.GetBeginAtom().GetIndex(), 

120 obbond.GetEndAtom().GetIndex(), 

121 obbond.GetBondOrder())) 

122 return bond_connectivities 

123 

124 def _fix_indices(self): 

125 """Clean up the index container type and remove zero-based indices to 

126 prevent duplicate structures and incorrect ordering when 

127 indices are later sorted. 

128 """ 

129 if not self.indices: 

130 self.indices = set() 

131 elif not isinstance(self.indices, Iterable): 

132 self.indices = set([self.indices]) 

133 # This is the most likely place to get the number of 

134 # geometries from. 

135 if hasattr(self.ccdata, 'atomcoords'): 

136 lencoords = len(self.ccdata.atomcoords) 

137 indices = set() 

138 for i in self.indices: 

139 if i < 0: 

140 i += lencoords 

141 indices.add(i) 

142 self.indices = indices 

143 return 

144 

145 

146del find_package