def Summary ( self, log = logFile ):
     """Print a summary of the stored data."""
     if self.QPARSED and LogFileActive ( log ):
         summary = log.GetSummary ( )
         summary.Start ( "CHARMM CRD File Summary" )
         summary.Entry ( "Number of Atom Lines", "{:d}".format ( len ( self.atoms ) ) )
         summary.Stop ( )
示例#2
0
 def Summary ( self, log = logFile ):
     """Summarizing."""
     if LogFileActive ( log ):
         summary = log.GetSummary ( )
         summary.Start ( "Element Container Summary" )
         summary.Entry ( "Number of Elements" , "{:d}".format ( len ( self.items ) ) )
         summary.Stop ( )
 def PathSummary ( self, log = logFile ):
     """Output a path summary."""
     if LogFileActive ( log ):
         # . Calculate all data for the current path.
         variables = Real1DArray.WithExtent ( self.NumberOfVariables ( ) )
         self.VariablesGet ( variables )
         self.Function ( variables )
         # . Get the energies of the first and last structures.
         self.energies[0:0] = [ self.objectiveFunction.Function ( self.x0 ) ]
         self.energies.append ( self.objectiveFunction.Function ( self.xn ) )
         # . Modify the distance lists.
         self.distances1.append ( None )
         self.distances2.extend ( [ None, None ] )
         # . Output.
         table  = log.GetTable ( columns = [ 10, 20, 20, 20 ] )
         table.Start   ( )
         table.Title   ( "Path Summary" )
         table.Heading ( "Structure"    )
         table.Heading ( "Energy"       )
         table.Heading ( "Dist(i,i+1)"  )
         table.Heading ( "Dist(i,i+2)"  )
         for ( i, ( e, d1, d2 ) ) in enumerate ( zip ( self.energies, self.distances1, self.distances2 ) ):
             table.Entry ( "{:d}"  .format ( i ) )
             table.Entry ( "{:.3f}".format ( e ) )
             if d1 is None: table.Entry ( "" )
             else:          table.Entry ( "{:.3f}".format ( d1 ) )
             if d2 is None: table.Entry ( "" )
             else:          table.Entry ( "{:.3f}".format ( d2 ) )
         table.Stop ( )
示例#4
0
 def Summary ( self, log = logFile ):
     """Summary."""
     if self.QPARSED and LogFileActive ( log ):
         # . Parse data.
         summary = log.GetSummary ( )
         summary.Start ( "CHARMM Parameter File Line Summary" )
         keys = self.numberOfSectionLines.keys ( )
         keys.sort ( )
         for key in keys: summary.Entry ( key, "{:d}".format ( self.numberOfSectionLines[key] ) )
         summary.Stop ( )
         # . Processed data.
         summary = log.GetSummary ( )
         summary.Start ( "CHARMM Parameter File Parameter Summary" )
         summary.Entry ( "Atom Types"             , "{:d}".format ( len ( self.atomtypes      ) ) )
         summary.Entry ( "Angles"                 , "{:d}".format ( len ( self.angles         ) ) )
         summary.Entry ( "Bonds"                  , "{:d}".format ( len ( self.bonds          ) ) )
         summary.Entry ( "Cmaps"                  , "{:d}".format ( len ( self.cmaps          ) ) )
         summary.Entry ( "Dihedrals (Nonwild)"    , "{:d}".format ( len ( self.dihedrals      ) ) )
         summary.Entry ( "Dihedrals (Wild)"       , "{:d}".format ( len ( self.dihedralwilds  ) ) )
         summary.Entry ( "Impropers (Nonwild)"    , "{:d}".format ( len ( self.impropers      ) ) )
         summary.Entry ( "Impropers (Wild)"       , "{:d}".format ( len ( self.improperwilds  ) ) )
         summary.Entry ( "Nonbonds (Nonwild)"     , "{:d}".format ( len ( self.nonbonds       ) ) )
         summary.Entry ( "Nonbonds (Wild)"        , "{:d}".format ( len ( self.nonbondwilds   ) ) )
         summary.Entry ( "Nonbonds 1-4 (Nonwild)" , "{:d}".format ( len ( self.nonbond14s     ) ) )
         summary.Entry ( "Nonbonds 1-4 (Wild)"    , "{:d}".format ( len ( self.nonbond14wilds ) ) )
         summary.Entry ( "Urey-Bradleys"          , "{:d}".format ( len ( self.ureybradleys   ) ) )
         summary.Stop ( )
