Skip to content

Commit

Permalink
Bundles templats and allow coordgen to run without maeparser.
Browse files Browse the repository at this point in the history
Now templates are always bundled into the coordgen library,
so there is no search on the file system. coordgen can also
be built without maeparser support (USE_MAEPARSER=OFF). This
disables user-local template files, but it allows coordgen
to be built without reliance on the maeparser library
(and therefore without boost).

This is useful for a couple of reasons:
* We definitely see bugs in template discovery
* 3rd party packagers seem to have trouble with
  building maeparser.
* Allow use in other novel places that can't use
  the boost:: infrastructure required by
  maeparser.

maeparser is always required for running the unit tests.

Includes a short Python script for generating the C++ files
describing the templates. This Python script currently requires
use of the Schrodinger Python tools, so it will need to be
manually updated if the templates are updated. There is a test
that will fail if a new template is added but isn't added to
the compiled templates.

run it like:

    $SCHRODINGER/run mol_generator.py templates.mae
  • Loading branch information
d-b-w committed Mar 2, 2020
1 parent d98f16a commit d860d14
Show file tree
Hide file tree
Showing 8 changed files with 379 additions and 140 deletions.
13 changes: 12 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ script:
- mkdir build
- cd build
- export CMAKE_PREFIX_PATH=$TRAVIS_BUILD_DIR/installation/lib/cmake
- cmake .. -DCMAKE_BUILD_TYPE=Debug -DCOORDGEN_RIGOROUS_BUILD=ON
- cmake .. -DCMAKE_BUILD_TYPE=Debug -DCOORDGEN_RIGOROUS_BUILD=ON ${CMAKE_OPTS}
- make
- ctest -V -T memcheck --output-on-failure || (cat Testing/Temporary/MemoryChecker.*.log && exit 29)

Expand Down Expand Up @@ -44,3 +44,14 @@ matrix:
- libboost1.62-all-dev
- valgrind

- name: bundle templates
os: linux
dist: xenial
env: CC="gcc-5" CXX="g++-5" CMAKE_OPTS="-DCOORDGEN_BUNDLE_TEMPLATES=ON"
addons:
apt:
packages:
- gcc-5
- g++-5
- libboost1.58-all-dev
- valgrind
53 changes: 32 additions & 21 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,23 @@ option(COORDGEN_RIGOROUS_BUILD "Abort the build if the compiler issues \
any warnings" OFF )
option(COORDGEN_BUILD_TESTS "Whether test executables should be built" ON)
option(COORDGEN_BUILD_EXAMPLE "Whether to build the sample executable" ON)
option(COORDGEN_USE_MAEPARSER "Whether to allow loading of run-time templates" OFF)
option(COORDGEN_BUILD_SHARED_LIBS "Build coordgen as a shared library \
(turn off for a static one)" ON)

# Use the maeparser_DIR variable to tell CMake where to search for the
# maeparser library.

set(MAEPARSER_VERSION "master" CACHE STRING "maeparser tag to build if \
a compiled library is not found")
if (COORDGEN_USE_MAEPARSER OR COORDGEN_BUILD_TESTS)
set(USE_MAEPARSER ON)
else()
set(USE_MAEPARSER OFF)
endif()

if (USE_MAEPARSER)
set(MAEPARSER_VERSION "master" CACHE STRING "maeparser tag to build if \
a compiled library is not found")
endif()

if(MSVC)
# C4251 disables warnings for export STL containers as arguments
Expand All @@ -33,7 +42,6 @@ else(MSVC)
endif(MSVC)
endif(COORDGEN_RIGOROUS_BUILD)


# Source files & headers
file(GLOB SOURCES "*.cpp")

Expand All @@ -44,25 +52,32 @@ if(COORDGEN_BUILD_SHARED_LIBS)
set_property(TARGET coordgen PROPERTY CXX_VISIBILITY_PRESET "hidden")
else(COORDGEN_BUILD_SHARED_LIBS)
add_library(coordgen STATIC ${SOURCES})
target_compile_definitions(coordgen PRIVATE "STATIC_MAEPARSER")
if (USE_MAEPARSER)
target_compile_definitions(coordgen PRIVATE "STATIC_MAEPARSER")
endif(USE_MAEPARSER)
target_compile_definitions(coordgen PRIVATE "STATIC_COORDGEN")
endif(COORDGEN_BUILD_SHARED_LIBS)

# Dependencies
if(TARGET maeparser)
message(STATUS "Using externally defined maeparser target to "
"build coordgen")
else()
include(CoordgenUtils)
set(MAEPARSER_BUILD_SHARED_LIBS ${COORDGEN_BUILD_SHARED_LIBS} CACHE BOOL "Library type for maeparser")
find_or_clone_maeparser()
if (USE_MAEPARSER)
if(TARGET maeparser)
message(STATUS "Using externally defined maeparser target to "
"build coordgen")
else()
include(CoordgenUtils)
set(MAEPARSER_BUILD_SHARED_LIBS ${COORDGEN_BUILD_SHARED_LIBS} CACHE BOOL "Library type for maeparser")
find_or_clone_maeparser()
endif()

