Molecular Database Handling¶
The OEMolDatabase class provides a fundamentally
different abstraction over a molecule file than the combination of
oemolistream and
OEReadMolecule
. The central underlying principle
utilized by this class is that many operations can be performed on a
molecular file without requiring the overhead of fully parsing the
molecule record into an OEMolBase object. Instead, we
can think of a molecular file as a database that can be manipulated
with much cheaper operations than OEReadMolecule
and
OEWriteMolecule
.
Opening and Reading¶
OEMolDatabase objects provide the ability to access
any molecule in a molecular database file in constant time,
O(1)
. This is accomplished by paying the overhead of scanning the
file during the OEMolDatabase.Open
call. However,
OEMolDatabase.Open
is designed to operate extremely
fast on any molecule file format OEChem TK
supports. OEMolDatabase.Open
is usually limited by
disk bandwidth instead of parsing and perception like
OEReadMolecule
and
OEWriteMolecule
.
After a database file is opened, the memory overhead of
OEMolDatabase is minimal since no molecules are
stored in memory. Instead, the OEMolDatabase only
stores a 8 byte file offset for each molecule record in the
file. Listing 1
demonstrates how to utilize this
feature to retrieve the “Nth” molecule from a molecule file using the
OEMolDatabase.GetMolecule
method.
Listing 1: Retrieving the Nth molecule in a file
package openeye.docexamples.oechem;
import openeye.oechem.*;
public class NthMolecule {
public static void main(String argv[]) {
if (argv.length != 3)
oechem.OEThrow.Usage("NthMolecule <input> <output> <index>");
OEMolDatabase moldb = new OEMolDatabase();
if (!moldb.Open(argv[0]))
oechem.OEThrow.Fatal("Unable to open " + argv[0]);
oemolostream ofs = new oemolostream();
if (!ofs.open(argv[1]))
oechem.OEThrow.Fatal("Unable to open " + argv[1]);
int idx = Integer.parseInt(argv[2]);
OEMol mol = new OEMol();
if (!moldb.GetMolecule(mol, idx))
oechem.OEThrow.Fatal("Unable to read a molecule from index " + idx);
oechem.OEWriteMolecule(ofs, mol);
ofs.close();
}
}
Listing 1
checks the return value of
OEMolDatabase.GetMolecule
for false, indicating the
molecule record at that position in the file does not contain a valid
molecule. For example, molecules without any atoms are valid records
in .sdf
files.
Note
OEMolDatabase.Open
is still a O(N)
operation
in order to know the position of each molecule record in the
file. However, this method is significantly cheaper than using
OEReadMolecule
, instead being limited by hard
disk bandwidth instead of processing speed. The
OEMolDatabase.Open
method can also be sped up by
creating an associated .idx
file as described by the
Index Files section.
Direct Data Access¶
OEMolDatabase achieves much of its speed by treating
molecules as chunks of bytes instead of OEMolBase
objects. This abstraction is leaked a little bit by providing users
the ability to access the raw bytes of the molecule record as well
through the OEMolDatabase.GetMolecule
method that
takes a oemolostream. For example, the user could
pass this method a oemolostream that has been opened
with oemolostream.openstring
in order to dump the
desired bytes to an in memory buffer. Listing
2
demonstrates how to use this feature to retrieve a
subset of molecules from a database file similar to how the LIMIT
and OFFSET
keywords work for an SQL query.
Listing 2: Retrieving a subset of a file
package openeye.docexamples.oechem;
import openeye.oechem.*;
public class DatabaseSubset {
public static void main(String argv[]) {
if (argv.length != 4)
oechem.OEThrow.Usage("DatabaseSubset <input> <output> <offset> <limit>");
OEMolDatabase moldb = new OEMolDatabase();
if (!moldb.Open(argv[0]))
oechem.OEThrow.Fatal("Unable to open " + argv[0]);
String ext = oechem.OEGetFileExtension(argv[1]);
if (moldb.GetFormat() != oechem.OEGetFileType(ext))
oechem.OEThrow.Fatal("Output format does not match input format: " +
oechem.OEGetFileExtension(argv[0]) + " != " + ext);
oemolostream ofs = new oemolostream();
if (!ofs.open(argv[1]))
oechem.OEThrow.Fatal("Unable to open " + argv[1]);
int offset = Integer.parseInt(argv[2]);
int limit = Integer.parseInt(argv[3]);
int maxIdx = offset + limit;
for (int idx = offset; idx < maxIdx; ++idx) {
moldb.WriteMolecule(ofs, idx);
}
ofs.close();
}
}
Note
The oemolostream must be set up to write output to
the exact same file format that the OEMolDatabase
is opened on. If file format conversion is also desired during the
read operation, the user should user
OEMolDatabase.GetMolecule
to read the molecule
into an OEMolBase and then use
OEWriteMolecule
.
Title Access¶
Molecule meta-data is often useful for manipulating databases
regardless of the molecule connection table. For this reason the
OEMolDatabase provides access to the molecule title
through the OEMolDatabase.GetTitle
method. The
OEMolDatabase.GetTitle
returns the same string that
would be returned by OEMolBase.GetTitle
if the
molecule was read in with OEReadMolecule
. The
difference is that the OEMolDatabase.GetTitle
method is more efficient because it will only parse the title from the
molecule record, and skip the rest of the bytes in the molecule
record. Listing 3
demonstrates how to use
OEMolDatabase.GetTitle
to implement a more
efficient version of the molextract
example.
Listing 3: Extract molecules by title
package openeye.docexamples.oechem;
import openeye.oechem.*;
public class DatabaseExtract {
public static void main(String argv[]) {
if (argv.length != 3)
oechem.OEThrow.Usage("DatabaseExtract <input> <output> <title>");
OEMolDatabase moldb = new OEMolDatabase();
if (!moldb.Open(argv[0]))
oechem.OEThrow.Fatal("Unable to open " + argv[0]);
String ext = oechem.OEGetFileExtension(argv[1]);
if (moldb.GetFormat() != oechem.OEGetFileType(ext))
oechem.OEThrow.Fatal("Output format does not match input format: " +
oechem.OEGetFileExtension(argv[0]) + " != " + ext);
oemolostream ofs = new oemolostream();
if (!ofs.open(argv[1]))
oechem.OEThrow.Fatal("Unable to open " + argv[1]);
String title = argv[2];
for (int idx = 0; idx < moldb.GetMaxMolIdx(); ++idx) {
if (title.equals(moldb.GetTitle(idx)))
moldb.WriteMolecule(ofs, idx);
}
ofs.close();
}
}
Note
Multi-conformer .oeb
files can have multiple titles per
molecule record. The top-level OEMCMolBase can
have a title, as well as each OEConfBase can have
a title. OEMolDatabase.GetTitle
will only return
the title of the top-level OEMCMolBase object and
make no attempt to search for a title among the conformer data. In
practice, this is fine since OMEGA will leave the
OEMCMolBase title the same as the input file, and
append warts to the individual conformer titles.
Index Files¶
The speed of OEMolDatabase.Open
is limited by how
fast data can be read from disk. For this reason, file position
offsets can be precomputed and stored in a parallel .idx
file. OEMolDatabase.Open
will automatically detect
the presence of this file based upon the file name of the file being
opened and use those file offsets instead. For example, if the
database file is called, “my_corporate_conformers.oeb”,
OEMolDatabase.Open
will look for a file named
“my_corporate_conformers.oeb.idx” to open as an index file. If an index
file can not be located, a full file scan will occur instead. For
files written once, and read many times, it can be highly beneficial
to create a parallel index file with the
OECreateMolDatabaseIdx
function.
Note
OEMolDatabase.Save
will automatically create a
.idx
file parallel to the file being saved. This behavior
can be modified by the
OEMolDatabaseSaveOptions.SetWriteIdx
method on
the OEMolDatabaseSaveOptions options class.
See also
OEGetMolDatabaseIdxFileName
for the default way index file names are created.
Database Generic Data¶
OEMolDatabase inherits from
OEBase, allowing it to contain and round-trip
generic data as described in the Generic Data
chapter. This data is only written and read from .oeb
files. It is stored in the .oeb
file as a
OEHeader record at the beginning of the
file. OEMolDatabase.Save
will write this record
back out the .oeb
file so that it be read by a subsequent
OEMolDatabase.Open
operation.
Caveats¶
Warning
OEMolDatabase will make an uncompressed copy of a
molecular database file when opened on a .gz
, GZipped
file. The temporary file will be deleted upon the destruction of
the OEMolDatabase object, however, it is still
recommended to not use .gz
files with
OEMolDatabase if it can be avoided. If a
different directory is desired for the uncompressed file, alter the
environment variables used by the
OEFileTempPath
function.
The largest caveat when working with OEMolDatabase objects is that they require a file on disk to provide storage. This allows the object to have a very small in-memory footprint at the expense of higher latency access to individual molecule records. The OEMolDatabase object makes no attempt to cache molecules that may be frequently read, instead leaving this up to the user at a higher level, or up to the operating system to cache frequently accessed disk pages.
The above reason is why .gz
files are not well supported. The
OEMolDatabase needs to be able to read a molecule
record by seeking to a particular location in the file. Though the
result is that multi-threaded applications can efficiently call
const
OEMolDatabase methods like
OEMolDatabase.GetMolecule
without any
synchronization overhead.