示例#5
0
    def Parse(self, log=logFile):
        """Parse the data on the file."""
        if not self.QPARSED:
            if LogFileActive(log):
                self.log = log

            self.Open()
            atoms = []
            line = None

            # In some files center atom is absent?
            center = None

            try:
                while True:
                    line = self.GetLine(QWARNING=False)
                    tokens = line.split()
                    if len(tokens) > 0:
                        if tokens[0] == "Gmodel":
                            Gmodels = map(float, tokens[1:])
                        if tokens[0] == "proton":
                            protons = map(int, tokens[1:])
                        if tokens[0] == "label":
                            labels = tokens[1:]
                        if tokens[0] == "center":
                            center = tokens[1]

                        if tokens[0] == self.siteLabel:
                            label = tokens[1]
                            charges = map(float, tokens[2:])
                            atoms.append((label, charges))
            except EOFError:
                pass
            self.WarningStop()
            self.Close()
            self.log = None
            self.QPARSED = True

            instances = []
            for instanceIndex, (
                    instanceLabel,
                    instanceGmodel,
                    instanceProtons,
            ) in enumerate(zip(labels, Gmodels, protons)):
                instanceAtoms = []
                instanceCharges = []
                for atomLabel, atomCharges in atoms:
                    instanceAtoms.append(atomLabel)
                    instanceCharges.append(atomCharges[instanceIndex])
                instances.append({
                    "label": instanceLabel,
                    "Gmodel": instanceGmodel,
                    "protons": instanceProtons,
                    "charges": instanceCharges,
                })

            self.siteAtoms = instanceAtoms
            self.siteInstances = instances
            self.siteCenter = center
示例#6
0
 def PrintComponentSequence(self,
                            log=logFile,
                            title="Sequence Component Sequence"):
     """Print the component sequences."""
     if LogFileActive(log):
         log.Heading(title)
         for entity in self.children:
             entity.PrintComponentSequence(log=log)
示例#7
0
 def Summary(self, log=logFile):
     """Summary."""
     if LogFileActive(log):
         summary = log.GetSummary()
         summary.Start("Electronic State")
         summary.Entry("Charge", "{:d}".format(self._charge))
         summary.Entry("Multiplicity", "{:d}".format(self._multiplicity))
         summary.Stop()
示例#8
0
 def Summary ( self, log = logFile ):
     """Summary."""
     if LogFileActive ( log ):
         summary = log.GetSummary ( )
         summary.Start ( self.Label ( ) + " Summary" )
         summary.Entry ( "Number of Atoms"    , "{:d}".format ( len ( self.system.atoms ) ) )
         summary.Entry ( "Number of Patterns" , "{:d}".format ( len ( self.patterns     ) ) )
         summary.Stop ( )
示例#9
0
def ExportSystem ( path, system, **options ):
    """Export a system."""
    format  = options.pop ( "format", None    )
    log     = options.pop ( "log"   , logFile )
    handler = _Exporter.GetHandler ( path, format = format )
    handler.ExportObject ( path, system, **options )
    if LogFileActive ( log ):
        log.Paragraph ( "System exported to \"{:s}\" in {:s} format.".format ( path, handler.label ) )
示例#10
0
 def Summary(self, log=logFile):
     """Summary."""
     if LogFileActive(log):
         summary = log.GetSummary()
         summary.Start("SGOF Process Pool Options")
         summary.Entry("Pool Type", self.poolType)
         summary.Entry("Processes", "{:d}".format(self.maximumProcesses))
         summary.Stop()
示例#11
0
 def Summary ( self, log = logFile ):
     """Summary."""
     if self.QPARSED and LogFileActive ( log ):
         summary = log.GetSummary ( )
         summary.Start ( "Amber Topology File Summary" )
         for ( name, i ) in zip ( _NAMES, self.pointers ):
             summary.Entry ( name, "{:d}".format ( i ) )
         summary.Stop ( )
 def Summary ( self, log = logFile ):
     """Summary."""
     if LogFileActive ( log ):
         summary = log.GetSummary ( pageWidth = 90, valueWidth = 12 )
         summary.Start ( "Hard Constraint Container Summary" )
         summary.Entry ( "Number of Charge Constraints", "{:d}".format ( self.NumberOfChargeConstraints ( ) ) )
         summary.Entry ( "Number of Fixed Atoms"       , "{:d}".format ( self.NumberOfFixedAtoms        ( ) ) )
         summary.Stop ( )