include_directories(${maeparser_INCLUDE_DIRS})
find_package(Boost REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})
endif (USE_MAEPARSER)

if (COORDGEN_USE_MAEPARSER)
target_link_libraries(coordgen ${maeparser_LIBRARIES})
target_compile_definitions(coordgen PRIVATE "USE_MAEPARSER")
endif()
include_directories(${maeparser_INCLUDE_DIRS})

find_package(Boost REQUIRED)
include_directories(${Boost_INCLUDE_DIRS})

target_link_libraries(coordgen ${maeparser_LIBRARIES})

set_target_properties(coordgen
PROPERTIES
Expand Down Expand Up @@ -100,10 +115,6 @@ install(FILES
sketcherMinimizerStretchInteraction.h
DESTINATION include/coordgen)

install(FILES
templates.mae
DESTINATION share/coordgen)

install(EXPORT coordgen-targets
FILE ${PROJECT_NAME}-config.cmake
DESTINATION lib/cmake)
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,22 @@ Schrodinger intends to continue to contribute to this code as it still uses it i

Examples and documentation will be added/improved over time

## Templates

Coordgen uses templates for some macrocycle systems. The source for the templates is `templates.mae`. If
you're an end user of coordgen, you can add local templates in a file called
`user_templates.mae` in a directory specified by `CoordgenTemplates::setTemplateDir()`. If you want to
update the templates, add new templates to `templates.mae` and run `mol_generator.py` to generate the
source files.

## Usage example

Code for a sample executable is provided in the `example_dir` directory. Building the example executable is enabled by default, but can be disabled by means of the `COORDGEN_BUILD_EXAMPLE` option.

## Automated Testing

Automated testing is still primarily taking place inside Schrodinger's internal build system, although tests are incrementally being added to the `testing` directory. Building the tests is enabled by default, but can be disabled by means of the `COORDGEN_BUILD_TESTS` option.
d

Memory debugging is, by default, configured to use `valgrind`. It can be run on the tests by passing `-DCMAKE_BUILD_TYPE=Debug` to cmake, to enable building the debugging symbols, and then using `ctest -T memcheck` inside the build directory.

## Building from source
Expand Down
183 changes: 183 additions & 0 deletions mol_generator.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
"""
Using the schrodinger Python modules, read a template .mae file to
generate the coordgen template C++ files.
"""

import sys
import os
import textwrap
import argparse
import subprocess

from schrodinger import structure

NAME = 'CoordgenTemplates'

# String that creates a molecule and adds it to a container
# of sketcherMinimizerMolecule* at index i.
#
# C++ programmers: `{` and `}` are escaped by duplication, so
# `{{` means `{`.
_MOLECULE = """
{{
auto molecule = new sketcherMinimizerMolecule();
std::array<std::tuple<int, float, float>, {atom_total}> atoms = {{{{
{atoms}
}}}};
std::array<std::array<int, 3>, {bond_total}> bonds = {{{{
{bonds}
}}}};
add_atoms(molecule, atoms);
add_bonds(molecule, bonds);
molecules[i] = molecule;
++i;
}}
"""

_DOC = """
///
// Find the templates for coordinate generation
//
// Autogenerated, do not edit.
//
// $SCHRODINGER/run {script_name} {template_file}
//
// generated using {template_file} version {git_hash}.
//
"""

_HEADER = """
#pragma once
{doc}
#include <vector>
class sketcherMinimizerMolecule;
namespace schrodinger {{
///
// Create a new vector of sketcherMinimizerMolecule*. Caller
// owns the sketcherMinimizerMolecule objects.
std::vector<sketcherMinimizerMolecule*> coordgen_templates();
}}
"""

_IMPLEMENTATION = """
{doc}
#include "{name}.h"
#include <array>
#include "sketcherMinimizerMolecule.h"
#include "sketcherMinimizerAtom.h"
#include "sketcherMinimizerBond.h"
using std::array;
using std::tuple;
template <typename T>
void add_atoms(sketcherMinimizerMolecule* molecule, const T& atoms)
{{
for (const auto& a: atoms) {{
auto atom = molecule->addNewAtom();
atom->setAtomicNumber(std::get<0>(a));
atom->setCoordinates(sketcherMinimizerPointF(std::get<1>(a), std::get<2>(a)));
}}
}}
template <typename T>
void add_bonds(sketcherMinimizerMolecule* molecule, const T& bonds)
{{
for (const auto& b: bonds) {{
auto* from_atom = molecule->getAtoms().at(b[0]);
auto* to_atom = molecule->getAtoms().at(b[1]);
auto bond = molecule->addNewBond(from_atom, to_atom);
bond->setBondOrder(b[2]);
}}
}}
namespace schrodinger {{
std::vector<sketcherMinimizerMolecule*> coordgen_templates()
{{
std::vector<sketcherMinimizerMolecule*> molecules({total});
size_t i = 0;
{body}
return molecules;
}}
}}
"""


def get_mol_def(st):
"""
Use _MOLECULE to define `st` as a sketcherMinimizerMolecule
"""
atoms = (
f' tuple<int, float, float>({a.atomic_number}, {a.x}f, {a.y}f)'
for a in st.atom)
atoms = ',\n'.join(atoms)

bonds = (
f' {{ {b.atom1.index - 1}, {b.atom2.index - 1}, {b.order} }}'
for b in st.bond)
bonds = ',\n'.join(bonds)

t = _MOLECULE.format(
bonds=bonds,
atoms=atoms,
atom_total=st.atom_total,
bond_total=len(st.bond))
return t


def main(args=None):
parser = argparse.ArgumentParser(args)
parser.add_argument('template_file')

opts = parser.parse_args()

git_hash = subprocess.check_output(
['git', 'log', '-n', '1', '--pretty=format:%H', opts.template_file])
git_hash = git_hash.decode().strip()[:20]
template_dir, template_base_name = os.path.split(opts.template_file)

doc = _DOC.format(
script_name=os.path.basename(__file__),
template_file=template_base_name,
git_hash=git_hash)

header = _HEADER.format(doc=doc)

total = structure.count_structures(opts.template_file)
body = ''

with structure.StructureReader(opts.template_file) as r:
for st in r:
mol_def = get_mol_def(st)
body += mol_def
body = textwrap.indent(body, ' ')

implementation = _IMPLEMENTATION.format(
doc=doc, name=NAME, body=body, total=total)

header_path = os.path.join(template_dir, f'{NAME}.h')
with open(header_path, 'w') as fh:
fh.write(header)

implementation_path = os.path.join(template_dir, f'{NAME}.cpp')
with open(implementation_path, 'w') as fh:
fh.write(implementation)


if __name__ == '__main__':
main()
60 changes: 60 additions & 0 deletions sketcherMaeReading.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#pragma once

///
// Shim for creating sketcherMolecules from .mae files.

#include "sketcherMinimizerMolecule.h"
#include "maeparser/MaeConstants.hpp"
#include "maeparser/Reader.hpp"

using namespace std;
using namespace schrodinger;

///
// A very simple utility function to parse a mae::Block into a 2D
// sketcherMinimizerMolecule. Anything beyond atomic number, x and y coordinates
// and bond orders will be ignored (i.e. no chiralities or stereo bonds will be
// parsed).
//
sketcherMinimizerMolecule* mol_from_mae_block(mae::Block& block)
{
auto molecule = new sketcherMinimizerMolecule();
// Atom data is in the m_atom indexed block
{
const auto atom_data = block.getIndexedBlock(mae::ATOM_BLOCK);
// All atoms are gauranteed to have these three field names:
const auto atomic_numbers =
atom_data->getIntProperty(mae::ATOM_ATOMIC_NUM);
const auto xs = atom_data->getRealProperty(mae::ATOM_X_COORD);
const auto ys = atom_data->getRealProperty(mae::ATOM_Y_COORD);
const auto size = atomic_numbers->size();

// atomic numbers, and x, y, and z coordinates
for (size_t i = 0; i < size; ++i) {
auto atom = molecule->addNewAtom();
atom->setAtomicNumber(atomic_numbers->at(i));
atom->setCoordinates(sketcherMinimizerPointF(
static_cast<float>(xs->at(i)), static_cast<float>(ys->at(i))));
}
}

// Bond data is in the m_bond indexed block
{
const auto bond_data = block.getIndexedBlock(mae::BOND_BLOCK);
// All bonds are gauranteed to have these three field names:
auto from_atoms = bond_data->getIntProperty(mae::BOND_ATOM_1);
auto to_atoms = bond_data->getIntProperty(mae::BOND_ATOM_2);
auto orders = bond_data->getIntProperty(mae::BOND_ORDER);
const auto size = from_atoms->size();

for (size_t i = 0; i < size; ++i) {
// Maestro atoms are 1 indexed!
auto* from_atom = molecule->getAtoms().at(from_atoms->at(i) - 1);
auto* to_atom = molecule->getAtoms().at(to_atoms->at(i) - 1);
auto bond = molecule->addNewBond(from_atom, to_atom);
bond->setBondOrder(orders->at(i));
}
}

return molecule;
}
Loading

0 comments on commit d860d14

Please sign in to comment.