1
2
3
4
5
6
7
8
9 """
10 pybel - A Cinfony module for accessing Open Babel
11
12 Global variables:
13 ob - the underlying SWIG bindings for Open Babel
14 informats - a dictionary of supported input formats
15 outformats - a dictionary of supported output formats
16 descs - a list of supported descriptors
17 fps - a list of supported fingerprint types
18 forcefields - a list of supported forcefields
19 """
20
21 import sys
22 import math
23 import os.path
24 import tempfile
25
26 if sys.platform[:4] == "java":
27 import org.openbabel as ob
28 import java.lang.System
29 java.lang.System.loadLibrary("openbabel_java")
30 _obfuncs = ob.openbabel_java
31 _obconsts = ob.openbabel_javaConstants
32 import javax
33 elif sys.platform[:3] == "cli":
34 import System
35 import clr
36 clr.AddReference('System.Windows.Forms')
37 clr.AddReference('System.Drawing')
38
39 from System.Windows.Forms import (
40 Application, DockStyle, Form, PictureBox, PictureBoxSizeMode
41 )
42 from System.Drawing import Image, Size
43
44 _obdotnet = os.environ["OBDOTNET"]
45 if _obdotnet[0] == '"':
46 _obdotnet = _obdotnet[1:-1]
47 clr.AddReferenceToFileAndPath(os.path.join(_obdotnet, "OBDotNet.dll"))
48 import OpenBabel as ob
49 _obfuncs = ob.openbabel_csharp
50 _obconsts = ob.openbabel_csharp
51 else:
52 import openbabel as ob
53 _obfuncs = _obconsts = ob
54 try:
55 import Tkinter as tk
56 import Image as PIL
57 import ImageTk as piltk
58 except ImportError:
59 tk = None
67 _obconv = ob.OBConversion()
68 _builder = ob.OBBuilder()
69 informats = _formatstodict(_obconv.GetSupportedInputFormat())
70 """A dictionary of supported input formats"""
71 outformats = _formatstodict(_obconv.GetSupportedOutputFormat())
72 """A dictionary of supported output formats"""
75 plugins = dict([(x, findplugin(x)) for x in names if findplugin(x)])
76 return plugins
78 if sys.platform[:4] == "cli":
79 plugins = ob.VectorString()
80 else:
81 plugins = ob.vectorString()
82 ob.OBPlugin.ListAsVector(ptype, None, plugins)
83 if sys.platform[:4] == "java":
84 plugins = [plugins.get(i) for i in range(plugins.size())]
85 return [x.split()[0] for x in plugins]
86
87 descs = _getpluginnames("descriptors")
88 """A list of supported descriptors"""
89 _descdict = _getplugins(ob.OBDescriptor.FindType, descs)
90 fps = [_x.lower() for _x in _getpluginnames("fingerprints")]
91 """A list of supported fingerprint types"""
92 _fingerprinters = _getplugins(ob.OBFingerprint.FindFingerprint, fps)
93 forcefields = [_x.lower() for _x in _getpluginnames("forcefields")]
94 """A list of supported forcefields"""
95 _forcefields = _getplugins(ob.OBForceField.FindType, forcefields)
96 operations = _getpluginnames("ops")
97 """A list of supported operations"""
98 _operations = _getplugins(ob.OBOp.FindType, operations)
99
100 -def readfile(format, filename, opt=None):
101 """Iterate over the molecules in a file.
102
103 Required parameters:
104 format - see the informats variable for a list of available
105 input formats
106 filename
107
108 Optional parameters:
109 opt - a dictionary of format-specific options
110 For format options with no parameters, specify the
111 value as None.
112
113 You can access the first molecule in a file using the next() method
114 of the iterator (or the next() keyword in Python 3):
115 mol = readfile("smi", "myfile.smi").next() # Python 2
116 mol = next(readfile("smi", "myfile.smi")) # Python 3
117
118 You can make a list of the molecules in a file using:
119 mols = list(readfile("smi", "myfile.smi"))
120
121 You can iterate over the molecules in a file as shown in the
122 following code snippet:
123 >>> atomtotal = 0
124 >>> for mol in readfile("sdf", "head.sdf"):
125 ... atomtotal += len(mol.atoms)
126 ...
127 >>> print atomtotal
128 43
129 """
130 if opt == None:
131 opt = {}
132 obconversion = ob.OBConversion()
133 formatok = obconversion.SetInFormat(format)
134 for k, v in opt.items():
135 if v == None:
136 obconversion.AddOption(k, obconversion.INOPTIONS)
137 else:
138 obconversion.AddOption(k, obconversion.INOPTIONS, str(v))
139 if not formatok:
140 raise ValueError("%s is not a recognised Open Babel format" % format)
141 if not os.path.isfile(filename):
142 raise IOError("No such file: '%s'" % filename)
143 def filereader():
144 obmol = ob.OBMol()
145 notatend = obconversion.ReadFile(obmol,filename)
146 while notatend:
147 yield Molecule(obmol)
148 obmol = ob.OBMol()
149 notatend = obconversion.Read(obmol)
150 return filereader()
151
153 """Read in a molecule from a string.
154
155 Required parameters:
156 format - see the informats variable for a list of available
157 input formats
158 string
159
160 Optional parameters:
161 opt - a dictionary of format-specific options
162 For format options with no parameters, specify the
163 value as None.
164
165 Example:
166 >>> input = "C1=CC=CS1"
167 >>> mymol = readstring("smi", input)
168 >>> len(mymol.atoms)
169 5
170 """
171 if opt == None:
172 opt = {}
173
174 obmol = ob.OBMol()
175 obconversion = ob.OBConversion()
176
177 formatok = obconversion.SetInFormat(format)
178 if not formatok:
179 raise ValueError("%s is not a recognised Open Babel format" % format)
180 for k, v in opt.items():
181 if v == None:
182 obconversion.AddOption(k, obconversion.INOPTIONS)
183 else:
184 obconversion.AddOption(k, obconversion.INOPTIONS, str(v))
185
186 success = obconversion.ReadString(obmol, string)
187 if not success:
188 raise IOError("Failed to convert '%s' to format '%s'" % (
189 string, format))
190 return Molecule(obmol)
191
193 """Represent a file to which *output* is to be sent.
194
195 Although it's possible to write a single molecule to a file by
196 calling the write() method of a molecule, if multiple molecules
197 are to be written to the same file you should use the Outputfile
198 class.
199
200 Required parameters:
201 format - see the outformats variable for a list of available
202 output formats
203 filename
204
205 Optional parameters:
206 overwrite -- if the output file already exists, should it
207 be overwritten? (default is False)
208 opt -- a dictionary of format-specific options
209 For format options with no parameters, specify the
210 value as None.
211
212 Methods:
213 write(molecule)
214 close()
215 """
216 - def __init__(self, format, filename, overwrite=False, opt=None):
217 if opt == None:
218 opt = {}
219 self.format = format
220 self.filename = filename
221 if not overwrite and os.path.isfile(self.filename):
222 raise IOError("%s already exists. Use 'overwrite=True' to overwrite it." % self.filename)
223
224 self.obConversion = ob.OBConversion()
225 formatok = self.obConversion.SetOutFormat(self.format)
226 if not formatok:
227 raise ValueError("%s is not a recognised Open Babel format" % format)
228
229 for k, v in opt.items():
230 if v == None:
231 self.obConversion.AddOption(k, self.obConversion.OUTOPTIONS)
232 else:
233 self.obConversion.AddOption(k, self.obConversion.OUTOPTIONS, str(v))
234 self.total = 0
235
236 - def write(self, molecule):
237 """Write a molecule to the output file.
238
239 Required parameters:
240 molecule
241 """
242 if not self.filename:
243 raise IOError("Outputfile instance is closed.")
244
245 if self.total==0:
246 self.obConversion.WriteFile(molecule.OBMol, self.filename)
247 else:
248 self.obConversion.Write(molecule.OBMol)
249 self.total += 1
250
252 """Close the Outputfile to further writing."""
253 self.obConversion.CloseOutFile()
254 self.filename = None
255
257 """Represent a Pybel Molecule.
258
259 Required parameter:
260 OBMol -- an Open Babel OBMol or any type of cinfony Molecule
261
262 Attributes:
263 atoms, charge, conformers, data, dim, energy, exactmass, formula,
264 molwt, spin, sssr, title, unitcell.
265 (refer to the Open Babel library documentation for more info).
266
267 Methods:
268 addh(), calcfp(), calcdesc(), draw(), localopt(), make3D(), removeh(),
269 write()
270
271 The underlying Open Babel molecule can be accessed using the attribute:
272 OBMol
273 """
274 _cinfony = True
275
277
278 if hasattr(OBMol, "_cinfony"):
279 a, b = OBMol._exchange
280 if a == 0:
281 mol = readstring("smi", b)
282 else:
283 mol = readstring("mol", b)
284 OBMol = mol.OBMol
285
286 self.OBMol = OBMol
287
288 @property
290 return [ Atom(self.OBMol.GetAtom(i+1)) for i in range(self.OBMol.NumAtoms()) ]
291 @property
292 - def charge(self): return self.OBMol.GetTotalCharge()
293 @property
295 @property
297 @property
298 - def dim(self): return self.OBMol.GetDimension()
299 @property
300 - def energy(self): return self.OBMol.GetEnergy()
301 @property
302 - def exactmass(self): return self.OBMol.GetExactMass()
303 @property
305 @property
306 - def molwt(self): return self.OBMol.GetMolWt()
307 @property
308 - def spin(self): return self.OBMol.GetTotalSpinMultiplicity()
309 @property
310 - def sssr(self): return self.OBMol.GetSSSR()
311 - def _gettitle(self): return self.OBMol.GetTitle()
312 - def _settitle(self, val): self.OBMol.SetTitle(val)
313 title = property(_gettitle, _settitle)
314 @property
316 unitcell_index = _obconsts.UnitCell
317 if sys.platform[:3] == "cli":
318 unitcell_index = System.UInt32(unitcell_index)
319 unitcell = self.OBMol.GetData(unitcell_index)
320 if unitcell:
321 if sys.platform[:3] != "cli":
322 return _obfuncs.toUnitCell(unitcell)
323 else:
324 return unitcell.Downcast[ob.OBUnitCell]()
325 else:
326 raise AttributeError("Molecule has no attribute 'unitcell'")
327 @property
329 if self.OBMol.HasNonZeroCoords():
330 return (1, self.write("mol"))
331 else:
332 return (0, self.write("can").split()[0])
333
335 """Iterate over the Atoms of the Molecule.
336
337 This allows constructions such as the following:
338 for atom in mymol:
339 print atom
340 """
341 return iter(self.atoms)
342
344 """Calculate descriptor values.
345
346 Optional parameter:
347 descnames -- a list of names of descriptors
348
349 If descnames is not specified, all available descriptors are
350 calculated. See the descs variable for a list of available
351 descriptors.
352 """
353 if not descnames:
354 descnames = descs
355 ans = {}
356 for descname in descnames:
357 try:
358 desc = _descdict[descname]
359 except KeyError:
360 raise ValueError("%s is not a recognised Open Babel descriptor type" % descname)
361 ans[descname] = desc.Predict(self.OBMol)
362 return ans
363
364 - def calcfp(self, fptype="FP2"):
365 """Calculate a molecular fingerprint.
366
367 Optional parameters:
368 fptype -- the fingerprint type (default is "FP2"). See the
369 fps variable for a list of of available fingerprint
370 types.
371 """
372 if sys.platform[:3] == "cli":
373 fp = ob.VectorUInt()
374 else:
375 fp = ob.vectorUnsignedInt()
376 fptype = fptype.lower()
377 try:
378 fingerprinter = _fingerprinters[fptype]
379 except KeyError:
380 raise ValueError("%s is not a recognised Open Babel Fingerprint type" % fptype)
381 fingerprinter.GetFingerprint(self.OBMol, fp)
382 return Fingerprint(fp)
383
384 - def write(self, format="smi", filename=None, overwrite=False, opt=None):
385 """Write the molecule to a file or return a string.
386
387 Optional parameters:
388 format -- see the informats variable for a list of available
389 output formats (default is "smi")
390 filename -- default is None
391 overwite -- if the output file already exists, should it
392 be overwritten? (default is False)
393 opt -- a dictionary of format specific options
394 For format options with no parameters, specify the
395 value as None.
396
397 If a filename is specified, the result is written to a file.
398 Otherwise, a string is returned containing the result.
399
400 To write multiple molecules to the same file you should use
401 the Outputfile class.
402 """
403 if opt == None:
404 opt = {}
405 obconversion = ob.OBConversion()
406 formatok = obconversion.SetOutFormat(format)
407 if not formatok:
408 raise ValueError("%s is not a recognised Open Babel format" % format)
409 for k, v in opt.items():
410 if v == None:
411 obconversion.AddOption(k, obconversion.OUTOPTIONS)
412 else:
413 obconversion.AddOption(k, obconversion.OUTOPTIONS, str(v))
414
415 if filename:
416 if not overwrite and os.path.isfile(filename):
417 raise IOError("%s already exists. Use 'overwrite=True' to overwrite it." % filename)
418 obconversion.WriteFile(self.OBMol,filename)
419 obconversion.CloseOutFile()
420 else:
421 return obconversion.WriteString(self.OBMol)
422
423 - def localopt(self, forcefield="mmff94", steps=500):
424 """Locally optimize the coordinates.
425
426 Optional parameters:
427 forcefield -- default is "mmff94". See the forcefields variable
428 for a list of available forcefields.
429 steps -- default is 500
430
431 If the molecule does not have any coordinates, make3D() is
432 called before the optimization. Note that the molecule needs
433 to have explicit hydrogens. If not, call addh().
434 """
435 forcefield = forcefield.lower()
436 if self.dim != 3:
437 self.make3D(forcefield)
438 ff = _forcefields[forcefield]
439 success = ff.Setup(self.OBMol)
440 if not success:
441 return
442 ff.SteepestDescent(steps)
443 ff.GetCoordinates(self.OBMol)
444
445
446
447
448
449
450
451
452
453
454
455 - def make3D(self, forcefield = "mmff94", steps = 50):
456 """Generate 3D coordinates.
457
458 Optional parameters:
459 forcefield -- default is "mmff94". See the forcefields variable
460 for a list of available forcefields.
461 steps -- default is 50
462
463 Once coordinates are generated, hydrogens are added and a quick
464 local optimization is carried out with 50 steps and the
465 MMFF94 forcefield. Call localopt() if you want
466 to improve the coordinates further.
467 """
468 forcefield = forcefield.lower()
469 _builder.Build(self.OBMol)
470 self.addh()
471 self.localopt(forcefield, steps)
472
474 """Add hydrogens."""
475 self.OBMol.AddHydrogens()
476
478 """Remove hydrogens."""
479 self.OBMol.DeleteHydrogens()
480
483
484 - def draw(self, show=True, filename=None, update=False, usecoords=False):
485 """Create a 2D depiction of the molecule.
486
487 Optional parameters:
488 show -- display on screen (default is True)
489 filename -- write to file (default is None)
490 update -- update the coordinates of the atoms to those
491 determined by the structure diagram generator
492 (default is False)
493 usecoords -- don't calculate 2D coordinates, just use
494 the current coordinates (default is False)
495
496 Tkinter and Python Imaging Library are required for image display.
497 """
498 obconversion = ob.OBConversion()
499 formatok = obconversion.SetOutFormat("_png2")
500 if not formatok:
501 errormessage = ("PNG depiction support not found. You should compile "
502 "Open Babel with support for Cairo. See installation "
503 "instructions for more information.")
504 raise ImportError(errormessage)
505
506
507 workingmol = Molecule(ob.OBMol(self.OBMol))
508 workingmol.removeh()
509
510 if not usecoords:
511 _operations['gen2D'].Do(workingmol.OBMol)
512 if update == True:
513 if workingmol.OBMol.NumAtoms() != self.OBMol.NumAtoms():
514 errormessage = ("It is not possible to update the original molecule "
515 "with the calculated coordinates, as the original "
516 "molecule contains explicit hydrogens for which no "
517 "coordinates have been calculated.")
518 raise RuntimeError(errormessage)
519 else:
520 for i in range(workingmol.OBMol.NumAtoms()):
521 self.OBMol.GetAtom(i + 1).SetVector(workingmol.OBMol.GetAtom(i + 1).GetVector())
522
523 if filename:
524 filedes = None
525 else:
526 if sys.platform[:3] == "cli" and show:
527 errormessage = ("It is only possible to show the molecule if you "
528 "provide a filename. The reason for this is that I kept "
529 "having problems when using temporary files.")
530 raise RuntimeError(errormessage)
531
532 filedes, filename = tempfile.mkstemp()
533
534 workingmol.write("_png2", filename=filename, overwrite=True)
535
536 if show:
537 if sys.platform[:4] == "java":
538 image = javax.imageio.ImageIO.read(java.io.File(filename))
539 frame = javax.swing.JFrame(visible=1)
540 frame.getContentPane().add(javax.swing.JLabel(javax.swing.ImageIcon(image)))
541 frame.setSize(300,300)
542 frame.setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE)
543 frame.show()
544 elif sys.platform[:3] == "cli":
545 form = _MyForm()
546 form.setup(filename, self.title)
547 Application.Run(form)
548 else:
549 if not tk:
550 errormessage = ("Tkinter or Python Imaging "
551 "Library not found, but is required for image "
552 "display. See installation instructions for "
553 "more information.")
554 raise ImportError(errormessage)
555 root = tk.Tk()
556 root.title((hasattr(self, "title") and self.title)
557 or self.__str__().rstrip())
558 frame = tk.Frame(root, colormap="new", visual='truecolor').pack()
559 image = PIL.open(filename)
560 imagedata = piltk.PhotoImage(image)
561 label = tk.Label(frame, image=imagedata).pack()
562 quitbutton = tk.Button(root, text="Close", command=root.destroy).pack(fill=tk.X)
563 root.mainloop()
564 if filedes:
565 os.close(filedes)
566 os.remove(filename)
567
569 """Represent a Pybel atom.
570
571 Required parameter:
572 OBAtom -- an Open Babel OBAtom
573
574 Attributes:
575 atomicmass, atomicnum, cidx, coords, coordidx, exactmass,
576 formalcharge, heavyvalence, heterovalence, hyb, idx,
577 implicitvalence, isotope, partialcharge, spin, type,
578 valence, vector.
579
580 (refer to the Open Babel library documentation for more info).
581
582 The original Open Babel atom can be accessed using the attribute:
583 OBAtom
584 """
585
588
589 @property
591 return (self.OBAtom.GetX(), self.OBAtom.GetY(), self.OBAtom.GetZ())
592 @property
593 - def atomicmass(self): return self.OBAtom.GetAtomicMass()
594 @property
595 - def atomicnum(self): return self.OBAtom.GetAtomicNum()
596 @property
597 - def cidx(self): return self.OBAtom.GetCIdx()
598 @property
599 - def coordidx(self): return self.OBAtom.GetCoordinateIdx()
600 @property
601 - def exactmass(self): return self.OBAtom.GetExactMass()
602 @property
604 @property
606 @property
608 @property
609 - def hyb(self): return self.OBAtom.GetHyb()
610 @property
611 - def idx(self): return self.OBAtom.GetIdx()
612 @property
614 @property
615 - def isotope(self): return self.OBAtom.GetIsotope()
616 @property
618 @property
619 - def spin(self): return self.OBAtom.GetSpinMultiplicity()
620 @property
621 - def type(self): return self.OBAtom.GetType()
622 @property
623 - def valence(self): return self.OBAtom.GetValence()
624 @property
625 - def vector(self): return self.OBAtom.GetVector()
626
628 c = self.coords
629 return "Atom: %d (%.2f %.2f %.2f)" % (self.atomicnum, c[0], c[1], c[2])
630
632 """Find which bits are set in a list/vector.
633
634 This function is used by the Fingerprint class.
635
636 >>> _findbits([13, 71], 8)
637 [1, 3, 4, 9, 10, 11, 15]
638 """
639 ans = []
640 start = 1
641 if sys.platform[:4] == "java":
642 fp = [fp.get(i) for i in range(fp.size())]
643 for x in fp:
644 i = start
645 while x > 0:
646 if x % 2:
647 ans.append(i)
648 x >>= 1
649 i += 1
650 start += bitsperint
651 return ans
652
654 """A Molecular Fingerprint.
655
656 Required parameters:
657 fingerprint -- a vector calculated by OBFingerprint.FindFingerprint()
658
659 Attributes:
660 fp -- the underlying fingerprint object
661 bits -- a list of bits set in the Fingerprint
662
663 Methods:
664 The "|" operator can be used to calculate the Tanimoto coeff. For example,
665 given two Fingerprints 'a', and 'b', the Tanimoto coefficient is given by:
666 tanimoto = a | b
667 """
669 self.fp = fingerprint
671 return ob.OBFingerprint.Tanimoto(self.fp, other.fp)
672 @property
674 return _findbits(self.fp, ob.OBFingerprint.Getbitsperint())
676 fp = self.fp
677 if sys.platform[:4] == "java":
678 fp = [self.fp.get(i) for i in range(self.fp.size())]
679 return ", ".join([str(x) for x in fp])
680
682 """A Smarts Pattern Matcher
683
684 Required parameters:
685 smartspattern
686
687 Methods:
688 findall(molecule)
689
690 Example:
691 >>> mol = readstring("smi","CCN(CC)CC") # triethylamine
692 >>> smarts = Smarts("[#6][#6]") # Matches an ethyl group
693 >>> print smarts.findall(mol)
694 [(1, 2), (4, 5), (6, 7)]
695
696 The numbers returned are the indices (starting from 1) of the atoms
697 that match the SMARTS pattern. In this case, there are three matches
698 for each of the three ethyl groups in the molecule.
699 """
701 """Initialise with a SMARTS pattern."""
702 self.obsmarts = ob.OBSmartsPattern()
703 success = self.obsmarts.Init(smartspattern)
704 if not success:
705 raise IOError("Invalid SMARTS pattern")
707 """Find all matches of the SMARTS pattern to a particular molecule.
708
709 Required parameters:
710 molecule
711 """
712 self.obsmarts.Match(molecule.OBMol)
713 vector = self.obsmarts.GetUMapList()
714 if sys.platform[:4] == "java":
715 vector = [vector.get(i) for i in range(vector.size())]
716 return list(vector)
717
719 """Store molecule data in a dictionary-type object
720
721 Required parameters:
722 obmol -- an Open Babel OBMol
723
724 Methods and accessor methods are like those of a dictionary except
725 that the data is retrieved on-the-fly from the underlying OBMol.
726
727 Example:
728 >>> mol = readfile("sdf", 'head.sdf').next() # Python 2
729 >>> # mol = next(readfile("sdf", 'head.sdf')) # Python 3
730 >>> data = mol.data
731 >>> print data
732 {'Comment': 'CORINA 2.61 0041 25.10.2001', 'NSC': '1'}
733 >>> print len(data), data.keys(), data.has_key("NSC")
734 2 ['Comment', 'NSC'] True
735 >>> print data['Comment']
736 CORINA 2.61 0041 25.10.2001
737 >>> data['Comment'] = 'This is a new comment'
738 >>> for k,v in data.items():
739 ... print k, "-->", v
740 Comment --> This is a new comment
741 NSC --> 1
742 >>> del data['NSC']
743 >>> print len(data), data.keys(), data.has_key("NSC")
744 1 ['Comment'] False
745 """
749 data = self._mol.GetData()
750 if sys.platform[:4] == "java":
751 data = [data.get(i) for i in range(data.size())]
752 answer = [x for x in data if
753 x.GetDataType()==_obconsts.PairData or
754 x.GetDataType()==_obconsts.CommentData]
755 if sys.platform[:3] != "cli":
756 answer = [_obfuncs.toPairData(x) for x in answer]
757 return answer
759 if not key in self:
760 raise KeyError("'%s'" % key)
762 return [x.GetAttribute() for x in self._data()]
764 return [x.GetValue() for x in self._data()]
768 return iter(self.keys())
770 return self.items()
772 return len(self._data())
774 return self._mol.HasData(key)
776 self._testforkey(key)
777 self._mol.DeleteData(self._mol.GetData(key))
779 for key in self:
780 del self[key]
783 - def update(self, dictionary):
784 for k, v in dictionary.items():
785 self[k] = v
787 self._testforkey(key)
788 answer = self._mol.GetData(key)
789 if sys.platform[:3] != "cli":
790 answer = _obfuncs.toPairData(answer)
791 return answer.GetValue()
793 if key in self:
794 if sys.platform[:3] != "cli":
795 pairdata = _obfuncs.toPairData(self._mol.GetData(key))
796 else:
797 pairdata = self._mol.GetData(key).Downcast[ob.OBPairData]()
798 pairdata.SetValue(str(value))
799 else:
800 pairdata = ob.OBPairData()
801 pairdata.SetAttribute(key)
802 pairdata.SetValue(str(value))
803 self._mol.CloneData(pairdata)
806
807 if sys.platform[:3] == "cli":
828
829 if __name__=="__main__":
830 import doctest
831 doctest.testmod(verbose=True)
832