示例#13
0
def IdentifyUndefinedCoordinates3(system,
                                  log=logFile,
                                  printHeavies=True,
                                  printHydrogens=False,
                                  usePDBNotation=True):
    """Identify any undefined coordinates."""
    # . Initialization.
    numberOfHeavies = 0
    numberOfHydrogens = 0

    # . See if there are undefined coordinates.
    if system.coordinates3.numberUndefined > 0:

        # . Get the indices of the undefined coordinates.
        undefined = system.coordinates3.undefined

        # . Find numbers.
        heavies = []
        hydrogens = []
        for i in undefined:
            n = system.atoms[i].atomicNumber
            if n == 1: hydrogens.append(i)
            else: heavies.append(i)
        numberOfHeavies = len(heavies)
        numberOfHydrogens = len(hydrogens)

        # . Set some options.
        usePDBNotation = usePDBNotation and (system.sequence is not None)

        # . Print the results.
        if LogFileActive(log):

            # . Basic summary.
            summary = log.GetSummary()
            summary.Start("Undefined Coordinates3 Summary")
            summary.Entry("Undefined Heavy Atoms",
                          "{:d}".format(numberOfHeavies))
            summary.Entry("Undefined Hydrogens",
                          "{:d}".format(numberOfHydrogens))
            summary.Stop()

            # . Explicit listings.
            for (doPrinting, indices,
                 tag) in ((printHeavies, heavies, "Heavy"),
                          (printHydrogens, hydrogens, "Hydrogen")):
                n = len(indices)
                if doPrinting and (n > 0):
                    if usePDBNotation:
                        table = log.GetTable(columns=min(5, n) * [20])
                    else:
                        table = log.GetTable(columns=min(10, n) * [10])
                    table.Start()
                    table.Title("Undefined " + tag + " Atoms")
                    for i in indices:
                        table.Entry(system.atoms[i].path)
                    table.Stop()
    # . Return.
    return (numberOfHeavies, numberOfHydrogens)
 def GeometryOptimizationSummary ( self, optimizationResults, log = logFile ):
     """Output for geometry optimization results."""
     if LogFileActive ( log ) and ( len ( optimizationResults ) > 0 ):
         # . Parameters.
         tableEnergyWidth    = 20
         tableIntegerWidth   = 20
         tableNameWidth      = 30
         tableParameterWidth = 20
         # . System labels.
         labels = optimizationResults.keys ( )
         labels.sort ( )
         # . Energies.
         # . Header.
         table = log.GetTable ( columns = [ tableNameWidth, tableEnergyWidth, tableEnergyWidth, tableEnergyWidth, tableIntegerWidth,tableIntegerWidth ] )
         table.Start   ( )
         table.Title   ( "Optimized Energies" )
         table.Heading ( "Label"           )
         table.Heading ( "Initial Energy"  )
         table.Heading ( "Final Energy"    )
         table.Heading ( "Energy Lowering" )
         table.Heading ( "Function Calls"  )
         table.Heading ( "Convergence"     )
         # . Data.
         for label in labels:
             data = optimizationResults[label]
             table.Entry ( label, alignment = "left" )
             table.Entry ( "{:20.4f}".format ( data["Initial Energy"]                          ) )
             table.Entry ( "{:20.4f}".format ( data["Final Energy"  ]                          ) )
             table.Entry ( "{:20.4f}".format ( data["Final Energy"  ] - data["Initial Energy"] ) )
             table.Entry ( "{:d}".format ( data["Function Calls"] ) )
             if data["Converged"]: table.Entry ( "T" )
             else:                 table.Entry ( "F" )
         table.Stop ( )
         # . Symmetry parameters.
         if self.useSymmetry:
             table = log.GetTable ( columns = [ tableNameWidth, tableParameterWidth, tableEnergyWidth, tableEnergyWidth, tableEnergyWidth ] )
             table.Start  ( )
             table.Title  ( "Optimized Symmetry Parameters" )
             table.Heading ( "Label"      )
             table.Heading ( "Parameter"  )
             table.Heading ( "Initial"    )
             table.Heading ( "Final"      )
             table.Heading ( "Difference" )
             for label in labels:
                 # . Get the data.
                 data      = optimizationResults[label]
                 spInitial = data["Initial Parameters"]
                 spFinal   = data["Final Parameters"  ]
                 spLabels  = spInitial.keys ( )
                 spLabels.sort ( )
                 for ( i, spLabel ) in enumerate ( spLabels ):
                     if i == 0: table.Entry ( label, alignment = "left" )
                     else:      table.Entry ( "" )
                     table.Entry ( spLabel, alignment = "left" )
                     table.Entry ( "{:.4f}".format ( spInitial[spLabel]                    ) )
                     table.Entry ( "{:.4f}".format ( spFinal  [spLabel]                    ) )
                     table.Entry ( "{:.4f}".format ( spInitial[spLabel] - spFinal[spLabel] ) )
             table.Stop ( )
