├── .gitignore
├── .travis.yml
├── CAOS
├── __init__.py
├── compatibility.py
├── dispatch.py
├── exceptions
│ ├── __init__.py
│ ├── dispatch_errors.py
│ └── reaction_errors.py
├── logging.py
├── mechanisms
│ ├── __init__.py
│ ├── acid_base.py
│ └── requirements
│ │ └── __init__.py
├── structures
│ ├── __init__.py
│ └── molecule.py
└── util.py
├── LICENSE
├── README.rst
├── docs-requirements.txt
├── docs
├── CAOS.exceptions.rst
├── CAOS.mechanisms.rst
├── CAOS.requirements.rst
├── CAOS.rst
├── CAOS.structures.rst
├── Makefile
├── conf.py
├── index.rst
└── make.bat
├── requirements.txt
├── setup.cfg
├── testing-requirements.txt
└── tests
├── test_dispatch.py
├── test_mechanisms
└── test_acid_base_reaction.py
├── test_reactions_basic.py
├── test_structures.py
└── test_util.py
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 |
5 | # C extensions
6 | *.so
7 |
8 | # Distribution / packaging
9 | .Python
10 | env/
11 | build/
12 | develop-eggs/
13 | dist/
14 | downloads/
15 | eggs/
16 | .eggs/
17 | lib/
18 | lib64/
19 | parts/
20 | sdist/
21 | var/
22 | *.egg-info/
23 | .installed.cfg
24 | *.egg
25 |
26 | # PyInstaller
27 | # Usually these files are written by a python script from a template
28 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
29 | *.manifest
30 | *.spec
31 |
32 | # Installer logs
33 | pip-log.txt
34 | pip-delete-this-directory.txt
35 |
36 | # Unit test / coverage reports
37 | htmlcov/
38 | .tox/
39 | .coverage
40 | .coverage.*
41 | .cache
42 | nosetests.xml
43 | coverage.xml
44 | *,cover
45 |
46 | # Translations
47 | *.mo
48 | *.pot
49 |
50 | # Django stuff:
51 | *.log
52 |
53 | # Sphinx documentation
54 | docs/_build/
55 |
56 | # PyBuilder
57 | target/
58 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: false
2 |
3 | language: python
4 |
5 | matrix:
6 | include:
7 | - python: "2.6"
8 | - python: "2.7"
9 | - python: "3.3"
10 | - python: "3.4"
11 | - python: "3.5"
12 | allow_failures:
13 | - python: "2.6"
14 | - python: "3.3"
15 | - python: "3.4"
16 | - python: "3.5"
17 | fast_finish: true
18 |
19 | install:
20 | - pip install -r requirements.txt
21 | - if [[ $TRAVIS_PYTHON_VERSION == 2.6 ]]; then pip install importlib; fi
22 | - pip install -r testing-requirements.txt
23 | - pip install -r docs-requirements.txt
24 | - if [[ $TRAVIS_PYTHON_VERSION != 3.4 && $TRAVIS_PYTHON_VERSION != 3.5 ]]; then pip install enum34; fi
25 |
26 | script:
27 | - echo "Checking correctness:"
28 | - nosetests
29 | - echo "Checking PEP 8 compliance:"
30 | - flake8 .
31 | - echo "Checking PEP 257 compliance:"
32 | - pep257 .
33 | - echo "Checking that the docs build:"
34 | - sphinx-build -E docs docs/_build
35 |
36 | after_success:
37 | - coveralls
38 |
--------------------------------------------------------------------------------
/CAOS/__init__.py:
--------------------------------------------------------------------------------
1 | """CAOS module."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
6 | import argparse
7 |
8 | from .logging import get_logger
9 |
10 |
11 | __version__ = "0.4.0"
12 | __author__ = "Dan Obermiller"
13 |
14 |
15 | if __name__ == '__main__':
16 | parser = argparse.ArgumentParser(
17 | description="Predict an organic chemistry reaction using the CAOS tool"
18 | ", powered by Python"
19 | )
20 |
21 | parser.add_argument(
22 | 'source', type=str,
23 | help="The source file for the reaction."
24 | )
25 | parser.add_argument(
26 | '-v', '--verbose', dest='verbose', action='store_true', default=False,
27 | help="Set the program to run in verbose mode."
28 | )
29 |
30 | args = parser.parse_args()
31 |
32 | if 'verbose' not in globals():
33 | verbose = args.verbose
34 | elif 'verbose' not in globals():
35 | verbose = False
36 |
37 | logger = get_logger(verbose)
38 |
39 | from . import mechanisms # noqa
40 |
--------------------------------------------------------------------------------
/CAOS/compatibility.py:
--------------------------------------------------------------------------------
1 | """Compatibility file to match functionality between py2 and 3."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
6 | import itertools
7 | import sys
8 |
9 | try:
10 | range = xrange
11 | except NameError:
12 | range = range
13 |
14 | try:
15 | str = unicode
16 | except NameError:
17 | str = str
18 |
19 | try:
20 | long
21 | except NameError:
22 | long = int
23 |
24 | try:
25 | from io import StringIO
26 | except ImportError:
27 | try:
28 | from cStringIO import StringIO
29 | except ImportError:
30 | from StringIO import StringIO
31 |
32 | if sys.version[0] == 2:
33 | map = itertools.imap
34 | zip = itertools.izip
35 |
36 | del sys
37 | del itertools
38 |
--------------------------------------------------------------------------------
/CAOS/dispatch.py:
--------------------------------------------------------------------------------
1 | """Handles registration and dispatch of reactions and molecule types.
2 |
3 | Provides a decorator that is used to register reaction mechanisms,
4 | `register_reaction_mechanism`. This allows the reaction system to
5 | determine which type of reaction should be attempted, dynamically at
6 | runtime.
7 |
8 | Attributes
9 | ----------
10 | react: function
11 | Function that attempts to react molecules under given conditions
12 | register_reaction_mechanism: function
13 | Registers a reaction mechanism with the dispatch system.
14 | reaction_is_registered: function
15 | Checks whether or not a reaction has been registered.
16 | """
17 |
18 | from __future__ import print_function, division, unicode_literals, \
19 | absolute_import
20 |
21 | import six
22 |
23 | from .exceptions.dispatch_errors import ExistingReactionError, \
24 | InvalidReactionError
25 | from .exceptions.reaction_errors import FailedReactionError
26 | from . import logger
27 |
28 |
29 | class ReactionDispatcher(object):
30 | """Class that dispatches on reaction types."""
31 |
32 | _REACTION_ATTEMPT_MESSAGE = ("Trying to react reactants {}"
33 | " in conditions {} as a {} type reaction.")
34 | _REACTION_FAILURE_MESSAGE = ("Couldn't react reactants {}"
35 | " in conditions {}.")
36 | _REGISTERED_MECHANISM_MESSAGE = "Added mechanism {} with requirements {}."
37 | _REQUIREMENT_NOT_MET_MESSAGE = ("Requirement {} for mechanism {} not met"
38 | " by reactants {} in conditions {}")
39 | _REQUIREMENT_PASSED_MESSAGE = "Passed requirement {} for mechanism {}"
40 | _ADDED_POSSIBLE_MECHANISM = "Added potential mechanism {}"
41 | _EXISTING_MECHANISM_ERROR = "A mechanism named {} already exists."
42 |
43 | _mechanism_namespace = {}
44 | _test_namespace = {}
45 |
46 | def __init__(self, requirements, __test=False):
47 | """Register a new reaction mechanism.
48 |
49 | Parameters
50 | ==========
51 | requirements: collection
52 | List of requirements that provided reactants and conditions
53 | must meet for this reaction to be considered.
54 | __test: bool
55 | Whether or not the reaction being registered is a test
56 | reaction and shouldn't be in the real namespace.
57 | """
58 |
59 | self.requirements = requirements
60 | self.__test = __test
61 |
62 | def __call__(self, mechanism_function):
63 | """Register the function.
64 |
65 | Parameters
66 | ==========
67 | mechanism_function: callable
68 | The function that should be called when the reaction is
69 | attempted.
70 |
71 | Returns
72 | =======
73 | mechanism_function: callable
74 | The function that was decorated, with a `logger` attribute
75 | added to it.
76 |
77 | Notes
78 | =====
79 | Callables that are decorated with this will not have any
80 | difference in behavior than if they were not decorated (with the
81 | exception of having a `logger` attribute added to them). In
82 | order to get dispatching behavior, the `react` function must
83 | be used instead.
84 | """
85 |
86 | mechanism_function.logger = logger
87 | ReactionDispatcher._register(
88 | mechanism_function, self.requirements,
89 | self._ReactionDispatcher__test
90 | )
91 |
92 | return mechanism_function
93 |
94 | @classmethod
95 | def _register(cls, function, requirements, __test):
96 | """Register a function with the dispatch system.
97 |
98 | Parameters
99 | ----------
100 | function : callable
101 | The mechanism to be registered.
102 | requirements : collection
103 | List of requirement functions.
104 | __test : bool
105 | Whether or not to use the testing namespace.
106 | """
107 |
108 | namespace = cls._get_namespace(__test)
109 | cls._validate_function(function, namespace)
110 | cls._validate_requirements(requirements)
111 |
112 | name = function.__name__
113 |
114 | namespace[name] = {
115 | "requirements": requirements,
116 | "function": function
117 | }
118 |
119 | if not __test:
120 | logger.log(
121 | cls._REGISTERED_MECHANISM_MESSAGE.format(
122 | name, requirements
123 | )
124 | )
125 |
126 | @classmethod
127 | def _get_namespace(cls, __test):
128 | """Get the namespace depending on if it is a test or not.
129 |
130 | Parameters
131 | ----------
132 | __test : bool
133 | The condition.
134 |
135 | Returns
136 | -------
137 | dict
138 | The namespace to be used.
139 | """
140 |
141 | return cls._test_namespace if __test else cls._mechanism_namespace
142 |
143 | @classmethod
144 | def _validate_function(cls, function, namespace):
145 | """Check that a function is valid.
146 |
147 | Parameters
148 | ----------
149 | function : callable
150 | A function or callable object that is serving as a reaction
151 | mechanism.
152 | namespace : dict
153 | The namespace being used.
154 |
155 | Raises
156 | ------
157 | ExistingReactionError
158 | The name of the function must be unique - if an existing
159 | mechanism shares this name it will cause an error.
160 | """
161 |
162 | mechanism_name = function.__name__
163 |
164 | if mechanism_name in namespace:
165 | message = cls._EXISTING_MECHANISM_ERROR.format(
166 | mechanism_name
167 | )
168 | logger.error(message)
169 | raise ExistingReactionError(message)
170 |
171 | @classmethod
172 | def _validate_requirements(cls, requirements):
173 | """Validate that the requirements are all callables.
174 |
175 | Parameters
176 | ----------
177 | requirements : collection
178 | List of all the requirements. Each must be a function or
179 | callable object.
180 |
181 | Raises
182 | ------
183 | InvalidReactionError
184 | If any of the requirements aren't callable then an error is
185 | raised.
186 | If the requirements is an empty list then an error is
187 | raised.
188 | """
189 |
190 | if not requirements:
191 | message = "There must be at least one requirement."
192 | logger.error(message)
193 | raise InvalidReactionError(message)
194 |
195 | for function in requirements:
196 | if not callable(function):
197 | if hasattr(function, '__name__'):
198 | message = "Requirement named {} is not a function.".format(
199 | function.__name__
200 | )
201 | else:
202 | message = "Requirement {} is not a function.".format(
203 | function
204 | )
205 | logger.error(message)
206 | raise InvalidReactionError(message)
207 |
208 | @classmethod
209 | def _generate_likely_reactions(cls, reactants, conditions, namespace):
210 | """Generate a list of potential reactions.
211 |
212 | Parameters
213 | ==========
214 | reactants: collection[Molecule]
215 | A list of molecules to be reacted
216 | conditions: mapping[String -> Object]
217 | Dictionary of the conditions in this molecule.
218 |
219 | Returns
220 | =======
221 | mechanisms: list[function]
222 | A list of mechanisms to try.
223 |
224 | Notes
225 | =====
226 | Currently this list is in no particular order - this will change
227 | and should not be relied on.
228 | """
229 |
230 | mechanisms = []
231 |
232 | for mech_name, mech_info in six.iteritems(namespace):
233 | mechanism = mech_info['function']
234 | requirements = mech_info['requirements']
235 |
236 | for req_function in requirements:
237 | req_name = req_function.__name__
238 | if not req_function(reactants, conditions):
239 | logger.log(cls._REQUIREMENT_NOT_MET_MESSAGE.format(
240 | req_name, mech_name, reactants, conditions
241 | ))
242 | break
243 | else:
244 | logger.log(cls._REQUIREMENT_PASSED_MESSAGE.format(
245 | req_name, mech_name
246 | ))
247 | else:
248 | logger.log(cls._ADDED_POSSIBLE_MECHANISM.format(mech_name))
249 | mechanisms.append(mechanism)
250 |
251 | return mechanisms
252 |
253 | @classmethod
254 | def _react(cls, reactants, conditions, __test=False):
255 | """The method that actually performs a reaction.
256 |
257 | Parameters
258 | ==========
259 | reactants: collection[Molecule]
260 | A list of molecules to be reacted
261 | conditions: mapping[String -> Object]
262 | Dictionary of the conditions in this molecule.
263 |
264 | Returns
265 | =======
266 | products: list[Molecule]
267 | Returns a list of the products.
268 | """
269 |
270 | namespace = cls._get_namespace(__test)
271 |
272 | potential_reactions = cls._generate_likely_reactions(
273 | reactants, conditions, namespace
274 | )
275 |
276 | for potential_reaction in potential_reactions:
277 | products = potential_reaction(reactants, conditions)
278 | logger.log(
279 | cls._REACTION_ATTEMPT_MESSAGE.format(
280 | reactants, conditions, potential_reaction
281 | )
282 | )
283 | if products:
284 | return products
285 |
286 | message = cls._REACTION_FAILURE_MESSAGE.format(reactants, conditions)
287 | logger.log(message)
288 | raise FailedReactionError(message)
289 |
290 | @classmethod
291 | def _is_registered_reaction(cls, reaction, __test=False):
292 | """Check if a reaction has been registered.
293 |
294 | Parameters
295 | ==========
296 | reaction: string, callable
297 | The reaction to be checked.
298 |
299 | Returns
300 | =======
301 | bool
302 | Whether or not the reaction has been registered.
303 | """
304 |
305 | namespace = cls._get_namespace(__test)
306 |
307 | if callable(reaction):
308 | for name, info in six.iteritems(namespace):
309 | if reaction is info['function']:
310 | return True
311 | else:
312 | return False
313 | else:
314 | return reaction in namespace
315 |
316 |
317 | # Provide friendlier way to call things
318 | react = ReactionDispatcher._react
319 | register_reaction_mechanism = ReactionDispatcher
320 | reaction_is_registered = ReactionDispatcher._is_registered_reaction
321 |
--------------------------------------------------------------------------------
/CAOS/exceptions/__init__.py:
--------------------------------------------------------------------------------
1 | """Exceptions used by the library."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
--------------------------------------------------------------------------------
/CAOS/exceptions/dispatch_errors.py:
--------------------------------------------------------------------------------
1 | """Errors that occur while dispatching the mechanism or type."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
6 |
7 | class DispatchException(Exception):
8 | """Generic error raised when some problem occurs during dispatch."""
9 |
10 | pass
11 |
12 |
13 | class ExistingReactionError(DispatchException):
14 | """A mechanism with this name has already been registered."""
15 |
16 | pass
17 |
18 |
19 | class InvalidReactionError(DispatchException):
20 | """The reaction being registered is invalid in some way."""
21 |
22 | pass
23 |
--------------------------------------------------------------------------------
/CAOS/exceptions/reaction_errors.py:
--------------------------------------------------------------------------------
1 | """Reaction errors; i.e. those that occur during a reaction."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
6 |
7 | class FailedReactionError(Exception):
8 | """Indicates that a reaction failed to occur."""
9 |
10 | pass
11 |
--------------------------------------------------------------------------------
/CAOS/logging.py:
--------------------------------------------------------------------------------
1 | """Singleton logging for the language.
2 |
3 | When verbose mode is enabled, logged messages are written to stdout or
4 | stderr, depending on the type of message. Otherwise non-error messages
5 | are suppressed.
6 |
7 | Attributes
8 | ----------
9 | LoggingLevelEnum : Enum
10 | Logging levels - available levels are DEBUG, INFO, WARN, and ERROR.
11 | """
12 |
13 | from __future__ import print_function, division, unicode_literals, \
14 | absolute_import
15 |
16 | from .compatibility import StringIO
17 |
18 | import sys
19 |
20 | try:
21 | from enum import IntEnum
22 | except ImportError:
23 | raise ImportError(
24 | "You need to install the enum34 package for Python versions < 3.4"
25 | )
26 |
27 |
28 | class LoggingLevelEnum(IntEnum):
29 | """Enum for different logging levels.
30 |
31 | Attributes
32 | ----------
33 | DEBUG : int
34 | Debug level.
35 | INFO : int
36 | Info level.
37 | """
38 |
39 | DEBUG = 1
40 | INFO = 2
41 |
42 | fake_out = StringIO()
43 | fake_err = StringIO()
44 |
45 | logger = None
46 |
47 |
48 | def get_logger(verbose, level=LoggingLevelEnum.INFO):
49 | """Get a singleton logger for the given level and verbosity.
50 |
51 | Parameters
52 | ----------
53 | verbose : bool
54 | Whether or not verbose mode is enabled.
55 | level : Optional[LoggingLevelEnum]
56 | The logging level to be used. Defaults to INFO.
57 |
58 | Returns
59 | -------
60 | logger : Logger
61 | The logger object, set the the appropriate logging level.
62 | """
63 |
64 | global logger
65 |
66 | if logger is None:
67 | if verbose:
68 | logger = VerboseLogger(sys.stdout, sys.stderr, level)
69 | else:
70 | logger = DefaultLogger(fake_out, fake_err, level)
71 |
72 | logger.level = level
73 |
74 | return logger
75 |
76 |
77 | class Logger(object):
78 | """Base logging class."""
79 |
80 | def __init__(self, out_stream, err_stream, level=LoggingLevelEnum.INFO):
81 | """Create a logger object.
82 |
83 | Parameters
84 | ----------
85 | out_stream : file-like
86 | Standard output stream for non-errors.
87 | err_stream : file-like
88 | Standard error stream for error messages.
89 | level : Optional[LoggingLevelEnum]
90 | The logging level to be used.
91 | """
92 |
93 | self.out = out_stream
94 | self.err = err_stream
95 | self.level = level
96 |
97 | def debug(self, message):
98 | """Write a debugging message to the output stream.
99 |
100 | Parameters
101 | ----------
102 | message : str
103 | The message to be written.
104 | """
105 |
106 | if self.level == LoggingLevelEnum.DEBUG:
107 | self.log(message, self.out)
108 |
109 | def info(self, message):
110 | """Write an informative message to the output stream.
111 |
112 | Parameters
113 | ----------
114 | message : str
115 | The message to be written
116 | """
117 |
118 | if self.level <= LoggingLevelEnum.INFO:
119 | self.log(message, self.out)
120 |
121 | def log(self, message, stream=None):
122 | """Log a message to a given stream.
123 |
124 | Parameters
125 | ----------
126 | message : str
127 | The message to log.
128 | stream : Optiona[file-like]
129 | The stream to write to.
130 | """
131 |
132 | if not stream:
133 | stream = self.out
134 |
135 | stream.write(message)
136 |
137 | def warn(self, message):
138 | """Write a warning to the error stream.
139 |
140 | Parameters
141 | ----------
142 | message : str
143 | The warning message.
144 | """
145 |
146 | self.log(message, self.err)
147 |
148 | def error(self, message):
149 | """Write an error message to the error stream.
150 |
151 | Parameters
152 | ----------
153 | message : str
154 | The error message.
155 | """
156 |
157 | self.log(message, self.err)
158 |
159 |
160 | class DefaultLogger(Logger):
161 | """Default logger for non-verbose mode."""
162 |
163 | def __init__(self, out=fake_out, err=sys.stderr,
164 | level=LoggingLevelEnum.INFO):
165 | """Create a default logger.
166 |
167 | Parameters
168 | ----------
169 | out, err : Optional[file-like]
170 | Streams to write to. Defaults to a dummy output stream, and
171 | `sys.stderr`, respectively.
172 | level : Optional[LoggingLevelEnum]
173 | Logging level to use.
174 | """
175 |
176 | super(DefaultLogger, self).__init__(out, err, level)
177 |
178 |
179 | class VerboseLogger(Logger):
180 | """Verbose mode logger."""
181 |
182 | def __init__(self, out=sys.stdout, err=sys.stderr,
183 | level=LoggingLevelEnum.DEBUG):
184 | """Create a verbose logger.
185 |
186 | Parameters
187 | ----------
188 | out, err : Optional[file-like]
189 | Streams to write to. Defaults to `sys.stdout` and
190 | `sys.stderr`, respectively.
191 | level : Optional[LoggingLevelEnum]
192 | Logging level to use.
193 | """
194 |
195 | super(VerboseLogger, self).__init__(out, err, level)
196 |
--------------------------------------------------------------------------------
/CAOS/mechanisms/__init__.py:
--------------------------------------------------------------------------------
1 | """Mechanisms for the CAOS system."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
6 | from importlib import import_module
7 |
8 | from . import requirements
9 |
10 | from ..dispatch import register_reaction_mechanism
11 |
12 | __mechanisms__ = ('acid_base',)
13 |
14 |
15 | for mechanism in __mechanisms__:
16 | mechanism_module = import_module(
17 | ".{}".format(mechanism), package="CAOS.mechanisms"
18 | )
19 |
20 | function_requirements = [
21 | getattr(requirements, requirement_name)
22 | for requirement_name in mechanism_module.__requirements__
23 | ]
24 | registrator = register_reaction_mechanism(function_requirements)
25 | mechanism_function = getattr(
26 | mechanism_module, "{}_reaction".format(mechanism)
27 | )
28 | registrator(mechanism_function)
29 |
--------------------------------------------------------------------------------
/CAOS/mechanisms/acid_base.py:
--------------------------------------------------------------------------------
1 | """The acid base mechanism implementation."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
6 | from copy import deepcopy
7 |
8 |
9 | __requirements__ = ('pka',)
10 |
11 |
12 | def _get_ideal_hydrogen(acid):
13 | # Todo: Actually compute this here instead of relying on an instance
14 | # variable
15 | return acid.pka_point
16 |
17 |
18 | def _get_hydrogen_acceptor(base):
19 | # Todo: Actually compute this here instead of relying on an instance
20 | # variable
21 | return base.pka_point
22 |
23 |
24 | def _move_hydrogen(conj_base, donate_id, conj_acid, accept_id):
25 | conj_base.remove_node(donate_id)
26 | id_ = conj_acid._next_free_atom_id
27 | conj_acid._add_node(id_, 'H')
28 | conj_acid._add_edge(
29 | conj_acid._next_free_bond_id,
30 | {'nodes': (id_, accept_id), 'order': 1}
31 | )
32 |
33 |
34 | def acid_base_reaction(reactants, conditions):
35 | """Perform an acid base reaction on the reactants.
36 |
37 | Parameters
38 | ----------
39 | reactants: list[Molecule]
40 | The reactants in the reaction.
41 | conditions: dict
42 | The conditions under which the reaction should occur.
43 |
44 | Returns
45 | -------
46 | products: list[Molecule]
47 | The products of the reaction.
48 | """
49 |
50 | # Figure out the acid and the base
51 | acid = reactants[0]
52 | base = reactants[1]
53 |
54 | for reactant in reactants:
55 | if reactant.pka < acid.pka:
56 | acid = reactant
57 | elif reactant.pka > base.pka:
58 | base = reactant
59 |
60 | # Figure out what is going to move and where
61 | donating_hydrogen_id = _get_ideal_hydrogen(acid)
62 | hydrogen_acceptor_id = _get_hydrogen_acceptor(base)
63 |
64 | # Make the conjugate acids, bases, and salt
65 | conjugate_acid = deepcopy(base)
66 | conjugate_base = deepcopy(acid)
67 | salt = None
68 |
69 | _move_hydrogen(
70 | conjugate_base, donating_hydrogen_id,
71 | conjugate_acid, hydrogen_acceptor_id
72 | )
73 |
74 | return [conjugate_acid, conjugate_base, salt]
75 |
--------------------------------------------------------------------------------
/CAOS/mechanisms/requirements/__init__.py:
--------------------------------------------------------------------------------
1 | """Requirements of the different mechanisms."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
6 |
7 | def pka(reactants, conditions):
8 | """Compute the pka of every molecule in the reactants.
9 |
10 | The pka, as well as the id of the "pka_point" is stored in the
11 | molecule. The "pka_point" is the id of the Hydrogen most likely to
12 | be donated, or the id of the atom most likely to accept a Hydrogen.
13 | The pka is based off of the pka_point of the atom.
14 |
15 | Parameters
16 | ----------
17 | reactants: list[Molecule]
18 | A list of reactant molecules.
19 | conditions: dict
20 | Dictionary of conditions.
21 |
22 | Notes
23 | -----
24 | Eventually this will be computed, however right now it just pulls
25 | specified information from the conditions dict.
26 | """
27 |
28 | if 'pkas' in conditions and 'pka_points' in conditions:
29 | for reactant in reactants:
30 | id_ = reactant.id
31 | reactant.pka = conditions['pkas'][id_]
32 | reactant.pka_point = conditions['pka_points'][id_]
33 | return True
34 | return False
35 |
--------------------------------------------------------------------------------
/CAOS/structures/__init__.py:
--------------------------------------------------------------------------------
1 | """Classes and functions related to the structure of molecules."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
--------------------------------------------------------------------------------
/CAOS/structures/molecule.py:
--------------------------------------------------------------------------------
1 | """Classes and functions associated with default molecule objects."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
6 | import json
7 |
8 | import networkx as nx
9 | from networkx.algorithms.isomorphism import is_isomorphic
10 | import six
11 |
12 | from .. import logger
13 | from ..compatibility import range
14 |
15 |
16 | class Molecule(nx.Graph):
17 | """Representation of a molecule as a graph."""
18 |
19 | _ATOM_EXISTS = "ATOM {} exists in the molecule as ID {}"
20 | _BOND_EXISTS = "BOND {} exists in the molecule as ID {}"
21 |
22 | @classmethod
23 | def from_default(cls, other):
24 | """Build a molecule from the default molecule.
25 |
26 | Parameters
27 | ----------
28 | other : Molecule
29 | The default molecule that this should be built from.
30 |
31 | Returns
32 | -------
33 | other : Molecule
34 | The input molecule because this is already a default
35 | molecule.
36 | """
37 |
38 | return other
39 |
40 | def to_default(self):
41 | """Build a default molecule from this instance.
42 |
43 | Returns
44 | -------
45 | default : Molecule
46 | A default molecule that is roughly equivalent to this one.
47 | """
48 |
49 | return self
50 |
51 | def __init__(self, atoms, bonds, **kwargs):
52 | """Initialize a molecule.
53 |
54 | Parameters
55 | ==========
56 | atoms : dict
57 | Mapping from id within the molecule to an atom
58 | bonds : dict
59 | Mapping from id within the molecule to a bond
60 |
61 | Examples
62 | ========
63 | >>> atoms = {
64 | ... 'a1': 'H',
65 | ... 'a2': 'H',
66 | ... 'a3': 'O'
67 | ... }
68 | >>> bonds = {
69 | ... 'b1': {
70 | ... 'nodes': ('a1', 'a3'),
71 | ... 'order': 1
72 | ... }, 'b2': {
73 | ... 'nodes': ('a2', 'a3'),
74 | ... 'order': 1
75 | ... }
76 | ... }
77 | >>> molecule = Molecule(atoms, bonds)
78 | """
79 |
80 | super(Molecule, self).__init__()
81 | self.atoms = atoms
82 | self.bonds = bonds
83 |
84 | for name, value in six.iteritems(kwargs):
85 | if not hasattr(self, name):
86 | setattr(self, name, value)
87 | else:
88 | raise ValueError("Keyword argument {} masks existing name.")
89 |
90 | _atoms = None
91 |
92 | @property
93 | def atoms(self):
94 | """The atoms in the molecule.
95 |
96 | Returns
97 | -------
98 | dict
99 | Dictionary of atoms. The mapping is from string IDs to
100 | atomic identifiers.
101 |
102 | Raises
103 | ------
104 | KeyError
105 | If there is an attempt to add an atom with an existing key,
106 | instead of replacing that atom it raises an error.
107 | """
108 |
109 | if self._atoms is None:
110 | self._atoms = {}
111 | return self._atoms
112 |
113 | @atoms.setter
114 | def atoms(self, atom_dict):
115 | """Set the atoms in the molecule."""
116 |
117 | for id_, symbol in six.iteritems(atom_dict):
118 | self._add_node(id_, symbol)
119 |
120 | def _add_node(self, id_, atomic_symbol):
121 | """Add a node (atom) to the molecule.
122 |
123 | Parameters
124 | ----------
125 | id_ : str
126 | Id of this atom.
127 | atomic_symbol : str
128 | Symbol associated with this atom (i.e. 'H' for Hydrogen).
129 |
130 | Raises
131 | ------
132 | KeyError
133 | You can't add a node if there is already an atom with that
134 | id in the molecule.
135 | """
136 |
137 | if id_ in self.atoms:
138 | message = Molecule._ATOM_EXISTS.format(atomic_symbol, id_)
139 | logger.log(message)
140 | raise KeyError(message)
141 | else:
142 | self.add_node(id_, {'symbol': atomic_symbol})
143 | self.atoms[id_] = atomic_symbol
144 |
145 | _bonds = None
146 |
147 | @property
148 | def bonds(self):
149 | """The bonds within the molecule.
150 |
151 | Returns
152 | -------
153 | dict
154 | Dictionary of the bonds. Mapping is from a string id to a
155 | dictionary of key-value pairs, including the nodes, the
156 | order, and any other pertient information.
157 |
158 | Raises
159 | ------
160 | KeyError
161 | If a bond with a given id already exists an error will be
162 | thrown.
163 | """
164 |
165 | if self._bonds is None:
166 | self._bonds = {}
167 | return self._bonds
168 |
169 | @bonds.setter
170 | def bonds(self, bond_dict):
171 | """Set the bonds in the molecule."""
172 |
173 | for id_, bond in six.iteritems(bond_dict):
174 | self._add_edge(id_, bond)
175 |
176 | def _add_edge(self, id_, bond):
177 | """Add an edge (bond) to the molecule.
178 |
179 | Parameters
180 | ----------
181 | id_ : str
182 | The id for the bond
183 | bond : dict
184 | Important values associated with the bond.
185 |
186 | Raises
187 | ------
188 | KeyError
189 | If a bond with an existing id is added, instead of replacing
190 | the existing one, an error is thrown.
191 | """
192 |
193 | bond['id'] = id_
194 | if id_ in self.bonds:
195 | message = Molecule._BOND_EXISTS.format(bond, id_)
196 | logger.log(message)
197 | raise KeyError(message)
198 | else:
199 | first, second = bond['nodes']
200 | self.add_edge(
201 | first, second,
202 | dict((key, value)
203 | for (key, value) in six.iteritems(bond)
204 | if key != 'nodes')
205 | )
206 | self.bonds[id_] = bond
207 |
208 | @property
209 | def _next_free_atom_id(self):
210 | return self._next_id('a')
211 |
212 | @property
213 | def _next_free_bond_id(self):
214 | return self._next_id('b')
215 |
216 | def _next_id(self, letter):
217 | invalid_nums = set()
218 | if letter == 'a':
219 | for atom_id in self.atoms:
220 | invalid_nums.add(int(atom_id[1:]))
221 | elif letter == 'b':
222 | for bond_id in self.bonds:
223 | invalid_nums.add(int(bond_id[1:]))
224 | else:
225 | raise ValueError(
226 | "What kind of id do you want? "
227 | "Must be an atom ('a') or a bond ('b')."
228 | )
229 |
230 | for i in range(len(invalid_nums) + 1):
231 | if i not in invalid_nums:
232 | return "{}{}".format(letter, i)
233 |
234 | def _node_matcher(self, first, second):
235 | """Check if two nodes are isomorphically equivalent.
236 |
237 | Parameters
238 | ----------
239 | first, second : dict
240 | Dictionaries of the contents of two nodes.
241 |
242 | Returns
243 | -------
244 | bool
245 | Whether or not two nodes are isographically equivalent, in
246 | this case meaning that they have the same atomic symbol.
247 | """
248 |
249 | return first['symbol'] == second['symbol']
250 |
251 | def __eq__(self, other):
252 | return is_isomorphic(self, other, node_match=self._node_matcher)
253 |
254 | def __ne__(self, other):
255 | return not self == other
256 |
257 | def __repr__(self):
258 | return '\n'.join(
259 | [
260 | json.dumps(self.atoms),
261 | json.dumps(self.bonds)
262 | ]
263 | )
264 |
--------------------------------------------------------------------------------
/CAOS/util.py:
--------------------------------------------------------------------------------
1 | """Utility functions that aren't core functionality."""
2 |
3 | from __future__ import print_function, division, unicode_literals, \
4 | absolute_import
5 |
6 |
7 | def raises(exception_types, function, args=None, kwargs=None):
8 | """Return whether or not the given function raises the error.
9 |
10 | Parameters
11 | ==========
12 | exception_types: tuple, Exception
13 | Tuple of the types of the exceptions (or a single type of
14 | exception) that should be caught.
15 | function: callable
16 | The function to be called
17 | args: collection, optional
18 | List of positional arguments to be used
19 | kwargs: mapping, optional
20 | Dictionary of keyword arguments to be used
21 |
22 | Examples
23 | ========
24 | It should return `False` when given a valid value
25 |
26 | >>> raises(ValueError, int, ["3"])
27 | False
28 |
29 | It should return `True` when given an invalid value that results in
30 | the expected error
31 |
32 | >>> raises(ValueError, int, ["hello"])
33 | True
34 |
35 | It should raise an error if it gets an unexpected error
36 |
37 | >>> raises(UnboundLocalError, int, ["hello"])
38 | Traceback (most recent call last):
39 | ...
40 | ValueError: invalid literal for int() with base 10: 'hello'
41 | """
42 |
43 | args = args if args is not None else []
44 | kwargs = kwargs if kwargs is not None else {}
45 | try:
46 | function(*args, **kwargs)
47 | except exception_types:
48 | return True
49 | else:
50 | return False
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 Dan Obermiller
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.rst:
--------------------------------------------------------------------------------
1 | CAOS - Computer Assisted Organic Synthesis (in Python!)
2 | =======================================================
3 |
4 | |Build status| |Coverage Status| |Documentation Status|
5 |
6 | CAOS is a useful tool for many organic chemists, but is often a hard one
7 | to use in practice. This library will seek to provide an easy method of
8 | predicting reactions.
9 |
10 | Documentation
11 | ~~~~~~~~~~~~~
12 |
13 | Is available at `readthedocs.org `__.
14 |
15 | Examples
16 | ~~~~~~~~
17 |
18 | Simple reactions can be performed in this way
19 |
20 | .. code:: python
21 |
22 | from CAOS.dispatch import react
23 | from CAOS.structures.molecule import Molecule
24 |
25 | acid = Molecule(
26 | {'a1': 'H', 'a2': 'H', 'a3': 'H', 'a4': 'O'},
27 | {'b1': {'nodes': ('a1', 'a4'), 'order': 1},
28 | 'b2': {'nodes': ('a2', 'a4'), 'order': 1},
29 | 'b3': {'nodes': ('a3', 'a4'), 'order': 1}
30 | },
31 | **{'id': 'Hydronium'}
32 | )
33 |
34 | base = Molecule(
35 | {'a1': 'H', 'a2': 'O'},
36 | {'b1': {'nodes': ('a1', 'a2'), 'order': 1}},
37 | **{'id': 'Hydroxide'}
38 | )
39 |
40 | conditions = {
41 | 'pkas': {'Hydronium': -1.74, 'Hydroxide': 15.7},
42 | 'pka_points': {'Hydronium': 'a1', 'Hydroxide': 'a2'}
43 | }
44 |
45 | products = react([acid, base], conditions)
46 |
47 | In this case, based on the information in the molecules and the conditions,
48 | the system will predict an acid base reaction that results in the creation of
49 | two water molecules and no salt.
50 |
51 | Additionally, user-defined reaction mechanisms can be added to the system.
52 |
53 | .. code:: python
54 |
55 | # aqueous_mechanism.py
56 | from CAOS.dispatch import register_reaction_mechanism
57 |
58 | def aqueous(reactants, conditions):
59 | return conditions.get('aqeuous', False)
60 |
61 | @register_reaction_mechanism([aqueous])
62 | def some_mechanism(reactants, conditions):
63 | # do something
64 | return products
65 |
66 | # reaction.py
67 | import aqueous_mechanism
68 | from CAOS.dispatch import react
69 | from CAOS.structures.molecule import Molecule
70 |
71 | m1 = Molecule(...)
72 | m2 = Molecule(...)
73 | conditions = {'aqueous': True}
74 |
75 | products = react([m1, m2], conditions)
76 |
77 | Here the system would use the aqueous mechanism that you have defined,
78 | because the conditions match the aqueous requirement the mechanism was
79 | decorated with.
80 |
81 | The system is under active development, and the goal is to eventually
82 | take as much of the work out of the hands of the user.
83 |
84 |
85 | Todos:
86 | ~~~~~~
87 |
88 | - [X] Add CI
89 | - [X] Add reaction registration and dispatch
90 | - [ ] Add loading molecules
91 | - [X] Add molecule inspection
92 | - [ ] Add common requirements functions
93 | - [ ] ???
94 |
95 | CAOS is still in early stages of development. Information will be added
96 | as it becomes available.
97 |
98 | Motivation
99 | ----------
100 |
101 | This is a project for my Fall 2015 DSLs class. It is loosely based off
102 | of a `previous project `__ however
103 | with the intent of being more modular, extensible, and language-like.
104 |
105 | Licensing
106 | ~~~~~~~~~
107 |
108 | CAOS is licensed using the `MIT License `_.
109 |
110 | .. include:: ../LICENSE
111 |
112 | .. |Build status| image:: https://travis-ci.org/PyCAOS/CAOS.svg?branch=master
113 | :target: https://travis-ci.org/PyCAOS/CAOS
114 | .. |Coverage Status| image:: https://coveralls.io/repos/PyCAOS/CAOS/badge.svg?branch=master&service=github
115 | :target: https://coveralls.io/github/PyCAOS/CAOS?branch=master
116 | .. |Documentation Status| image:: https://readthedocs.org/projects/caos/badge/?version=latest
117 | :target: http://caos.readthedocs.org/en/latest/?badge=latest
118 |
--------------------------------------------------------------------------------
/docs-requirements.txt:
--------------------------------------------------------------------------------
1 | numpydoc
2 | sphinx
3 | sphinx-autobuild
--------------------------------------------------------------------------------
/docs/CAOS.exceptions.rst:
--------------------------------------------------------------------------------
1 | CAOS.exceptions package
2 | =======================
3 |
4 | Module contents
5 | ---------------
6 |
7 | .. automodule:: CAOS.exceptions
8 | :members:
9 | :undoc-members:
10 | :show-inheritance:
11 |
12 | Submodules
13 | ----------
14 |
15 | CAOS.exceptions.dispatch_errors module
16 | --------------------------------------
17 |
18 | .. automodule:: CAOS.exceptions.dispatch_errors
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
23 | CAOS.exceptions.reaction_errors module
24 | --------------------------------------
25 |
26 | .. automodule:: CAOS.exceptions.reaction_errors
27 | :members:
28 | :undoc-members:
29 | :show-inheritance:
30 |
--------------------------------------------------------------------------------
/docs/CAOS.mechanisms.rst:
--------------------------------------------------------------------------------
1 | CAOS.mechanisms package
2 | =======================
3 |
4 | Subpackages
5 | -----------
6 |
7 | .. toctree::
8 |
9 | CAOS.requirements.rst
10 |
11 | Module contents
12 | ---------------
13 |
14 | .. automodule:: CAOS.mechanisms
15 | :members:
16 | :undoc-members:
17 | :show-inheritance:
18 |
19 | Submodules
20 | ----------
21 |
22 | CAOS.mechanisms.acid_base module
23 | ---------------=----------------
24 |
25 | .. automodule:: CAOS.mechanisms.acid_base
26 | :members:
27 | :undoc-members:
28 | :show-inheritance:
29 |
--------------------------------------------------------------------------------
/docs/CAOS.requirements.rst:
--------------------------------------------------------------------------------
1 | CAOS.mechanisms.requirements package
2 | =========================
3 |
4 | Module contents
5 | ---------------
6 |
7 | .. automodule:: CAOS.mechanisms.requirements
8 | :members:
9 | :undoc-members:
10 | :show-inheritance:
11 |
--------------------------------------------------------------------------------
/docs/CAOS.rst:
--------------------------------------------------------------------------------
1 | CAOS package
2 | ============
3 |
4 | Module contents
5 | ---------------
6 |
7 | .. automodule:: CAOS
8 | :members:
9 | :undoc-members:
10 | :show-inheritance:
11 |
12 | Submodules
13 | ----------
14 |
15 | CAOS.dispatch module
16 | --------------------
17 |
18 | .. automodule:: CAOS.dispatch
19 | :members:
20 | :undoc-members:
21 |
22 | CAOS.logging module
23 | ------------------------
24 |
25 | .. automodule:: CAOS.logging
26 | :members:
27 | :undoc-members:
28 | :show-inheritance:
29 |
30 | CAOS.util module
31 | ----------------
32 |
33 | .. automodule:: CAOS.util
34 | :members:
35 | :undoc-members:
36 |
37 | Subpackages
38 | -----------
39 |
40 | .. toctree::
41 |
42 | CAOS.mechanisms
43 | CAOS.structures
44 | CAOS.exceptions
45 |
--------------------------------------------------------------------------------
/docs/CAOS.structures.rst:
--------------------------------------------------------------------------------
1 | CAOS.structures package
2 | =======================
3 |
4 | Module contents
5 | ---------------
6 |
7 | .. automodule:: CAOS.structures
8 | :members:
9 | :undoc-members:
10 | :show-inheritance:
11 |
12 | Submodules
13 | ----------
14 |
15 | CAOS.structures.molecule module
16 | -------------------------------
17 |
18 | .. automodule:: CAOS.structures.molecule
19 | :members:
20 | :undoc-members:
21 | :show-inheritance:
22 |
--------------------------------------------------------------------------------
/docs/Makefile:
--------------------------------------------------------------------------------
1 | # Makefile for Sphinx documentation
2 | #
3 |
4 | # You can set these variables from the command line.
5 | SPHINXOPTS =
6 | SPHINXBUILD = sphinx-build
7 | PAPER =
8 | BUILDDIR = _build
9 |
10 | # User-friendly check for sphinx-build
11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1)
12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/)
13 | endif
14 |
15 | # Internal variables.
16 | PAPEROPT_a4 = -D latex_paper_size=a4
17 | PAPEROPT_letter = -D latex_paper_size=letter
18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
19 | # the i18n builder cannot share the environment and doctrees with the others
20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) .
21 |
22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext
23 |
24 | help:
25 | @echo "Please use \`make ' where is one of"
26 | @echo " html to make standalone HTML files"
27 | @echo " dirhtml to make HTML files named index.html in directories"
28 | @echo " singlehtml to make a single large HTML file"
29 | @echo " pickle to make pickle files"
30 | @echo " json to make JSON files"
31 | @echo " htmlhelp to make HTML files and a HTML help project"
32 | @echo " qthelp to make HTML files and a qthelp project"
33 | @echo " devhelp to make HTML files and a Devhelp project"
34 | @echo " epub to make an epub"
35 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter"
36 | @echo " latexpdf to make LaTeX files and run them through pdflatex"
37 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx"
38 | @echo " text to make text files"
39 | @echo " man to make manual pages"
40 | @echo " texinfo to make Texinfo files"
41 | @echo " info to make Texinfo files and run them through makeinfo"
42 | @echo " gettext to make PO message catalogs"
43 | @echo " changes to make an overview of all changed/added/deprecated items"
44 | @echo " xml to make Docutils-native XML files"
45 | @echo " pseudoxml to make pseudoxml-XML files for display purposes"
46 | @echo " linkcheck to check all external links for integrity"
47 | @echo " doctest to run all doctests embedded in the documentation (if enabled)"
48 |
49 | clean:
50 | rm -rf $(BUILDDIR)/*
51 |
52 | html:
53 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html
54 | @echo
55 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html."
56 |
57 | dirhtml:
58 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml
59 | @echo
60 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml."
61 |
62 | singlehtml:
63 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml
64 | @echo
65 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml."
66 |
67 | pickle:
68 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle
69 | @echo
70 | @echo "Build finished; now you can process the pickle files."
71 |
72 | json:
73 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json
74 | @echo
75 | @echo "Build finished; now you can process the JSON files."
76 |
77 | htmlhelp:
78 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp
79 | @echo
80 | @echo "Build finished; now you can run HTML Help Workshop with the" \
81 | ".hhp project file in $(BUILDDIR)/htmlhelp."
82 |
83 | qthelp:
84 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp
85 | @echo
86 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \
87 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:"
88 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/CAOS.qhcp"
89 | @echo "To view the help file:"
90 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/CAOS.qhc"
91 |
92 | devhelp:
93 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp
94 | @echo
95 | @echo "Build finished."
96 | @echo "To view the help file:"
97 | @echo "# mkdir -p $$HOME/.local/share/devhelp/CAOS"
98 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/CAOS"
99 | @echo "# devhelp"
100 |
101 | epub:
102 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub
103 | @echo
104 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub."
105 |
106 | latex:
107 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
108 | @echo
109 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex."
110 | @echo "Run \`make' in that directory to run these through (pdf)latex" \
111 | "(use \`make latexpdf' here to do that automatically)."
112 |
113 | latexpdf:
114 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
115 | @echo "Running LaTeX files through pdflatex..."
116 | $(MAKE) -C $(BUILDDIR)/latex all-pdf
117 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
118 |
119 | latexpdfja:
120 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex
121 | @echo "Running LaTeX files through platex and dvipdfmx..."
122 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja
123 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex."
124 |
125 | text:
126 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text
127 | @echo
128 | @echo "Build finished. The text files are in $(BUILDDIR)/text."
129 |
130 | man:
131 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man
132 | @echo
133 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man."
134 |
135 | texinfo:
136 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
137 | @echo
138 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo."
139 | @echo "Run \`make' in that directory to run these through makeinfo" \
140 | "(use \`make info' here to do that automatically)."
141 |
142 | info:
143 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo
144 | @echo "Running Texinfo files through makeinfo..."
145 | make -C $(BUILDDIR)/texinfo info
146 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo."
147 |
148 | gettext:
149 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale
150 | @echo
151 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale."
152 |
153 | changes:
154 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes
155 | @echo
156 | @echo "The overview file is in $(BUILDDIR)/changes."
157 |
158 | linkcheck:
159 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck
160 | @echo
161 | @echo "Link check complete; look for any errors in the above output " \
162 | "or in $(BUILDDIR)/linkcheck/output.txt."
163 |
164 | doctest:
165 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest
166 | @echo "Testing of doctests in the sources finished, look at the " \
167 | "results in $(BUILDDIR)/doctest/output.txt."
168 |
169 | xml:
170 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml
171 | @echo
172 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml."
173 |
174 | pseudoxml:
175 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml
176 | @echo
177 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml."
178 |
--------------------------------------------------------------------------------
/docs/conf.py:
--------------------------------------------------------------------------------
1 | # -*- coding: utf-8 -*-
2 | #
3 | # CAOS documentation build configuration file, created by
4 | # sphinx-quickstart on Sun Nov 08 22:28:08 2015.
5 | #
6 | # This file is execfile()d with the current directory set to its
7 | # containing dir.
8 | #
9 | # Note that not all possible configuration values are present in this
10 | # autogenerated file.
11 | #
12 | # All configuration values have a default; values that are commented out
13 | # serve to show the default.
14 |
15 | import sys
16 | import os
17 |
18 | # If extensions (or modules to document with autodoc) are in another directory,
19 | # add these directories to sys.path here. If the directory is relative to the
20 | # documentation root, use os.path.abspath to make it absolute, like shown here.
21 | sys.path.insert(0, os.path.abspath('..'))
22 |
23 | # http://stackoverflow.com/a/15210813/3076272
24 | numpydoc_show_class_members = False
25 |
26 | import sphinx.environment
27 | from docutils.utils import get_source_line
28 |
29 | # http://stackoverflow.com/a/28778969/3076272
30 | def _warn_node(self, msg, node):
31 | if not msg.startswith('nonlocal image URI found:'):
32 | self._warnfunc(msg, '%s:%s' % get_source_line(node))
33 |
34 | sphinx.environment.BuildEnvironment.warn_node = _warn_node
35 |
36 | # -- General configuration ------------------------------------------------
37 |
38 | # If your documentation needs a minimal Sphinx version, state it here.
39 | #needs_sphinx = '1.0'
40 |
41 | # Add any Sphinx extension module names here, as strings. They can be
42 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
43 | # ones.
44 | extensions = [
45 | 'sphinx.ext.autodoc',
46 | 'sphinx.ext.autosummary',
47 | 'sphinx.ext.doctest',
48 | 'sphinx.ext.coverage',
49 | 'sphinx.ext.viewcode',
50 | 'numpydoc'
51 | ]
52 |
53 | # Add any paths that contain templates here, relative to this directory.
54 | templates_path = ['_templates']
55 |
56 | # The suffix of source filenames.
57 | source_suffix = '.rst'
58 |
59 | # The encoding of source files.
60 | #source_encoding = 'utf-8-sig'
61 |
62 | # The master toctree document.
63 | master_doc = 'index'
64 |
65 | # General information about the project.
66 | project = u'CAOS'
67 | copyright = u'2015, Dan Obermiller'
68 |
69 | # The version info for the project you're documenting, acts as replacement for
70 | # |version| and |release|, also used in various other places throughout the
71 | # built documents.
72 | #
73 | # The short X.Y version.
74 | version = '0.4.0'
75 | # The full version, including alpha/beta/rc tags.
76 | release = '0.4.0'
77 |
78 | # The language for content autogenerated by Sphinx. Refer to documentation
79 | # for a list of supported languages.
80 | #language = None
81 |
82 | # There are two options for replacing |today|: either, you set today to some
83 | # non-false value, then it is used:
84 | #today = ''
85 | # Else, today_fmt is used as the format for a strftime call.
86 | #today_fmt = '%B %d, %Y'
87 |
88 | # List of patterns, relative to source directory, that match files and
89 | # directories to ignore when looking for source files.
90 | exclude_patterns = ['_build']
91 |
92 | # The reST default role (used for this markup: `text`) to use for all
93 | # documents.
94 | #default_role = None
95 |
96 | # If true, '()' will be appended to :func: etc. cross-reference text.
97 | #add_function_parentheses = True
98 |
99 | # If true, the current module name will be prepended to all description
100 | # unit titles (such as .. function::).
101 | #add_module_names = True
102 |
103 | # If true, sectionauthor and moduleauthor directives will be shown in the
104 | # output. They are ignored by default.
105 | #show_authors = False
106 |
107 | # The name of the Pygments (syntax highlighting) style to use.
108 | pygments_style = 'sphinx'
109 |
110 | # A list of ignored prefixes for module index sorting.
111 | #modindex_common_prefix = []
112 |
113 | # If true, keep warnings as "system message" paragraphs in the built documents.
114 | #keep_warnings = False
115 |
116 |
117 | # -- Options for HTML output ----------------------------------------------
118 |
119 | # The theme to use for HTML and HTML Help pages. See the documentation for
120 | # a list of builtin themes.
121 | html_theme = 'alabaster'
122 |
123 | # Theme options are theme-specific and customize the look and feel of a theme
124 | # further. For a list of options available for each theme, see the
125 | # documentation.
126 | #html_theme_options = {}
127 |
128 | # Add any paths that contain custom themes here, relative to this directory.
129 | #html_theme_path = []
130 |
131 | # The name for this set of Sphinx documents. If None, it defaults to
132 | # " v documentation".
133 | #html_title = None
134 |
135 | # A shorter title for the navigation bar. Default is the same as html_title.
136 | #html_short_title = None
137 |
138 | # The name of an image file (relative to this directory) to place at the top
139 | # of the sidebar.
140 | #html_logo = None
141 |
142 | # The name of an image file (within the static path) to use as favicon of the
143 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32
144 | # pixels large.
145 | #html_favicon = None
146 |
147 | # Add any paths that contain custom static files (such as style sheets) here,
148 | # relative to this directory. They are copied after the builtin static files,
149 | # so a file named "default.css" will overwrite the builtin "default.css".
150 | html_static_path = ['_static']
151 |
152 | # Add any extra paths that contain custom files (such as robots.txt or
153 | # .htaccess) here, relative to this directory. These files are copied
154 | # directly to the root of the documentation.
155 | #html_extra_path = []
156 |
157 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
158 | # using the given strftime format.
159 | #html_last_updated_fmt = '%b %d, %Y'
160 |
161 | # If true, SmartyPants will be used to convert quotes and dashes to
162 | # typographically correct entities.
163 | #html_use_smartypants = True
164 |
165 | # Custom sidebar templates, maps document names to template names.
166 | #html_sidebars = {}
167 |
168 | # Additional templates that should be rendered to pages, maps page names to
169 | # template names.
170 | #html_additional_pages = {}
171 |
172 | # If false, no module index is generated.
173 | #html_domain_indices = True
174 |
175 | # If false, no index is generated.
176 | #html_use_index = True
177 |
178 | # If true, the index is split into individual pages for each letter.
179 | #html_split_index = False
180 |
181 | # If true, links to the reST sources are added to the pages.
182 | #html_show_sourcelink = True
183 |
184 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
185 | #html_show_sphinx = True
186 |
187 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
188 | #html_show_copyright = True
189 |
190 | # If true, an OpenSearch description file will be output, and all pages will
191 | # contain a tag referring to it. The value of this option must be the
192 | # base URL from which the finished HTML is served.
193 | #html_use_opensearch = ''
194 |
195 | # This is the file name suffix for HTML files (e.g. ".xhtml").
196 | #html_file_suffix = None
197 |
198 | # Output file base name for HTML help builder.
199 | htmlhelp_basename = 'CAOSdoc'
200 |
201 |
202 | # -- Options for LaTeX output ---------------------------------------------
203 |
204 | latex_elements = {
205 | # The paper size ('letterpaper' or 'a4paper').
206 | #'papersize': 'letterpaper',
207 |
208 | # The font size ('10pt', '11pt' or '12pt').
209 | #'pointsize': '10pt',
210 |
211 | # Additional stuff for the LaTeX preamble.
212 | #'preamble': '',
213 | }
214 |
215 | # Grouping the document tree into LaTeX files. List of tuples
216 | # (source start file, target name, title,
217 | # author, documentclass [howto, manual, or own class]).
218 | latex_documents = [
219 | ('index', 'CAOS.tex', u'CAOS Documentation',
220 | u'Dan Obermiller', 'manual'),
221 | ]
222 |
223 | # The name of an image file (relative to this directory) to place at the top of
224 | # the title page.
225 | #latex_logo = None
226 |
227 | # For "manual" documents, if this is true, then toplevel headings are parts,
228 | # not chapters.
229 | #latex_use_parts = False
230 |
231 | # If true, show page references after internal links.
232 | #latex_show_pagerefs = False
233 |
234 | # If true, show URL addresses after external links.
235 | #latex_show_urls = False
236 |
237 | # Documents to append as an appendix to all manuals.
238 | #latex_appendices = []
239 |
240 | # If false, no module index is generated.
241 | #latex_domain_indices = True
242 |
243 |
244 | # -- Options for manual page output ---------------------------------------
245 |
246 | # One entry per manual page. List of tuples
247 | # (source start file, name, description, authors, manual section).
248 | man_pages = [
249 | ('index', 'caos', u'CAOS Documentation',
250 | [u'Dan Obermiller'], 1)
251 | ]
252 |
253 | # If true, show URL addresses after external links.
254 | #man_show_urls = False
255 |
256 |
257 | # -- Options for Texinfo output -------------------------------------------
258 |
259 | # Grouping the document tree into Texinfo files. List of tuples
260 | # (source start file, target name, title, author,
261 | # dir menu entry, description, category)
262 | texinfo_documents = [
263 | ('index', 'CAOS', u'CAOS Documentation',
264 | u'Dan Obermiller', 'CAOS', 'One line description of project.',
265 | 'Miscellaneous'),
266 | ]
267 |
268 | # Documents to append as an appendix to all manuals.
269 | #texinfo_appendices = []
270 |
271 | # If false, no module index is generated.
272 | #texinfo_domain_indices = True
273 |
274 | # How to display URL addresses: 'footnote', 'no', or 'inline'.
275 | #texinfo_show_urls = 'footnote'
276 |
277 | # If true, do not generate a @detailmenu in the "Top" node's menu.
278 | #texinfo_no_detailmenu = False
279 |
--------------------------------------------------------------------------------
/docs/index.rst:
--------------------------------------------------------------------------------
1 | .. CAOS documentation master file, created by
2 | sphinx-quickstart on Sun Nov 08 22:28:08 2015.
3 | You can adapt this file completely to your liking, but it should at least
4 | contain the root `toctree` directive.
5 |
6 | .. include:: ../README.rst
7 |
8 | .. toctree::
9 | :maxdepth: 2
10 |
11 | CAOS
12 |
13 | Indices and tables
14 | ==================
15 |
16 | * :ref:`genindex`
17 | * :ref:`modindex`
18 | * :ref:`search`
19 |
20 |
--------------------------------------------------------------------------------
/docs/make.bat:
--------------------------------------------------------------------------------
1 | @ECHO OFF
2 |
3 | REM Command file for Sphinx documentation
4 |
5 | if "%SPHINXBUILD%" == "" (
6 | set SPHINXBUILD=sphinx-build
7 | )
8 | set BUILDDIR=_build
9 | set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% .
10 | set I18NSPHINXOPTS=%SPHINXOPTS% .
11 | if NOT "%PAPER%" == "" (
12 | set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS%
13 | set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS%
14 | )
15 |
16 | if "%1" == "" goto help
17 |
18 | if "%1" == "help" (
19 | :help
20 | echo.Please use `make ^` where ^ is one of
21 | echo. html to make standalone HTML files
22 | echo. dirhtml to make HTML files named index.html in directories
23 | echo. singlehtml to make a single large HTML file
24 | echo. pickle to make pickle files
25 | echo. json to make JSON files
26 | echo. htmlhelp to make HTML files and a HTML help project
27 | echo. qthelp to make HTML files and a qthelp project
28 | echo. devhelp to make HTML files and a Devhelp project
29 | echo. epub to make an epub
30 | echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter
31 | echo. text to make text files
32 | echo. man to make manual pages
33 | echo. texinfo to make Texinfo files
34 | echo. gettext to make PO message catalogs
35 | echo. changes to make an overview over all changed/added/deprecated items
36 | echo. xml to make Docutils-native XML files
37 | echo. pseudoxml to make pseudoxml-XML files for display purposes
38 | echo. linkcheck to check all external links for integrity
39 | echo. doctest to run all doctests embedded in the documentation if enabled
40 | goto end
41 | )
42 |
43 | if "%1" == "clean" (
44 | for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i
45 | del /q /s %BUILDDIR%\*
46 | goto end
47 | )
48 |
49 |
50 | %SPHINXBUILD% 2> nul
51 | if errorlevel 9009 (
52 | echo.
53 | echo.The 'sphinx-build' command was not found. Make sure you have Sphinx
54 | echo.installed, then set the SPHINXBUILD environment variable to point
55 | echo.to the full path of the 'sphinx-build' executable. Alternatively you
56 | echo.may add the Sphinx directory to PATH.
57 | echo.
58 | echo.If you don't have Sphinx installed, grab it from
59 | echo.http://sphinx-doc.org/
60 | exit /b 1
61 | )
62 |
63 | if "%1" == "html" (
64 | %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html
65 | if errorlevel 1 exit /b 1
66 | echo.
67 | echo.Build finished. The HTML pages are in %BUILDDIR%/html.
68 | goto end
69 | )
70 |
71 | if "%1" == "dirhtml" (
72 | %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml
73 | if errorlevel 1 exit /b 1
74 | echo.
75 | echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml.
76 | goto end
77 | )
78 |
79 | if "%1" == "singlehtml" (
80 | %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml
81 | if errorlevel 1 exit /b 1
82 | echo.
83 | echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml.
84 | goto end
85 | )
86 |
87 | if "%1" == "pickle" (
88 | %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle
89 | if errorlevel 1 exit /b 1
90 | echo.
91 | echo.Build finished; now you can process the pickle files.
92 | goto end
93 | )
94 |
95 | if "%1" == "json" (
96 | %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json
97 | if errorlevel 1 exit /b 1
98 | echo.
99 | echo.Build finished; now you can process the JSON files.
100 | goto end
101 | )
102 |
103 | if "%1" == "htmlhelp" (
104 | %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp
105 | if errorlevel 1 exit /b 1
106 | echo.
107 | echo.Build finished; now you can run HTML Help Workshop with the ^
108 | .hhp project file in %BUILDDIR%/htmlhelp.
109 | goto end
110 | )
111 |
112 | if "%1" == "qthelp" (
113 | %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp
114 | if errorlevel 1 exit /b 1
115 | echo.
116 | echo.Build finished; now you can run "qcollectiongenerator" with the ^
117 | .qhcp project file in %BUILDDIR%/qthelp, like this:
118 | echo.^> qcollectiongenerator %BUILDDIR%\qthelp\CAOS.qhcp
119 | echo.To view the help file:
120 | echo.^> assistant -collectionFile %BUILDDIR%\qthelp\CAOS.ghc
121 | goto end
122 | )
123 |
124 | if "%1" == "devhelp" (
125 | %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp
126 | if errorlevel 1 exit /b 1
127 | echo.
128 | echo.Build finished.
129 | goto end
130 | )
131 |
132 | if "%1" == "epub" (
133 | %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub
134 | if errorlevel 1 exit /b 1
135 | echo.
136 | echo.Build finished. The epub file is in %BUILDDIR%/epub.
137 | goto end
138 | )
139 |
140 | if "%1" == "latex" (
141 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
142 | if errorlevel 1 exit /b 1
143 | echo.
144 | echo.Build finished; the LaTeX files are in %BUILDDIR%/latex.
145 | goto end
146 | )
147 |
148 | if "%1" == "latexpdf" (
149 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
150 | cd %BUILDDIR%/latex
151 | make all-pdf
152 | cd %BUILDDIR%/..
153 | echo.
154 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
155 | goto end
156 | )
157 |
158 | if "%1" == "latexpdfja" (
159 | %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex
160 | cd %BUILDDIR%/latex
161 | make all-pdf-ja
162 | cd %BUILDDIR%/..
163 | echo.
164 | echo.Build finished; the PDF files are in %BUILDDIR%/latex.
165 | goto end
166 | )
167 |
168 | if "%1" == "text" (
169 | %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text
170 | if errorlevel 1 exit /b 1
171 | echo.
172 | echo.Build finished. The text files are in %BUILDDIR%/text.
173 | goto end
174 | )
175 |
176 | if "%1" == "man" (
177 | %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man
178 | if errorlevel 1 exit /b 1
179 | echo.
180 | echo.Build finished. The manual pages are in %BUILDDIR%/man.
181 | goto end
182 | )
183 |
184 | if "%1" == "texinfo" (
185 | %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo
186 | if errorlevel 1 exit /b 1
187 | echo.
188 | echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo.
189 | goto end
190 | )
191 |
192 | if "%1" == "gettext" (
193 | %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale
194 | if errorlevel 1 exit /b 1
195 | echo.
196 | echo.Build finished. The message catalogs are in %BUILDDIR%/locale.
197 | goto end
198 | )
199 |
200 | if "%1" == "changes" (
201 | %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes
202 | if errorlevel 1 exit /b 1
203 | echo.
204 | echo.The overview file is in %BUILDDIR%/changes.
205 | goto end
206 | )
207 |
208 | if "%1" == "linkcheck" (
209 | %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck
210 | if errorlevel 1 exit /b 1
211 | echo.
212 | echo.Link check complete; look for any errors in the above output ^
213 | or in %BUILDDIR%/linkcheck/output.txt.
214 | goto end
215 | )
216 |
217 | if "%1" == "doctest" (
218 | %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest
219 | if errorlevel 1 exit /b 1
220 | echo.
221 | echo.Testing of doctests in the sources finished, look at the ^
222 | results in %BUILDDIR%/doctest/output.txt.
223 | goto end
224 | )
225 |
226 | if "%1" == "xml" (
227 | %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml
228 | if errorlevel 1 exit /b 1
229 | echo.
230 | echo.Build finished. The XML files are in %BUILDDIR%/xml.
231 | goto end
232 | )
233 |
234 | if "%1" == "pseudoxml" (
235 | %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml
236 | if errorlevel 1 exit /b 1
237 | echo.
238 | echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml.
239 | goto end
240 | )
241 |
242 | :end
243 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | six
2 | networkx==1.9.1
3 |
--------------------------------------------------------------------------------
/setup.cfg:
--------------------------------------------------------------------------------
1 | [coverage:run]
2 | branch = True
3 | omit = */python??/*
4 | */python?.?/*
5 | */site-packages/*
6 | */tests/*
7 | */docs/*
8 | */compatibility.py
9 | */logging.py
10 |
11 | [nosetests]
12 | with-doctest = 1
13 | with-coverage = 1
14 | cover-package = CAOS
15 | verbosity = 3
16 |
17 | [flake8]
18 | # 79 is a silly line length to adhere to
19 | max-line-length = 80
20 | exclude = tests,.git,__pycache,docs
21 | max-complexity = 10
22 | verbose = 1
23 | ignore = F401
24 |
25 | [pep257]
26 | # ignore magic method docstrings: D105
27 | # ignore new lines after function docstrings: D202
28 | # ignore silly linebreak (non standard): D203
29 | ignore = D105,D202,D203
30 | # Ignore docs folder, tests folder, any folder starting with .
31 | match-dir='[^\.]|(?!docs)|(?!tests).*'
32 |
--------------------------------------------------------------------------------
/testing-requirements.txt:
--------------------------------------------------------------------------------
1 | coveralls
2 | coverage
3 | nose
4 | flake8
5 | flake8-todo
6 | pep8-naming
7 | pep257
8 |
--------------------------------------------------------------------------------
/tests/test_dispatch.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, division, unicode_literals, \
2 | absolute_import
3 |
4 | from CAOS.dispatch import register_reaction_mechanism, reaction_is_registered, \
5 | ReactionDispatcher, react
6 | from CAOS.util import raises
7 | from CAOS.exceptions.dispatch_errors import InvalidReactionError, \
8 | ExistingReactionError
9 |
10 |
11 | def teardown_module():
12 | for key in map('reaction{}'.format, [1, 2, 4]):
13 | del ReactionDispatcher._test_namespace[key]
14 |
15 |
16 | def vacuous(*_, **__):
17 | return True
18 |
19 |
20 | def test_register_simple_reaction():
21 | @register_reaction_mechanism([vacuous], True)
22 | def reaction1(reactants, conditions):
23 | return 42
24 |
25 | assert reaction_is_registered('reaction1', True)
26 | assert reaction_is_registered(reaction1, True)
27 |
28 |
29 | def test_register_simple_reaction_with_requirements():
30 | def magic(reactants, conditions):
31 | return reactants and conditions
32 |
33 | @register_reaction_mechanism([magic], True)
34 | def reaction2(reactants, conditions):
35 | return 36
36 |
37 | assert reaction_is_registered('reaction2', True)
38 | assert reaction_is_registered(reaction2, True)
39 |
40 |
41 | def test_register_simple_reaction_with_invalid_requirements():
42 | voodoo = 17
43 | function = register_reaction_mechanism([voodoo], True)
44 |
45 | def reaction3(reactants, conditions):
46 | return 11
47 |
48 | args = [reaction3]
49 |
50 | assert raises(InvalidReactionError, function, args)
51 | assert not reaction_is_registered('reaction3', True)
52 | assert not reaction_is_registered(reaction3, True)
53 |
54 |
55 | def test_register_existing_reaction():
56 | function = register_reaction_mechanism([vacuous], True)
57 |
58 | def reaction2(*args):
59 | pass
60 |
61 | args = [reaction2]
62 |
63 | assert raises(ExistingReactionError, function, args)
64 |
65 |
66 | def test_mechanism_not_modified_by_decorator():
67 | def reaction4(*args):
68 | return 42
69 |
70 | decorated = register_reaction_mechanism([vacuous], True)(reaction4)
71 | assert reaction4 is decorated
72 | assert reaction4() == decorated()
73 |
74 | assert reaction_is_registered('reaction4', True)
75 | assert reaction_is_registered(reaction4, True)
76 |
77 |
78 | def test_no_requirements_error():
79 | registrator = register_reaction_mechanism([], True)
80 | assert raises(InvalidReactionError, registrator, ((lambda x, y: None),))
81 |
82 |
83 | def test_not_callable_has_name():
84 | class _(object):
85 | __name__ = 'dumb'
86 |
87 | registrator = register_reaction_mechanism([_()], True)
88 |
89 | assert raises(InvalidReactionError, registrator, ((lambda x, y: None),))
90 |
--------------------------------------------------------------------------------
/tests/test_mechanisms/test_acid_base_reaction.py:
--------------------------------------------------------------------------------
1 | from CAOS.structures.molecule import Molecule
2 | from CAOS.dispatch import react
3 |
4 |
5 | def test_simple_acid_base_reaction():
6 | acid = Molecule(
7 | {'a1': 'H', 'a2': 'H', 'a3': 'H', 'a4': 'O'},
8 | {'b1': {'nodes': ('a1', 'a4'), 'order': 1},
9 | 'b2': {'nodes': ('a2', 'a4'), 'order': 1},
10 | 'b3': {'nodes': ('a3', 'a4'), 'order': 1}
11 | },
12 | **{'id': 'Hydronium'}
13 | )
14 |
15 | base = Molecule(
16 | {'a1': 'H', 'a2': 'O'},
17 | {'b1': {'nodes': ('a1', 'a2'), 'order': 1}},
18 | **{'id': 'Hydroxide'}
19 | )
20 |
21 | conditions = {
22 | 'pkas': {'Hydronium': -1.74, 'Hydroxide': 15.7},
23 | 'pka_points': {'Hydronium': 'a1', 'Hydroxide': 'a2'}
24 | }
25 |
26 | products = react([acid, base], conditions)
27 |
28 | conjugate_acid = Molecule(
29 | {'a1': 'H', 'a2': 'H', 'a3': 'O'},
30 | {'b1': {'nodes': ('a1', 'a3'), 'order': 1},
31 | 'b2': {'nodes': ('a2', 'a3'), 'order': 1}
32 | }
33 | )
34 |
35 | conjugate_base = Molecule(
36 | {'a1': 'H', 'a2': 'H', 'a3': 'O'},
37 | {'b1': {'nodes': ('a1', 'a3'), 'order': 1},
38 | 'b2': {'nodes': ('a2', 'a3'), 'order': 1}
39 | }
40 | )
41 |
42 | assert products[0] == conjugate_acid
43 | assert products[1] == conjugate_base
44 |
45 | # Determining the salt isn't implemented
46 | assert products[2] is None
47 |
--------------------------------------------------------------------------------
/tests/test_reactions_basic.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, division, unicode_literals, \
2 | absolute_import
3 |
4 | from nose.tools import with_setup
5 |
6 | from CAOS.dispatch import react, register_reaction_mechanism, \
7 | ReactionDispatcher
8 | from CAOS.util import raises
9 | from CAOS.exceptions.reaction_errors import FailedReactionError
10 |
11 |
12 | def requirement1(r, c):
13 | return True
14 |
15 |
16 | def requirement2(r, c):
17 | return False
18 |
19 |
20 | class TestGeneratePotentialMechanisms(object):
21 |
22 | def teardown(self):
23 | for key in ['a', 'b', 'c']:
24 | if key in ReactionDispatcher._test_namespace:
25 | del ReactionDispatcher._test_namespace[key]
26 |
27 | # Test some stupidly simple cases without any real requirements
28 | def test_find_options_all_valid(self):
29 |
30 | @register_reaction_mechanism([requirement1], True)
31 | def a(r, c):
32 | return ["Hello, world!"]
33 |
34 | @register_reaction_mechanism([requirement1], True)
35 | def b(r, c):
36 | return ["Hello, world!"]
37 |
38 | @register_reaction_mechanism([requirement1], True)
39 | def c(r, c):
40 | return ["Hello, world!"]
41 |
42 | potential_mechanisms = ReactionDispatcher._generate_likely_reactions(
43 | None, None, ReactionDispatcher._get_namespace(True)
44 | )
45 | assert all(function in potential_mechanisms for function in [a, b, c])
46 |
47 | def test_find_options_some_valid(self):
48 |
49 | @register_reaction_mechanism([requirement1], True)
50 | def a(r, c):
51 | return ["Hello, world!"]
52 |
53 | @register_reaction_mechanism([requirement2], True)
54 | def b(r, c):
55 | return ["Hello, world!"]
56 |
57 | @register_reaction_mechanism([requirement1], True)
58 | def c(r, c):
59 | return ["Hello, world!"]
60 |
61 | potential_mechanisms = ReactionDispatcher._generate_likely_reactions(
62 | None, None, ReactionDispatcher._get_namespace(True)
63 | )
64 | assert a in potential_mechanisms
65 | assert c in potential_mechanisms
66 | assert b not in potential_mechanisms
67 |
68 | def test_find_options_none_valid(self):
69 |
70 | @register_reaction_mechanism([requirement2], True)
71 | def a(r, c):
72 | return ["Hello, world!"]
73 |
74 | @register_reaction_mechanism([requirement2], True)
75 | def b(r, c):
76 | return ["Hello, world!"]
77 |
78 | @register_reaction_mechanism([requirement2], True)
79 | def c(r, c):
80 | return ["Hello, world!"]
81 |
82 | potential_mechanisms = ReactionDispatcher._generate_likely_reactions(
83 | None, None, ReactionDispatcher._get_namespace(True)
84 | )
85 |
86 | assert not any(function in potential_mechanisms for function in [a, b, c])
87 |
88 |
89 | class TestPerformReactions(object):
90 |
91 | def teardown(self):
92 | for key in ['a', 'b', 'c']:
93 | if key in ReactionDispatcher._test_namespace:
94 | del ReactionDispatcher._test_namespace[key]
95 |
96 | def test_single_option(self):
97 | @register_reaction_mechanism([requirement1], True)
98 | def a(r, c):
99 | return ["Hello, world!"]
100 |
101 | assert react(None, None, True) == ["Hello, world!"]
102 |
103 | def test_multiple_options(self):
104 | @register_reaction_mechanism([requirement1], True)
105 | def a(r, c):
106 | return ["a"]
107 |
108 | @register_reaction_mechanism([requirement1], True)
109 | def b(r, c):
110 | return ["b"]
111 |
112 | # The order is not guaranteed, but it should equal one of them.
113 | # This will be fixed once ordering is worked out.
114 | assert react(None, None, True) in (["a"], ["b"])
115 |
116 | def test_multiple_options_some_invalid(self):
117 | @register_reaction_mechanism([requirement1], True)
118 | def a(r, c):
119 | return ["a"]
120 |
121 | @register_reaction_mechanism([requirement1], True)
122 | def b(r, c):
123 | return ["b"]
124 |
125 | @register_reaction_mechanism([requirement2], True)
126 | def c(r, c_):
127 | return ["c"]
128 |
129 | assert react(None, None, True) in (["a"], ["b"])
130 |
131 | def test_no_options(self):
132 | function = react
133 | args = [None, None, True]
134 | exception_type = FailedReactionError
135 | assert raises(exception_type, function, args)
136 |
--------------------------------------------------------------------------------
/tests/test_structures.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, division, unicode_literals, \
2 | absolute_import
3 |
4 | from CAOS.structures.molecule import Molecule
5 | from CAOS.util import raises
6 |
7 |
8 | def test_from_default():
9 | a = Molecule(
10 | {'a1': 'H', 'a2': 'O'},
11 | {'b1': {'nodes': ('a1', 'a2')}}
12 | )
13 | b = Molecule.from_default(a)
14 | assert a == b
15 | assert a is b
16 |
17 |
18 | def test_to_default():
19 | a = Molecule(
20 | {'a1': 'H', 'a2': 'O'},
21 | {'b1': {'nodes': ('a1', 'a2')}}
22 | )
23 | b = a.to_default()
24 | assert a == b
25 | assert a is b
26 |
27 |
28 | def test_add_node():
29 | a = Molecule(
30 | {'a1': 'H', 'a2': 'O'},
31 | {'b1': {'nodes': ('a1', 'a2')}}
32 | )
33 | a._add_node('a3', 'H')
34 | assert 'a3' in a.atoms
35 | assert a.atoms['a3'] == 'H'
36 |
37 |
38 | def test_add_existing_node():
39 | a = Molecule(
40 | {'a1': 'H', 'a2': 'O'},
41 | {'b1': {'nodes': ('a1', 'a2')}}
42 | )
43 |
44 | args = ['a2', 'H']
45 | function = a._add_node
46 | exception_type = KeyError
47 | assert raises(exception_type, function, args)
48 |
49 |
50 | def test_add_edge():
51 | a = Molecule(
52 | {'a1': 'H', 'a2': 'O'},
53 | {'b1': {'nodes': ('a1', 'a2')}}
54 | )
55 | a._add_edge('b2', {'nodes': ('a1', 'a2')})
56 | assert 'b2' in a.bonds
57 | assert a.bonds['b2'] == {'nodes': ('a1', 'a2'), 'id': 'b2'}
58 |
59 |
60 | def test_add_existing_edge():
61 | a = Molecule(
62 | {'a1': 'H', 'a2': 'O'},
63 | {'b1': {'nodes': ('a1', 'a2')}}
64 | )
65 |
66 | args = ['b1', {'nodes': ('a1', 'a2')}]
67 | function = a._add_edge
68 | exception_type = KeyError
69 | assert raises(exception_type, function, args)
70 |
71 |
72 | def test_simple_equality():
73 | a = Molecule(
74 | {'a1': 'H', 'a2': 'O'},
75 | {'b1': {'nodes': ('a1', 'a2')}}
76 | )
77 | b = Molecule(
78 | {'a1': 'H', 'a2': 'O'},
79 | {'b1': {'nodes': ('a1', 'a2')}}
80 | )
81 |
82 | assert a == b
83 |
84 |
85 | def test_isomorphic_equality():
86 | a = Molecule(
87 | {'a1': 'H', 'a2': 'O'},
88 | {'b1': {'nodes': ('a1', 'a2')}}
89 | )
90 | b = Molecule(
91 | {'a2': 'H', 'a1': 'O'},
92 | {'b2': {'nodes': ('a1', 'a2')}}
93 | )
94 |
95 | assert a == b
96 |
97 |
98 | def test_inequality():
99 | a = Molecule(
100 | {'a1': 'H', 'a2': 'O'},
101 | {'b1': {'nodes': ('a1', 'a2')}}
102 | )
103 | b = Molecule(
104 | {'a1': 'N', 'a2': 'O'},
105 | {'b1': {'nodes': ('a1', 'a2')}}
106 | )
107 |
108 | assert a != b
109 |
110 |
111 | def test_stupid_repr_test():
112 | a = Molecule(
113 | {'a1': 'H', 'a2': 'O'},
114 | {'b1': {'nodes': ('a1', 'a2')}}
115 | )
116 |
117 | assert repr(a)
118 |
119 |
120 | def test_next_id_atom():
121 | a = Molecule(
122 | {'a1': 'H', 'a2': 'O'},
123 | {'b1': {'nodes': ('a1', 'a2')}}
124 | )
125 |
126 | assert a._next_free_atom_id == 'a0'
127 |
128 |
129 | def test_next_id_bond():
130 | a = Molecule(
131 | {'a1': 'H', 'a2': 'O'},
132 | {'b1': {'nodes': ('a1', 'a2')}}
133 | )
134 |
135 | assert a._next_free_bond_id == 'b0'
136 |
137 |
138 | def test_next_id_invalid():
139 | a = Molecule(
140 | {'a1': 'H', 'a2': 'O'},
141 | {'b1': {'nodes': ('a1', 'a2')}}
142 | )
143 |
144 | assert raises(ValueError, a._next_id, ('c',))
145 |
146 |
147 | def test_masking_kwarg():
148 | assert raises(ValueError, Molecule, (
149 | {'a1': 'H', 'a2': 'O'},
150 | {'b1': {'nodes': ('a1', 'a2')}}),
151 | {'node': 13}
152 | )
153 |
--------------------------------------------------------------------------------
/tests/test_util.py:
--------------------------------------------------------------------------------
1 | from __future__ import print_function, division, unicode_literals, \
2 | absolute_import
3 |
4 | from CAOS.util import raises
5 |
6 |
7 | def test_raises_false():
8 | function = int
9 | args = [3]
10 | exception_type = ValueError
11 | assert not raises(exception_type, function, args)
12 |
13 |
14 | def test_raises_true():
15 | function = int
16 | args = ["hello"]
17 | exception_type = ValueError
18 | assert raises(exception_type, function, args)
19 |
20 |
21 | def test_raises_error():
22 | function = int
23 | args = ["hello"]
24 | exception_type = UnboundLocalError
25 | assert raises(ValueError, raises, (exception_type, function, args))
26 |
--------------------------------------------------------------------------------