示例#15
0
def ImportSystem ( path, **options ):
    """Import a system."""
    format  = options.pop ( "format", None    )
    log     = options.pop ( "log"   , logFile )
    handler = _Importer.GetHandler ( path, format = format )
    item    = handler.ImportObject ( path, System, **options )
    if LogFileActive ( log ):
        log.Paragraph( "System imported from \"{:s}\" in {:s} format.".format ( path, handler.label ) )
    return item
示例#16
0
def ImportCoordinates3 ( path, **options ):
    """Import a set of coordinates."""
    format  = options.pop ( "format", None    )
    log     = options.pop ( "log"   , logFile )
    handler = _Importer.GetHandler ( path, format = format )
    item    = handler.ImportObject ( path, Coordinates3, **options )
    if LogFileActive ( log ):
        log.Paragraph( "Coordinates3 imported from \"{:s}\" in {:s} format.".format ( path, handler.label ) )
    return item
 def Summary(self, log=logFile):
     """Summary of a log of path data."""
     if LogFileActive(log):
         self.BuildFunctionSplines()
         self.BuildFunctionProfile()
         self.FindMaxima()
         self.SummaryPath(log=log)
         self.SummaryFunctionProfile(log=log)
         self.SummaryMaxima(log=log)
示例#18
0
 def Summary ( self, log = logFile ):
     """Summary."""
     if LogFileActive ( log ):
         if self.crystalClass is not None:
             summary = log.GetSummary ( )
             summary.Start ( "Symmetry Summary" )
             summary.Entry ( "Crystal Class", self.crystalClass.Label ( ) )
             summary.Stop ( )
         if self.transformations is not None:
             self.transformations.Summary ( log = log )
示例#19
0
 def Summary (self, log=logFile):
     """Summary."""
     if LogFileActive (log):
         summary = log.GetSummary ()
         summary.Start ("GMCT Monte Carlo sampling model")
         summary.Entry ("Equilibration scans"    , "%d"   % self.equilibrationScans)
         summary.Entry ("Production scans"       , "%d"   % self.productionScans)
         summary.Entry ("Limit for double moves" , "%.1f" % self.doubleFlip)
         summary.Entry ("Limit for triple moves" , "%.1f" % self.tripleFlip)
         summary.Stop ()
 def Summary ( self, log = logFile, pageWidth = 100, valueWidth = 14 ):
     """Summary."""
     if LogFileActive ( log ):
         summary = log.GetSummary ( pageWidth = pageWidth, valueWidth = valueWidth )
         summary.Start ( "Self-Avoiding Walk Options" )
         summary.Entry ( "Gamma" , "{:.3g}".format ( self.gamma ) )
         summary.Entry ( "Kappa" , "{:.3g}".format ( self.kappa ) )
         summary.Entry ( "Rho"   , "{:.3g}".format ( self.rho   ) )
         summary.Entry ( "Remove Rotation Translation" , "{!r}".format ( self.removeRotationTranslation ) )
         summary.Stop ( )
示例#21
0
    def Summary(self,
                relativeEnergy=True,
                roundCharge=True,
                title="",
                log=logFile):
        """Summarize calculated substate energies in a table."""
        if self.isCalculated:
            indicesOfSites = self.indicesOfSites
            zeroEnergy = self.zeroEnergy
            substates = self.substates
            owner = self.owner
            nsites = len(indicesOfSites)

            if LogFileActive(log):
                tab = log.GetTable(columns=[6, 9, 8, 8] + [14] * nsites)
                tab.Start()
                if title:
                    tab.Title(title)
                tab.Heading("State")
                tab.Heading("Gmicro")
                tab.Heading("Charge")
                tab.Heading("Protons")

                for siteIndex in indicesOfSites:
                    site = owner.sites[siteIndex]
                    tab.Heading("%s %s %d" %
                                (site.segName, site.resName, site.resSerial))

                for stateIndex, (energy,
                                 indicesOfInstances) in enumerate(substates):
                    tab.Entry("%6d" % (stateIndex + 1))
                    if relativeEnergy:
                        energy = energy - zeroEnergy
                    tab.Entry("%9.2f" % energy)

                    nprotons = 0
                    charge = 0.0
                    labels = []
                    for siteIndex, instanceIndex in zip(
                            indicesOfSites, indicesOfInstances):
                        site = owner.sites[siteIndex]
                        instance = site.instances[instanceIndex]
                        nprotons = nprotons + instance.protons
                        charge = charge + sum(instance.charges)
                        labels.append(instance.label)

                    # Charges should ALWAYS sum up to integer values
                    if roundCharge:
                        tab.Entry("%d" % round(charge))
                    else:
                        tab.Entry("%.1f" % charge)
                    tab.Entry("%d" % nprotons)
                    for label in labels:
                        tab.Entry(label.center(14))
                tab.Stop()
 def Summary(self, log=logFile):
     """Summary."""
     if self.QPARSED and LogFileActive(log):
         # . Parse data.
         summary = log.GetSummary()
         summary.Start("CHARMM PSF File Summary")
         keys = self.counters.keys()
         keys.sort()
         for key in keys:
             summary.Entry(key, "{:d}".format(self.counters[key]))
         summary.Stop()
示例#23
0
 def Summary(self, log=logFile):
     """Summary."""
     if LogFileActive(log):
         summary = log.GetSummary()
         if self.label is None: summary.Start("PDB Component Summary")
         else:
             summary.Start("Summary for PDB Component " +
                           self.label.upper())
         summary.Entry("Number of Atoms", "{:d}".format(len(self.atoms)))
         summary.Entry("Number of Bonds", "{:d}".format(len(self.bonds)))
         summary.Entry("Formal Charge", "{:d}".format(self.formalCharge))
         summary.Stop()
示例#24
0
 def PrintComponentSequence(self, log=logFile, title=None):
     """Print the component sequence of the entity."""
     if LogFileActive(log):
         length = min(8, len(self.children))
         table = log.GetTable(columns=length * [15])
         table.Start()
         if title is None: table.Title(self.Label() + " Component Sequence")
         else: table.Title(title)
         table.Title(title)
         for component in self.children:
             table.Entry(component.label, alignment="r")
         table.Stop()
def BuildSolventBox ( crystalClass, symmetryParameters, molecule, density, log = logFile, randomNumberGenerator = None ):
    """Build a solvent box.

    No attempt is made to avoid overlapping molecules as it is assumed this will be corrected in a subsequent optimization process.
    """

    # . Initialization.
    solvent = None

    # . Find the number of solvent molecules that fit in the box.
    nmolecules = SolventMoleculeNumber ( molecule, symmetryParameters, density )

    # . Check the number of molecules.
    if nmolecules > 0:

        # . Create the solvent system.
        molecule.coordinates3.ToPrincipalAxes ( )
        solvent = MergeRepeatByAtom ( molecule, nmolecules )
        solvent.DefineSymmetry ( crystalClass = crystalClass, a     = symmetryParameters.a,     \
                                                              b     = symmetryParameters.b,     \
                                                              c     = symmetryParameters.c,     \
                                                              alpha = symmetryParameters.alpha, \
                                                              beta  = symmetryParameters.beta,  \
                                                              gamma = symmetryParameters.gamma  )

        # . Check the random number generator.
        if randomNumberGenerator is None: randomNumberGenerator = RandomNumberGenerator.WithRandomSeed ( )

        # . Do random rotations and translations.
        natoms      = len ( molecule.atoms )
        rotation    = Matrix33.Null ( )
        selection   = Selection.FromIterable ( range ( natoms ) )
        translation = Vector3.Null ( )
        for i in range ( nmolecules ):
            rotation.RandomRotation ( randomNumberGenerator )
            solvent.coordinates3.Rotate  ( rotation,    selection = selection )
            for j in range ( 3 ): translation[j] = randomNumberGenerator.NextReal ( )
            symmetryParameters.M.ApplyTo ( translation )
            solvent.coordinates3.Translate ( translation, selection = selection )
            selection.Increment ( natoms )

        # . Do some printing.
        if LogFileActive ( log ):
            summary = log.GetSummary ( )
            summary.Start ( "Cubic Solvent Box Summary" )
            summary.Entry ( "Number of Molecules", "{:d}"  .format ( nmolecules                        ) )
            summary.Entry ( "Density (kg m^-3)",   "{:.3f}".format ( SystemDensity ( solvent )         ) )
            summary.Entry ( "Box Volume",          "{:.3f}".format ( solvent.symmetryParameters.volume ) )
            summary.Stop ( )

    # . Return the system.
    return solvent
    def Parse(self, log=logFile):
        """Parse the data on the file."""
        if not self.QPARSED:
            # . Initialization.
            if LogFileActive(log): self.log = log
            # . Open the file.
            self.Open()
            try:
                # . Header line.
                line = self.GetLine()
                hasNormals = (line.find("N") > -1)
                if line.find("n") > -1:
                    items = self.GetTokens(converters=[int])
                    ndimensions = items[0]
                else:
                    ndimensions = 3
                # . Counters line.
                items = self.GetTokens(converters=[int, int, int])
                nvertices = items[0]
                npolygons = items[1]
                # . Allocate the object.
                surface = PolygonalSurface.WithSizes(ndimensions,
                                                     npolygons,
                                                     nvertices,
                                                     initialize=True)
                self.surface = surface
                # . Empty line.
                self.GetLine()
                # . Vertex lines.
                nfields = ndimensions
                if hasNormals: nfields *= 2
                converters = nfields * [float]
                for i in range(nvertices):
                    items = self.GetTokens(converters=converters)
                    surface.SetVertex(i, items[:ndimensions])
                    if hasNormals: surface.SetNormal(i, items[ndimensions:])
                # . Empty line.
                self.GetLine()
                # . Face lines.
                nfields = ndimensions + 1
                converters = nfields * [int]
                for i in range(npolygons):
                    items = self.GetTokens(converters=converters)
                    surface.SetPolygon(i, items[1:])
            except EOFError:
                pass
# . Close the file.
            self.WarningStop()
            self.Close()
            # . Set the parsed flag and some other options.
            self.log = None
            self.QPARSED = True
示例#27
0
 def Summary(self, log=logFile):
     """Summarizing."""
     if LogFileActive(log):
         if self.label is None: title = self.__class__.__name__ + " Summary"
         else:
             title = "Summary for " + self.__class__.__name__ + " \"" + self.label + "\""
         log.Heading(title, includeBlankLine=True)
         attributes = self.__class__.defaultAttributes.keys()
         attributes.sort()
         for attribute in attributes:
             item = self.__dict__.get(attribute, None)
             if (item is not None) and hasattr(item, "Summary"):
                 item.Summary(log=log)
示例#28
0
 def Summary(self, log=logFile):
     """Summary."""
     if LogFileActive(log):
         summary = log.GetSummary(pageWidth=90, valueWidth=16)
         summary.Start("Summary of Energy Terms")
         if self.__total is None: summary.Entry("Potential Energy", "None")
         else:
             summary.Entry("Potential Energy",
                           "{:16.4f}".format(self.__total))
         if self.__grms is None: summary.Entry("RMS Gradient", "None")
         else: summary.Entry("RMS Gradient", "{:16.4f}".format(self.__grms))
         for (name, value) in self.__terms:
             summary.Entry(name, "{:16.4f}".format(value))
         summary.Stop()
 def LogStart(self, state, log=logFile):
     """Start logging."""
     if (self.logFrequency > 0) and LogFileActive(
             log, pushPriority=PrintPriority_Low):
         state.log = log
         state.table = log.GetTable(columns=[6, 6, 18, 20, 20, 20, 20, 20])
         state.table.Start()
         state.table.Heading("Iteration", columnSpan=2)
         state.table.Heading("Active Images")
         state.table.Heading("Function Calls")
         state.table.Heading("Optimizations")
         state.table.Heading("Max. RMS Gradient")
         state.table.Heading("Path Length")
         state.table.Heading("Min. Angle")
def SystemExtents ( system, log = logFile ):
    """Calculate the extents of a system."""
    extents = None
    if system.coordinates3 is not None:
        ( origin, extents ) = system.coordinates3.EnclosingOrthorhombicBox ( )
        if LogFileActive ( log ):
            table = log.GetTable ( columns = [ 5, 20 ] )
            table.Start ( )
            table.Title ( "System Extents" )
            for ( label, extent ) in zip ( [ "x", "y", "z" ], extents ):
                table.Entry ( label, alignment = "l" )
                table.Entry ( "{:.3f}".format ( extent ) )
            table.Stop ( )
    return extents