From 8bc15bad6c9f2121d41f92714223e3f91bef98a2 Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Wed, 8 Jan 2025 11:32:44 +0100 Subject: [PATCH 1/7] change name to CGsmiles --- docs/source/conf.py | 12 ++++++------ docs/source/gettingstarted/api_examples.rst | 10 +++++----- docs/source/gettingstarted/syntax_examples.rst | 14 +++++++------- docs/source/syntax/basic_graph_description.rst | 16 ++++++++-------- docs/source/syntax/chirality.rst | 10 +++++----- docs/source/syntax/fragments.rst | 8 ++++---- docs/source/syntax/introduction.rst | 6 +++--- docs/source/syntax/multiple_resolutions.rst | 12 ++++++------ 8 files changed, 44 insertions(+), 44 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index e95b313..dc1ec8a 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -25,7 +25,7 @@ # -- Project information ----------------------------------------------------- -project = 'CGSmiles' +project = 'CGsmiles' copyright = '2024, Dr. F Gruenewald' author = 'F. Gruneewald and P. C. Kroon' @@ -132,7 +132,7 @@ # -- Options for HTMLHelp output --------------------------------------------- # Output file base name for HTML help builder. -htmlhelp_basename = 'CGSmilesdoc' +htmlhelp_basename = 'CGsmilesdoc' # -- Options for LaTeX output ------------------------------------------------ @@ -159,7 +159,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'CGSmiles.tex', 'CGSmiles Documentation', + (master_doc, 'CGsmiles.tex', 'CGsmiles Documentation', author, 'manual'), ] @@ -169,7 +169,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ - (master_doc, 'cgsmiles', 'CGSmiles Documentation', + (master_doc, 'cgsmiles', 'CGsmiles Documentation', [author], 1) ] @@ -180,8 +180,8 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'CGSmiles', 'CGSmiles Documentation', - author, 'CGSmiles', 'One line description of project.', + (master_doc, 'CGsmiles', 'CGsmiles Documentation', + author, 'CGsmiles', 'One line description of project.', 'Miscellaneous'), ] diff --git a/docs/source/gettingstarted/api_examples.rst b/docs/source/gettingstarted/api_examples.rst index f3320c0..6a5de42 100644 --- a/docs/source/gettingstarted/api_examples.rst +++ b/docs/source/gettingstarted/api_examples.rst @@ -2,7 +2,7 @@ API Examples ============ The following tutorials illustrate how to use read, -draw, and manipulate CGSmiles using the package API. +draw, and manipulate CGsmiles using the package API. For more detailed information on the syntax please consult the examples and Syntax documentation. @@ -10,7 +10,7 @@ Read and draw CGSmile of Polystyrene ------------------------------------ If one just seeks to describe a graph at abitrary level of -complexity CGSmiles notation can be used. +complexity CGsmiles notation can be used. .. code:: python @@ -18,7 +18,7 @@ complexity CGSmiles notation can be used. import networkx as nx import cgsmiles - # Express 5 units of Polystyrene in CGSmiles + # Express 5 units of Polystyrene in CGsmiles cgsmiles_str = "{[#PS]|5}.{#PS=[$]CC[$](c1ccccc1)}" # Resolve molecule into networkx graphs @@ -48,7 +48,7 @@ BENZ.pdb from this repository. # Read pdb of Benzen mol = vermouth.pdb.read_pdb("BENZ.pdb") - # Express the mapping as CGSmiles string + # Express the mapping as CGsmiles string cgsmiles_str = "{[#R]1[#R][#R]1}.{#R=[$]cc[$]}" # Resolve molecule into networkx graphs @@ -73,4 +73,4 @@ Searching the Martini databse of small molecules ------------------------------------------------ Here goes some example on how to lookup molecules from the Martini -Database using CGSmiles +Database using CGsmiles diff --git a/docs/source/gettingstarted/syntax_examples.rst b/docs/source/gettingstarted/syntax_examples.rst index f07052c..c99c418 100644 --- a/docs/source/gettingstarted/syntax_examples.rst +++ b/docs/source/gettingstarted/syntax_examples.rst @@ -1,18 +1,18 @@ Syntax Examples =============== -This page collects examples of CGSmiles string of increasing +This page collects examples of CGsmiles string of increasing complexity. They are seperated into the following categories: -- CGSmiles without fragments -- CGSmiles with all-atom fragments -- CGSmiles with coarse-grained fragments +- CGsmiles without fragments +- CGsmiles with all-atom fragments +- CGsmiles with coarse-grained fragments -CGSmiles without fragments +CGsmiles without fragments -------------------------- If one just seeks to describe a graph at abitrary level of -complexity CGSmiles notation can be used. Each of the smiles +complexity CGsmiles notation can be used. Each of the smiles listed below can be read and converted using the `read_cgsmiles` function of the package: @@ -57,7 +57,7 @@ function of the package: "{[#nodeA]([#nodeAB][#nodeAB])|5}" -CGSmiles with all-atom fragments +CGsmiles with all-atom fragments -------------------------------- - simple linear graph describing PEO with two OH end-groups diff --git a/docs/source/syntax/basic_graph_description.rst b/docs/source/syntax/basic_graph_description.rst index 09bce26..cbe4200 100644 --- a/docs/source/syntax/basic_graph_description.rst +++ b/docs/source/syntax/basic_graph_description.rst @@ -3,11 +3,11 @@ General Graph Syntax Overview -------- -The first resolution of the CGSmiles notation captures the coarsest representation +The first resolution of the CGsmiles notation captures the coarsest representation of a molecule. The syntax is adapted from the SMILES notation and can be used to represent arbitrary graphs. These graphs do not need to be molecules but the syntax is geared towards molecules. The basic syntax features are sufficient to -write a CGSmiles string for any (connected) graph. The advanced syntax features +write a CGsmiles string for any (connected) graph. The advanced syntax features can be used to reduce the verbosity through use of a multiplication operator, allow annotation of bond orders, which are important for atomic resolutions and resolving multiple resolutions, as well as a general annotation syntax that @@ -15,7 +15,7 @@ permits writing of node labels. Basic Syntax Features ----------------------- -The basic structure of CGSmiles involves describing each node within a graph +The basic structure of CGsmiles involves describing each node within a graph using a specific notation that identifies connections and relationships between nodes. Here’s how the nodes and their connections are represented: @@ -63,7 +63,7 @@ connecting nodes A, B, and C would be written as ``[#A]1[#B][#C]1``. String Encapsulation ^^^^^^^^^^^^^^^^^^^^ -For clarity and to define boundaries, CGSmiles strings are enclosed in curly braces. +For clarity and to define boundaries, CGsmiles strings are enclosed in curly braces. .. code-block:: none @@ -117,7 +117,7 @@ Annotations ^^^^^^^^^^^ Some important information are are not encoded by the graph representation of a molecule. Such information are for examples charges or chirality. -CGSmiles supports a general annotation syntax, which allows users to store +CGsmiles supports a general annotation syntax, which allows users to store this kind of information in the form of ``symbol=value`` pairs. Any node name may be followed by one or more of these ``symbol=value`` pairs separated by a semi-colon. For example, to specify that node a has a charge of 1 but node @@ -151,7 +151,7 @@ symbols is named a `dialect` and can be specified using the functionality in the dialect module. Note that currently dialects are not easily accessible for modification. -CGSmiles comes with two sets of predefined dialects. One is used for the coarse +CGsmiles comes with two sets of predefined dialects. One is used for the coarse resolution fragments / graphs and the other for those which are of atomic resolution. The table below lists the specifications of those keywords. Note that it is always permissible to use the keyword explicitly. @@ -175,7 +175,7 @@ Reserved Annotation Symbols Multiplication Operator ^^^^^^^^^^^^^^^^^^^^^^^ To efficiently represent repeated units in large molecules, such as polymers, -CGSmiles syntax includes a multiplication operator ``|``. This operator can be +CGsmiles syntax includes a multiplication operator ``|``. This operator can be applied after a node or a branch to repeat it a specified number of times. - **Node Multiplication:** The multiplication operator is placed after a node @@ -200,7 +200,7 @@ applied after a node or a branch to repeat it a specified number of times. Syntax Features Lookup Table ---------------------------- Below is the updated quick reference table for the essential features of -CGSmiles syntax: +CGsmiles syntax: +----------------+----------------------------------------------+------------------------------------------------+ | Feature | Description | Example | diff --git a/docs/source/syntax/chirality.rst b/docs/source/syntax/chirality.rst index 80f4c80..798f6cd 100644 --- a/docs/source/syntax/chirality.rst +++ b/docs/source/syntax/chirality.rst @@ -6,7 +6,7 @@ features have no direct counterparts in CG models and require special treatment. Implicit Hydrogen ^^^^^^^^^^^^^^^^^ The simplest case is the treatment of implicit hydrogen atoms. SMILES allows for -shorthand notation where hydrogen atoms can be omitted and CGSmiles adopts this +shorthand notation where hydrogen atoms can be omitted and CGsmiles adopts this approach. Hydrogen atoms are automatically assigned once the full atomistic molecule is resolved. This procedure ensures proper handling of any unconsumed bonding operators, which are interpreted as additional hydrogen atoms where @@ -25,7 +25,7 @@ constructing the complete SMILES string. Chirality ^^^^^^^^^ -CGSmiles adopts an explicit method of chirality assignment using annotations. A +CGsmiles adopts an explicit method of chirality assignment using annotations. A chiral atom can be annotated using the ``x`` keyword as shorthand for chirality. For example, S-Alanine is represented as ``C[C;x=S]C(=O)ON``, while R-Alanine is written as ``C[C;x=R]C(=O)ON``. The ``x`` may be omitted if a weight is defined @@ -35,7 +35,7 @@ general annotation syntax for more information. Aromaticity ^^^^^^^^^^^ In SMILES, aromaticity is encoded using lowercase letters as a shorthand for -aromatic atoms or a colon as a marker for aromatic bonds. CGSmiles utilizes the +aromatic atoms or a colon as a marker for aromatic bonds. CGsmiles utilizes the same convention. In addition, aromatic systems may also be split across multiple fragments by simply keeping the shorthand. For example, Martini Benzene is represented as: @@ -46,13 +46,13 @@ represented as: Although the shorthand for aromaticity is well-defined, its interpretation in SMILES remains somewhat ambiguous. To ensure unambiguous valance assignment, -necessary for tasks like adding hydrogen atoms, CGSmiles employs the following +necessary for tasks like adding hydrogen atoms, CGsmiles employs the following definition: only atoms capable of participating in delocalization-induced molecular equivalence (i.e., systems where multiple resonance structures can be drawn without introducing charges) are considered aromatic. By this definition Benzene is aromatic but thiophene is not. CGsmiles uses the same definition as Pysmiles package, which provides a more detailed discussion of this topic. To -enhance user-friendliness, the CGSmiles API automatically corrects strings with +enhance user-friendliness, the CGsmiles API automatically corrects strings with incorrectly assigned aromaticity at the time of reading. If corrections cannot be made unambiguously, an error is raised, ensuring robust and accurate handling of aromaticity. diff --git a/docs/source/syntax/fragments.rst b/docs/source/syntax/fragments.rst index 48d7692..310e8fa 100644 --- a/docs/source/syntax/fragments.rst +++ b/docs/source/syntax/fragments.rst @@ -3,7 +3,7 @@ General Fragment Syntax Overview -------- -CGSmiles supports the representation of molecular structures at different +CGsmiles supports the representation of molecular structures at different resolutions through a fragment replacement syntax. This allows users to specify more detailed molecular structures connected to a coarse graph representation. @@ -23,7 +23,7 @@ syntax to define an atomic resolution fragment. Bond Operators ^^^^^^^^^^^^^^ To define how two consecutive fragments at a finer resolution are connected, -CGSmiles builds upon the bonding connector syntax established in BigSMILES to +CGsmiles builds upon the bonding connector syntax established in BigSMILES to avoid ambiguity. Any node or atom that connects to a neighboring fragment is followed by one of four bonding connectors (‘$’, ‘>’, ‘<’, ‘!’) enclosed in square brackets. In addition, any operator may be combined with an alphanumeric @@ -52,7 +52,7 @@ label to distinguish non-equivalent operators of the same type. - **Shared Bonding Operator !** To address a common scenario in CG force fields where an atom is distributed - between two finer resolution nodes, CGSmiles introduces the shared bonding + between two finer resolution nodes, CGsmiles introduces the shared bonding operator ‘!’. In the case of toluene represented at the Martini 3 level, some of the ring atoms are shared between the two CG beads. When two fragments are connected using the shared bonding operator, the atoms at the connection point @@ -78,7 +78,7 @@ double bond between ethane and propane fragment. Updated Bonding Descriptors Lookup Table ---------------------------------------- -This table now includes the squash descriptor, summarizing all the bonding descriptors used in CGSmiles: +This table now includes the squash descriptor, summarizing all the bonding descriptors used in CGsmiles: +----------------+---------------------------+--------------------------------------------------------------------+ | Descriptor | Symbol | Description | diff --git a/docs/source/syntax/introduction.rst b/docs/source/syntax/introduction.rst index e17881c..1e13b5c 100644 --- a/docs/source/syntax/introduction.rst +++ b/docs/source/syntax/introduction.rst @@ -1,7 +1,7 @@ Introduction ============ -The CGSmiles line notation encodes arbitrary resolutions of molecules and +The CGsmiles line notation encodes arbitrary resolutions of molecules and defines the conversion between these resolutions unambiguously. Each resolution is explicitly defined and multiple resolutions may be layered together using this notation. @@ -12,7 +12,7 @@ polymer, which represent a coarser resolution compared to the next (all-atom) representation. Edges in the graph describe chemical connections between these (groups of) atoms. -With this premise, the first resolution of the CGSmiles notation describes +With this premise, the first resolution of the CGsmiles notation describes the molecule graph at the coarsest level. Subsequent resolutions define fragments that specify how each node is represented at the next finer resolution (e.g. residue to coarse-grained beads, or coarse-grained beads @@ -25,6 +25,6 @@ below: In the remainder of this section we first explain the syntax to describe a general graph, which can represent a molecule at any resolution in -CGSmiles. Subsequently, the description is extended to define fragments. +CGsmiles. Subsequently, the description is extended to define fragments. Finally, it is show how to deal with special issues that can arise when converting a coarse resolution graph to atomic representation. diff --git a/docs/source/syntax/multiple_resolutions.rst b/docs/source/syntax/multiple_resolutions.rst index c879c5e..bea6c1b 100644 --- a/docs/source/syntax/multiple_resolutions.rst +++ b/docs/source/syntax/multiple_resolutions.rst @@ -1,7 +1,7 @@ Layering of Resolutions ======================= -CGSmiles enables the representation of molecular graphs at arbitrary resolutions +CGsmiles enables the representation of molecular graphs at arbitrary resolutions and their connection to progressively finer resolutions, allowing for the hierarchical layering of multiple levels of details. @@ -14,7 +14,7 @@ The notation starts with the coarsest representation of the system – the base graph. This graph is enclosed in curly braces. Each additional resolution is represented as a list of fragment graphs, also enclosed in curly braces and separated from the preceding resolution graph by a period. If the final resolution -graph is at the atomic level, either CGSmiles or OpenSMILES syntax can be used +graph is at the atomic level, either CGsmiles or OpenSMILES syntax can be used to describe the fragment graph. This dual approach allows seamless conversion to atomistic resolution using established standards, while also supporting intermediate coarse-grained representations. @@ -30,7 +30,7 @@ Linearizing Rings ^^^^^^^^^^^^^^^^^ Rings at the atomistic resolution can often be mapped into linear structures at the CG level, a common practice in chemically specific force fields such -as Martini. In the CGSmiles notation, bond orders at the coarser resolution are +as Martini. In the CGsmiles notation, bond orders at the coarser resolution are utilized to describe such a case. For example, cyclohexane is represented at the Martini 3 level with a bond @@ -61,7 +61,7 @@ correspond to any finer-resolution nodes or atoms. For example, at the Martini 3 resolution glucose is represented by three CG particles splitting the sugar ring and one additional virtual particle. The TC4 bead captures the hydrophobic interactions at the ring center but lacks any corresponding fragments at finer -resolution. To accommodate such particles, the CGSmiles notation employs zero +resolution. To accommodate such particles, the CGsmiles notation employs zero bond order edges, referred to as virtual edges. .. code-block:: none @@ -83,9 +83,9 @@ the fine-grained resolution because of a loss in resolution at the CG level. An example are Martini lipids such as POPC. POPC can describe lipids with a tail length of 16 or 18 carbons and thus represents at least four molecules when accounting for the position for the double bond. To capture this feature -CGSmiles allows to overload the wildcard (*) syntax using annotations. In +CGsmiles allows to overload the wildcard (*) syntax using annotations. In OpenSMILES a wildcard means any atom can be placed at the wildcard position. -To specify a selection of atoms CGSmiles allows to annotate a wildcard using the +To specify a selection of atoms CGsmiles allows to annotate a wildcard using the select keyword abbreviated as ‘s’. Thus, a tail bead in POPC could be written as ``C1=CCCC[*;s=C,0][*;s=C,0]``. Note that the current molecule resolver is not able to handle wildcard overloading. From 28e09d3ca3cd0d057fa36fb7ce03cfc1bd555730 Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Wed, 8 Jan 2025 11:35:12 +0100 Subject: [PATCH 2/7] change name to CGsmiles --- cgsmiles/dialects.py | 2 +- cgsmiles/pysmiles_utils.py | 4 ++-- cgsmiles/read_fragments.py | 14 +++++++------- cgsmiles/resolve.py | 32 ++++++++++++++++---------------- cgsmiles/sample.py | 6 +++--- cgsmiles/write_cgsmiles.py | 16 ++++++++-------- docs/source/api/overview.rst | 12 ++++++------ 7 files changed, 43 insertions(+), 43 deletions(-) diff --git a/cgsmiles/dialects.py b/cgsmiles/dialects.py index 9ba9eab..2dfc089 100644 --- a/cgsmiles/dialects.py +++ b/cgsmiles/dialects.py @@ -139,7 +139,7 @@ def create_dialect(default_attributes, # KNOWN DIALECTS # ########################################################## # this one is for global use -# it is the base CGSmiles dialect +# it is the base CGsmiles dialect CGSMILES_DEFAULT_DIALECT = create_dialect({"fragname": (None, str), "q": (0.0, float), "w": (1.0, float)}) diff --git a/cgsmiles/pysmiles_utils.py b/cgsmiles/pysmiles_utils.py index 4062c9e..d8cef62 100644 --- a/cgsmiles/pysmiles_utils.py +++ b/cgsmiles/pysmiles_utils.py @@ -77,7 +77,7 @@ def rebuild_h_atoms(mol_graph, "show delocalization-induced molecular equivalency and thus " "is not considered aromatic. For example, 4-methyl imidazole " "is often written as [nH]1cc(nc1)C, but should be written as " - "[NH]1C=C(N=C1)C. A corresponding CGSmiles string would be " + "[NH]1C=C(N=C1)C. A corresponding CGsmiles string would be " "{[#A]1[#B][#C]1}.{#A=[>][<]N,#B=[$]N=C[>],#C=[$]C(C)=C[<]}") raise SyntaxError(msg) nx.set_node_attributes(mol_graph, 0, 'hcount') @@ -126,7 +126,7 @@ def read_fragment_smiles(smiles_str, ez_isomers={}, attributes={}): """ - Read a smiles_str corresponding to a CGSmiles fragment and + Read a smiles_str corresponding to a CGsmiles fragment and annotate bonding descriptors, isomers, as well as any other attributes. diff --git a/cgsmiles/read_fragments.py b/cgsmiles/read_fragments.py index 59d7328..f91a82d 100644 --- a/cgsmiles/read_fragments.py +++ b/cgsmiles/read_fragments.py @@ -104,21 +104,21 @@ def collect_ring_number(smile_iter, token, node_count, rings): def strip_bonding_descriptors(fragment_string): """ - Processes a CGSmiles fragment string by + Processes a CGsmiles fragment string by stripping the bonding descriptors and storing them in a dict with reference to the atom they - refer to. Furthermore, a cleaned SMILES or CGSmiles + refer to. Furthermore, a cleaned SMILES or CGsmiles string is returned. Parameters ---------- fragment_string: str - a CGSmiles fragment string + a CGsmiles fragment string Returns ------- str: - a canonical SMILES or CGSmiles string + a canonical SMILES or CGsmiles string dict: a dict mapping bonding descriptors to the nodes within the string @@ -255,19 +255,19 @@ def fragment_iter(fragment_str, all_atom=True): def read_fragments(fragment_str, all_atom=True, fragment_dict=None): """ - Collects the fragments defined in a CGSmiles fragment string + Collects the fragments defined in a CGsmiles fragment string as networkx.Graph and returns a dict of them. Bonding descriptors are annotated as node attribtues. Parameters ---------- fragment_str: str - string using CGSmiles fragment syntax + string using CGsmiles fragment syntax all_atom: bool If the fragment strings are all-atom following the OpenSmiles syntax. Default is True but if - set to False fragments follow the CGSmiles + set to False fragments follow the CGsmiles syntax. fragment_dict: dict diff --git a/cgsmiles/resolve.py b/cgsmiles/resolve.py index cb592d0..9a6c29e 100644 --- a/cgsmiles/resolve.py +++ b/cgsmiles/resolve.py @@ -14,7 +14,7 @@ def compatible(left, right, legacy=False): """ Check bonding descriptor compatibility according - to the CGSmiles syntax conventions. With legacy + to the CGsmiles syntax conventions. With legacy the BigSmiles convention can be used. Parameters @@ -87,27 +87,27 @@ def match_bonding_descriptors(source, target, bond_attribute="bonding", legacy=F class MoleculeResolver: """ - Resolve the molecule(s) described by a CGSmiles string and return a networkx.Graph + Resolve the molecule(s) described by a CGsmiles string and return a networkx.Graph of the molecule. First, this class has to be initiated using one of three class construction - methods. When trying to read a CGSmiles string always use the first method. + methods. When trying to read a CGsmiles string always use the first method. The other constructors can be used in case fragments or the lowest resolution molecule are defined by graphs that come from elsewhere. `self.from_string`: use when fragments and lowest resolution are - described in one CGSmiles string. - `self.from_graph`: use when fragments are described by CGSmiles + described in one CGsmiles string. + `self.from_graph`: use when fragments are described by CGsmiles strings but the lowest resolution is given as nx.Graph `self.from_fragment_dicts`: use when fragments are given as nx.Graphs and the lowest resolution is provided as - CGSmiles string + CGsmiles string Once the `MoleculeResolver` is initiated you can call the `resolve_iter` to loop over the different levels of resolution. The resolve iter will always return the previous lower resolution graph as well as the current higher - resolution graph. For example, if the CGSmiles string describes a monomer + resolution graph. For example, if the CGsmiles string describes a monomer sequence of a regular polymer, the lower resolution graph will be the graph of this monomer sequence and the higher resolution graph the full molecule. @@ -137,7 +137,7 @@ class MoleculeResolver: >>> resolver = MoleculeResolver.from_graph(cgsmiles_str, block_graph) Finally, there is the option of having the fragments from elsewhere for - example a library. Then only the graph defined as CGSmiles string. In this + example a library. Then only the graph defined as CGsmiles string. In this case the `from_fragment_dicts` method can be used. Please note that the fragment graphs need to have the following attributes as a graph returned by the `cgsmiles.read_fragments` function. @@ -178,11 +178,11 @@ def __init__(self, legacy: bool which syntax convention to use for matching the bonding descriptors. Legacy syntax adheres to the BigSmiles convention. Default syntax - adheres to CGSmiles convention where bonding descriptors '$' match + adheres to CGsmiles convention where bonding descriptors '$' match with every '$' and every '<' matches every '>'. With the BigSmiles convention a alphanumeric string may be provided that distinguishes these connectors. For example, '$A' would not match '$B'. However, - such use cases should be rare and the CGSmiles convention facilitates + such use cases should be rare and the CGsmiles convention facilitates usage of bonding descriptors in the Sampler where the labels are used to assign different probabilities. """ @@ -199,7 +199,7 @@ def __init__(self, @staticmethod def read_fragment_strings(fragment_strings, last_all_atom=True): """ - Read a list of CGSmiles fragment_strings and return a list + Read a list of CGsmiles fragment_strings and return a list of dicts with the fragment graphs. If `last_all_atom` is True then pysmiles is used to read the last fragment string provided in the list. @@ -207,7 +207,7 @@ def read_fragment_strings(fragment_strings, last_all_atom=True): Parameters ---------- fragment_strings: list[str] - list of CGSmiles fragment strings + list of CGsmiles fragment strings last_all_atom: bool if the last string in the list is an all atom string and should be read using pysmiles. @@ -348,7 +348,7 @@ def squash_atoms(self): def resolve(self): """ - Resolve a CGSmiles string once and return the next resolution. + Resolve a CGsmiles string once and return the next resolution. """ # check if this is an all-atom level resolution all_atom = (self.resolution_counter == self.resolutions - 1 and self.last_all_atom) @@ -429,7 +429,7 @@ def from_string(cls, cgsmiles_str, last_all_atom=True, legacy=False): legacy: bool which syntax convention to use for matching the bonding descriptors. Legacy syntax adheres to the BigSmiles convention. Default syntax - adheres to CGSmiles convention. A more detailed explanation can be + adheres to CGsmiles convention. A more detailed explanation can be found in the MoleculeResolver.__init__ method. Returns @@ -466,7 +466,7 @@ def from_graph(cls, cgsmiles_str, meta_graph, last_all_atom=True, legacy=False): legacy: bool which syntax convention to use for matching the bonding descriptors. Legacy syntax adheres to the BigSmiles convention. Default syntax - adheres to CGSmiles convention. A more detailed explanation can be + adheres to CGsmiles convention. A more detailed explanation can be found in the MoleculeResolver.__init__ method. Returns @@ -507,7 +507,7 @@ def from_fragment_dicts(cls, cgsmiles_str, fragment_dicts, last_all_atom=True, l legacy: bool which syntax convention to use for matching the bonding descriptors. Legacy syntax adheres to the BigSmiles convention. Default syntax - adheres to CGSmiles convention. A more detailed explanation can be + adheres to CGsmiles convention. A more detailed explanation can be found in the MoleculeResolver.__init__ method. Returns diff --git a/cgsmiles/sample.py b/cgsmiles/sample.py index adf4436..041e0d2 100644 --- a/cgsmiles/sample.py +++ b/cgsmiles/sample.py @@ -42,12 +42,12 @@ def _set_bond_order_defaults(bonding): class MoleculeSampler: """ - Given a fragment string in CGSmiles format and probabilities for residues + Given a fragment string in CGsmiles format and probabilities for residues to occur, return a random molecule with target molecular weight. First, this class has to be initiated using the class construction method `from_string`, which makes sure to read and resolve the fragment - graphs provided in the CGSmiles string. + graphs provided in the CGsmiles string. Once the `MoleculeSampler` is initiated you can call the `sampler` method in order to generate a new random polymer molecule from the fragment string @@ -124,7 +124,7 @@ class MoleculeSampler: can be provided. For example, To generate a bottle brush polymer that has PMA in the backbone - and PEG as side-chain terminated with an OH group the following CGSmiles string + and PEG as side-chain terminated with an OH group the following CGsmiles string in combination with the above mentioned probabilities can be provided. Note that in this case we declare '$A' and '$B' to be terminal bonding diff --git a/cgsmiles/write_cgsmiles.py b/cgsmiles/write_cgsmiles.py index 1b871a3..5ebdcb5 100644 --- a/cgsmiles/write_cgsmiles.py +++ b/cgsmiles/write_cgsmiles.py @@ -11,7 +11,7 @@ def format_node(molecule, current): """ Format a node from a `molecule` graph according to - the CGSmiles syntax. The attribute fragname has to + the CGsmiles syntax. The attribute fragname has to be set for the `current` node. Parameters @@ -68,7 +68,7 @@ def write_graph(molecule, smiles_format=False, default_element='*'): Returns ------- str - The CGSmiles string describing `molecule`. + The CGsmiles string describing `molecule`. """ start = min(molecule) dfs_successors = nx.dfs_successors(molecule, source=start) @@ -161,19 +161,19 @@ def write_graph(molecule, smiles_format=False, default_element='*'): def write_cgsmiles_graph(molecule): """ - Write a CGSmiles graph sans fragments at + Write a CGsmiles graph sans fragments at different resolution. Parameters ---------- molecule: networkx.Graph a molecule where each node as a fragname attribute - that is used as name in the CGSmiles string. + that is used as name in the CGsmiles string. Returns ------- str - the CGSmiles string + the CGsmiles string """ cgsmiles_str = write_graph(molecule) @@ -192,7 +192,7 @@ def write_cgsmiles_fragments(fragment_dict, smiles_format=True): a dict of fragment graphs smiles_format: bool write all atom SMILES if True (default) otherwise - write CGSmiles + write CGsmiles Returns ------- @@ -208,7 +208,7 @@ def write_cgsmiles_fragments(fragment_dict, smiles_format=True): def write_cgsmiles(molecule_graph, fragments, last_all_atom=True): """ - Write a CGSmiles string given a low resolution molecule graph + Write a CGsmiles string given a low resolution molecule graph and any number of higher resolutions provided as fragment dicts. Parameters @@ -222,7 +222,7 @@ def write_cgsmiles(molecule_graph, fragments, last_all_atom=True): Returns ------- str - CGSmiles string + CGsmiles string """ final_str = write_cgsmiles_graph(molecule_graph) for layer, fragment in enumerate(fragments): diff --git a/docs/source/api/overview.rst b/docs/source/api/overview.rst index 5744b21..da462ec 100644 --- a/docs/source/api/overview.rst +++ b/docs/source/api/overview.rst @@ -1,13 +1,13 @@ Overview ======== -The API is designed to read, write, and interpret CGSmiles string. +The API is designed to read, write, and interpret CGsmiles string. Detailed information can be found in the module documentation. This overview page provides some quick tutorial style explanation of the main functionalities. -Reading CGSmiles +Reading CGsmiles ---------------- -A CGSmiles string can contain a base-graph (see Syntax Rules) and +A CGsmiles string can contain a base-graph (see Syntax Rules) and multiple enumerations of fragment graphs each corresonding to a different resolution. The base graph can be read using the ``read_cgsmiles`` function, while the fragments can be read using @@ -44,7 +44,7 @@ take the molecule in Figure 3 of the main paper: .. code-block:: python from cgsmiles import MoleculeResolver - # CGSmiles string with 3 resolutions + # CGsmiles string with 3 resolutions cgsmiles_str = "{[#hphilic][#hdphob]|3[#hphilic]}.\ {#hphilic=[<][#PEO][>]|3,#hdphob=[<][#PMA][>]([#BUT])}.\ {#PEO=[<][#SN3r][>],#PMA=[<][#TC3][>][#SN4a][$],#BUT=[$][#SC3][$]}.\ @@ -60,9 +60,9 @@ take the molecule in Figure 3 of the main paper: Alternatively, we could just have gotten the final two pairs by calling ``.resolve_all()``. -Drawing CGSmiles +Drawing CGsmiles ---------------- -It is very easy to check the correctness of a CGSmiles string by +It is very easy to check the correctness of a CGsmiles string by simply drawing the molecule and the mapping to the coarser level. Drawing molecules can be accomplished using the drawing module. From 14fa86255b1fc7144866dcd796d8d433b71e34dd Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Wed, 8 Jan 2025 15:52:26 +0100 Subject: [PATCH 3/7] update docs --- README.rst | 31 +++++++++++++-------- docs/source/api/overview.rst | 26 ++++++++--------- docs/source/gettingstarted/installation.rst | 3 ++ 3 files changed, 35 insertions(+), 25 deletions(-) diff --git a/README.rst b/README.rst index fbabf26..52b899e 100644 --- a/README.rst +++ b/README.rst @@ -42,6 +42,25 @@ the different use cases can be found in this documentation. If you are here from one of the packages using CGSmiles check out the GettingStarted section to learn the syntax. +Installation +============ + +The easiest ways to install **cgsmiles** is using pip: + +.. code:: bash + + pip install git+https://github.com/gruenewald-lab/CGsmiles.git + +In the future we will also distribute it through the Pypi +package index but that is currently not supported. Note that the drawing module +depends on the `scipy `__ and `matplotlib `__ +packages. These need to be installed before the module can be used. + +.. code:: bash + + pip install scipy + pip install matplotlib + Examples ======== @@ -65,18 +84,6 @@ Martini 3 Benzene # Draw molecule at different resolutions ax, pos = draw_molecule(mol_graph) -Installation -============ - -The easiest ways to install **cgsmiles** is using pip: - -.. code:: bash - - pip install git+https://github.com/gruenewald-lab/CGsmiles.git - -In the future we will also distribute it through the Pypi -package index but that is currently not supported. - Related Tools ============= diff --git a/docs/source/api/overview.rst b/docs/source/api/overview.rst index da462ec..3c56484 100644 --- a/docs/source/api/overview.rst +++ b/docs/source/api/overview.rst @@ -1,23 +1,23 @@ Overview ======== -The API is designed to read, write, and interpret CGsmiles string. +The API is designed to read, write, and interpret CGsmiles strings. Detailed information can be found in the module documentation. -This overview page provides some quick tutorial style explanation -of the main functionalities. +This page provides some quick tutorial style explanation of the +main functionalities. Reading CGsmiles ---------------- A CGsmiles string can contain a base-graph (see Syntax Rules) and -multiple enumerations of fragment graphs each corresonding to a +multiple enumerations of fragment graphs each corresponding to a different resolution. The base graph can be read using the ``read_cgsmiles`` function, while the fragments can be read using the ``read_fragments`` function. However, most user will find it -convienient to directly read the entire string and resolve the +convenient to directly read the entire string and resolve the different resolutions. This is done using the ``MoleculeResolver`` class. -First we need to import the ``MoleculeResolver`` and initate it -using the ``from_string`` or one of the other initator methods. +First we need to import the ``MoleculeResolver`` and initiate it +using the ``from_string`` or one of the other initiator methods. Note that we can specify if the last resolution is at the atomic level by providing ``last_all_atom=True`` argument. @@ -121,10 +121,10 @@ illustrates this for poly(ethylene) glycol. ax, pos = draw_molecule(mol_graph, labels=labels, scale=1) ax.set_frame_on('True') -Likley you will see that not the entire molecule fits in the bounding box as +Likely, you will see that not the entire molecule fits in the bounding box as indicated by the frame. The reason is that the drawing function does not automatically scale the image. You have two choices now. You can use the scale -keywod to shrink the molecule image until it fits (e.g. ``scale=0.5``) or you +keyword to shrink the molecule image until it fits (e.g. ``scale=0.5``) or you can provide a larger canvas. .. code:: python @@ -142,8 +142,8 @@ terms of labels, bonds, and atoms. Thus you only have to find a visually pleasin canvas size once and can draw a large collection of molecules. One added bonus feature of the drawing utility is that it will draw cis/trans -isomers correctly accoding to the cgsmiles string the user has provided. You -can see a simple exmaple below. +isomers correctly according to the cgsmiles string the user has provided. You +can see a simple example below. .. code:: python @@ -154,10 +154,10 @@ can see a simple exmaple below. fig, axes = plt.subplots(1,2, figsize=(6, 6)) # trans butene - cgsmiles_str_tans = "{[#A][#B]}.{#A=c\c[$],#B=[$]c\c}" + cgsmiles_str_tans = "{[#A][#B]}.{#A=C\C=[$],#B=[$]=C\C}" # cis butene - cgsmiles_str_cis = "{[#A][#B]}.{#A=c\c[$],#B=[$]c/c}" + cgsmiles_str_cis = "{[#A][#B]}.{#A=C\C=[$],#B=[$]=C/C}" # Resolve molecule into networkx graphs for ax, cgstr in zip(axes, [cgsmiles_str_tans, cgsmiles_str_cis]): diff --git a/docs/source/gettingstarted/installation.rst b/docs/source/gettingstarted/installation.rst index 88db138..ac75d0f 100644 --- a/docs/source/gettingstarted/installation.rst +++ b/docs/source/gettingstarted/installation.rst @@ -9,3 +9,6 @@ The easiest ways to install **cgsmiles** is using pip: In the future we will also distribute it through the Pypi package index but that is currently not supported. + +Note that the drawing module depends on the `scipy `__ and `matplotlib `__ +packages. These need to be installed before the module can be used. From 1bbaed1677b9c384d9b4dba5f978da1eb934b9c2 Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Wed, 8 Jan 2025 16:40:38 +0100 Subject: [PATCH 4/7] have tutorials --- .../tutorials/drawing.rst} | 69 ++------------- .../source/gettingstarted/tutorials/index.rst | 11 +++ .../mapping.rst} | 40 --------- .../gettingstarted/tutorials/martini.rst | 5 ++ .../gettingstarted/tutorials/polymers.rst | 87 +++++++++++++++++++ .../gettingstarted/tutorials/resolving.rst | 55 ++++++++++++ docs/source/index.rst | 3 +- 7 files changed, 164 insertions(+), 106 deletions(-) rename docs/source/{api/overview.rst => gettingstarted/tutorials/drawing.rst} (61%) create mode 100644 docs/source/gettingstarted/tutorials/index.rst rename docs/source/gettingstarted/{api_examples.rst => tutorials/mapping.rst} (52%) create mode 100644 docs/source/gettingstarted/tutorials/martini.rst create mode 100644 docs/source/gettingstarted/tutorials/polymers.rst create mode 100644 docs/source/gettingstarted/tutorials/resolving.rst diff --git a/docs/source/api/overview.rst b/docs/source/gettingstarted/tutorials/drawing.rst similarity index 61% rename from docs/source/api/overview.rst rename to docs/source/gettingstarted/tutorials/drawing.rst index 3c56484..41b1261 100644 --- a/docs/source/api/overview.rst +++ b/docs/source/gettingstarted/tutorials/drawing.rst @@ -1,67 +1,8 @@ -Overview -======== -The API is designed to read, write, and interpret CGsmiles strings. -Detailed information can be found in the module documentation. -This page provides some quick tutorial style explanation of the -main functionalities. - -Reading CGsmiles ----------------- -A CGsmiles string can contain a base-graph (see Syntax Rules) and -multiple enumerations of fragment graphs each corresponding to a -different resolution. The base graph can be read using the -``read_cgsmiles`` function, while the fragments can be read using -the ``read_fragments`` function. However, most user will find it -convenient to directly read the entire string and resolve the -different resolutions. This is done using the ``MoleculeResolver`` -class. - -First we need to import the ``MoleculeResolver`` and initiate it -using the ``from_string`` or one of the other initiator methods. -Note that we can specify if the last resolution is at the atomic -level by providing ``last_all_atom=True`` argument. - -.. code-block:: python - - from cgsmiles import MoleculeResolver - cgsmiles_string = '{[#TC5]1[#TC5][#TC5]1}.{#TC5=[$]cc[$]}' - resolver = MoleculeResolver.from_string(cgsmiles_string, - last_all_atom=True) - -Next we can resolve the atomic resolution from the CG graph by -running the ``.resolve`` function once. - -.. code-block:: python - - cg_graph, aa_graph = resolver.resolve() - -For multiple resolutions we can run the ``resolver`` function -multiple times. Each time a new set of graphs at a coarse level -and the next finer level is returned. Alternatively, the -``resolve_iter`` can be used to loop over all resolutions. Let's -take the molecule in Figure 3 of the main paper: - -.. code-block:: python - - from cgsmiles import MoleculeResolver - # CGsmiles string with 3 resolutions - cgsmiles_str = "{[#hphilic][#hdphob]|3[#hphilic]}.\ - {#hphilic=[<][#PEO][>]|3,#hdphob=[<][#PMA][>]([#BUT])}.\ - {#PEO=[<][#SN3r][>],#PMA=[<][#TC3][>][#SN4a][$],#BUT=[$][#SC3][$]}.\ - {#SN3r=[<]COC[>],#TC3=[<]CC[>][$1],#SN4a=[$1]C(=O)OC[$2],#SC3=[$2]CCC}" - # Generate the MoleculeResolver - resolver = MoleculeResolver.from_string(cgsmiles_str, last_all_atom=True) - - # Now we can loop over all resolutions using - for coarse_graph, finer_graph in resolver.resolve_iter(): - print(coarse_graph.nodes(data='fragname')) - print(finer_graph.nodes(data='atomname')) - -Alternatively, we could just have gotten the final two pairs by calling -``.resolve_all()``. - -Drawing CGsmiles ----------------- +Drawing +======= +Note that this tutorial requires the scipy and matplotlib packages +to be installed. See installation instructions. + It is very easy to check the correctness of a CGsmiles string by simply drawing the molecule and the mapping to the coarser level. Drawing molecules can be accomplished using the drawing module. diff --git a/docs/source/gettingstarted/tutorials/index.rst b/docs/source/gettingstarted/tutorials/index.rst new file mode 100644 index 0000000..6ce6ea3 --- /dev/null +++ b/docs/source/gettingstarted/tutorials/index.rst @@ -0,0 +1,11 @@ +Tutorials +========= + +.. toctree:: + :maxdepth: 2 + + resolving.rst + drawing.rst + martini.rst + polymers.rst + mapping.rst diff --git a/docs/source/gettingstarted/api_examples.rst b/docs/source/gettingstarted/tutorials/mapping.rst similarity index 52% rename from docs/source/gettingstarted/api_examples.rst rename to docs/source/gettingstarted/tutorials/mapping.rst index 6a5de42..1e4fd63 100644 --- a/docs/source/gettingstarted/api_examples.rst +++ b/docs/source/gettingstarted/tutorials/mapping.rst @@ -1,37 +1,3 @@ -API Examples -============ - -The following tutorials illustrate how to use read, -draw, and manipulate CGsmiles using the package API. -For more detailed information on the syntax please -consult the examples and Syntax documentation. - -Read and draw CGSmile of Polystyrene ------------------------------------- - -If one just seeks to describe a graph at abitrary level of -complexity CGsmiles notation can be used. - -.. code:: python - - import matplotlib.pyplot as plt - import networkx as nx - import cgsmiles - - # Express 5 units of Polystyrene in CGsmiles - cgsmiles_str = "{[#PS]|5}.{#PS=[$]CC[$](c1ccccc1)}" - - # Resolve molecule into networkx graphs - res_graph, mol_graph = cgsmiles.MoleculeResolver(cgsmiles_str).resolve() - - # Draw molecule at different resolutions - for g in [res_graph, mol_graph]: - nx.draw_networkx(g) - plt.show() - - # Get fragment corresponding to first residue - fragment_1 = res_graph.nodes[0]['graph'] - Map all-atom structure to CG resolution --------------------------------------- @@ -68,9 +34,3 @@ BENZ.pdb from this repository. pos += mol.nodes[mapping[all_atom_node]]['position'] final_pos = pos / len(fragement) res_graph.nodes[node][final_pos] - -Searching the Martini databse of small molecules ------------------------------------------------- - -Here goes some example on how to lookup molecules from the Martini -Database using CGsmiles diff --git a/docs/source/gettingstarted/tutorials/martini.rst b/docs/source/gettingstarted/tutorials/martini.rst new file mode 100644 index 0000000..93fe416 --- /dev/null +++ b/docs/source/gettingstarted/tutorials/martini.rst @@ -0,0 +1,5 @@ +Martini Mappings +================ + +CGsmiles can be used to define Martini mappings. + diff --git a/docs/source/gettingstarted/tutorials/polymers.rst b/docs/source/gettingstarted/tutorials/polymers.rst new file mode 100644 index 0000000..34ee560 --- /dev/null +++ b/docs/source/gettingstarted/tutorials/polymers.rst @@ -0,0 +1,87 @@ +Polymers +======== + +Here is a collection of CGsmiles applications for +polymer molecules. + +Linear Polymer Polystyrene +-------------------------- + +Polystyrene (PS) is one of the most common commodity +polymers. In the following example we resolve the +CGSmiles string and draw the polymer graphs. + +.. code:: python + + import networkx as nx + import pysmiles + import cgsmiles + from cgsmiles.drawing import draw_molecule, FRAGID_TO_COLOR + + # Express 5 units of Polystyrene in CGsmiles + cgsmiles_str = "{[#PS]|5}.{#PS=[$]CC[$](c1ccccc1)}" + + # Resolve molecule into networkx graphs + res_graph, mol_graph = cgsmiles.MoleculeResolver(cgsmiles_str).resolve() + + # Draw graph at the monomer resolution + labels = nx.get_node_attributes(res_graph, 'fragname') + ax, pos = draw_molecule(res_graph, + colors=FRAGID_TO_COLOR, + cg_mapping=False, + labels=labels) + + # Draw graph at the atomic resolution + pysmiles.remove_explicit_hydrogens(mol_graph) + ax, pos = draw_molecule(mol_graph, scale=0.7 + +Graft Polymer mPEG Acrylate +--------------------------- + +mPEG Acrylate is a branched graft polymer, that +contains PEG units attached to an poly methyl acrylate +backbone. In CGSmiles we can represent this polymers +in multiple equivalent ways. + +.. code:: python + + import matplotlib.pyplot as plt + import networkx as nx + import pysmiles + import cgsmiles + from cgsmiles.drawing import draw_molecule + + # Using 2 resolutions + cgsmiles_str_two = "{[#PMA]([#PEG]|3)|5}.{#PMA=[<]CC[>]C(=O)OC[$],#PEG=[$]COC[$]}" + + # Using 3 resolutions + cgsmiles_str_three = "{[#mPEG]|5}.{#mPEG=[$][#PMA][$]([#PEG]|3)}.{#PMA=[<]CC[>]C(=O)OC[$],#PEG=[$]COC[$]}" + + # Resolve molecule into networkx graphs + # Using the resolve_all method we directly jump to the last level + # which means that res_graph is the graph of the monomeric repeat units + res_graph_two, _ = cgsmiles.MoleculeResolver.from_string(cgsmiles_str_two).resolve_all() + res_graph_three, _ = cgsmiles.MoleculeResolver.from_string(cgsmiles_str_three).resolve_all() + + # Let's make a custom coluring function that colors by fragment name + def custom_colors_names(graph): + fragname_colors = {"PMA": "tab:blue", "PEG": "tab:red"} + fragnames = nx.get_node_attributes(graph, "fragname") + colors = {node: fragname_colors[fragname] for node, fragname in fragnames.items()} + return colors, fragnames + + colors_two, labels_two = custom_colors(res_graph_two) + colors_three, labels_three = custom_colors(res_graph_three) + + # Draw the residue graphs only + fig, axes = plt.subplots(1, 2, figsize=(10, 6)) + draw_molecule(res_graph_two, + ax=axes[0], + cg_mapping=False, + colors=custom_colors, + scale=0.75) + draw_molecule(res_graph_three, + ax=axes[1], + cg_mapping=False, + colors=custom_colors, + scale=0.75) diff --git a/docs/source/gettingstarted/tutorials/resolving.rst b/docs/source/gettingstarted/tutorials/resolving.rst new file mode 100644 index 0000000..a45ac41 --- /dev/null +++ b/docs/source/gettingstarted/tutorials/resolving.rst @@ -0,0 +1,55 @@ +Reading & Resolving +=================== + +A CGsmiles string can contain a base-graph (see Syntax Rules) and +multiple enumerations of fragment graphs each corresponding to a +different resolution. The base graph can be read using the +``read_cgsmiles`` function, while the fragments can be read using +the ``read_fragments`` function. However, most user will find it +convenient to directly read the entire string and resolve the +different resolutions. This is done using the ``MoleculeResolver`` +class. + +First we need to import the ``MoleculeResolver`` and initiate it +using the ``from_string`` or one of the other initiator methods. +Note that we can specify if the last resolution is at the atomic +level by providing ``last_all_atom=True`` argument. + +.. code-block:: python + + from cgsmiles import MoleculeResolver + cgsmiles_string = '{[#TC5]1[#TC5][#TC5]1}.{#TC5=[$]cc[$]}' + resolver = MoleculeResolver.from_string(cgsmiles_string, + last_all_atom=True) + +Next we can resolve the atomic resolution from the CG graph by +running the ``.resolve`` function once. + +.. code-block:: python + + cg_graph, aa_graph = resolver.resolve() + +For multiple resolutions we can run the ``resolver`` function +multiple times. Each time a new set of graphs at a coarse level +and the next finer level is returned. Alternatively, the +``resolve_iter`` can be used to loop over all resolutions. Let's +take the molecule in Figure 3 of the main paper: + +.. code-block:: python + + from cgsmiles import MoleculeResolver + # CGsmiles string with 3 resolutions + cgsmiles_str = "{[#hphilic][#hdphob]|3[#hphilic]}.\ + {#hphilic=[<][#PEO][>]|3,#hdphob=[<][#PMA][>]([#BUT])}.\ + {#PEO=[<][#SN3r][>],#PMA=[<][#TC3][>][#SN4a][$],#BUT=[$][#SC3][$]}.\ + {#SN3r=[<]COC[>],#TC3=[<]CC[>][$1],#SN4a=[$1]C(=O)OC[$2],#SC3=[$2]CCC}" + # Generate the MoleculeResolver + resolver = MoleculeResolver.from_string(cgsmiles_str, last_all_atom=True) + + # Now we can loop over all resolutions using + for coarse_graph, finer_graph in resolver.resolve_iter(): + print(coarse_graph.nodes(data='fragname')) + print(finer_graph.nodes(data='atomname')) + +Alternatively, we could just have gotten the final two pairs by calling +``.resolve_all()``. diff --git a/docs/source/index.rst b/docs/source/index.rst index c72f144..1926816 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -9,7 +9,7 @@ Table of Contents gettingstarted/installation gettingstarted/syntax_examples - gettingstarted/api_examples + gettingstarted/tutorials/index.rst .. toctree:: :maxdepth: 2 @@ -25,7 +25,6 @@ Table of Contents :maxdepth: 2 :caption: API - api/overview.rst api/cgsmiles.resolve api/cgsmiles.drawing api/cgsmiles.sample From cfd3e599bc324b53d910df0e86b8c5f907d6898d Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Wed, 8 Jan 2025 16:43:24 +0100 Subject: [PATCH 5/7] fix resolve docstring --- cgsmiles/resolve.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cgsmiles/resolve.py b/cgsmiles/resolve.py index 9a6c29e..da89145 100644 --- a/cgsmiles/resolve.py +++ b/cgsmiles/resolve.py @@ -129,6 +129,7 @@ class MoleculeResolver: --------------------- Alternatively, one could have gotten the block level graph from somewhere else defined as `nx.Graph` in that case: + >>> # the string only defines the fragments >>> cgsmiles_str = "{#B1=[#PEO]|4,#B2=[#PE]|2}.{#PEO=[>]COC[<],#PE=[>]CC[<]}" >>> block_graph = nx.Graph() @@ -141,6 +142,7 @@ class MoleculeResolver: case the `from_fragment_dicts` method can be used. Please note that the fragment graphs need to have the following attributes as a graph returned by the `cgsmiles.read_fragments` function. + >>> fragment_dicts = [] >>> for frag_string in ["{#B1=[#PEO]|4,#B2=[#PE]|2}", "{#PEO=[>]COC[<],#PE=[>]CC[<]}"]: >>> frag_dict = read_fragments(frag_string) From b040b457aa60b2283cdb59889b34b501fd4f98ed Mon Sep 17 00:00:00 2001 From: Fabian Gruenewald Date: Wed, 8 Jan 2025 17:38:15 +0100 Subject: [PATCH 6/7] add images --- .../gettingstarted/tutorials/martini.rst | 54 +++++++++++++++++- docs/source/images/dichlorotoluene.jpeg | Bin 0 -> 84222 bytes docs/source/images/toluene.jpeg | Bin 0 -> 80338 bytes 3 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 docs/source/images/dichlorotoluene.jpeg create mode 100644 docs/source/images/toluene.jpeg diff --git a/docs/source/gettingstarted/tutorials/martini.rst b/docs/source/gettingstarted/tutorials/martini.rst index 93fe416..5ed8071 100644 --- a/docs/source/gettingstarted/tutorials/martini.rst +++ b/docs/source/gettingstarted/tutorials/martini.rst @@ -1,5 +1,57 @@ Martini Mappings ================ -CGsmiles can be used to define Martini mappings. +CGsmiles can be used to define Martini mappings. Currently there a no canonical +rules on how the CGsmiles string needs to be formatted. However, we recommend to +follow the rules listed below to store all information relevant for forward +mapping, bead assignment, and backwards mapping. +Design Guidelines +----------------- + +- use the bead type as fragment name in the CGsmiles string +- if there are multiple fragments that have the same bead type append letters + A-Z (e.g. TC5 -> TC5A) +- if an atom is part of more than one fragment use the [!] bonding operator +- annotate chiral atoms in the fragment part +- annotate cis/trans isomers in the fragment part +- annotate weights if applicable +- annotate charges in the Martini resolution part if there are any +- draw your string to check it's correctness + +Consider the difference between the Martini 3 mappings for Toluene and +2,4-dichlorotoluene shown below. + +.. list-table:: Martini Mappings of Toluene and 2,4-dichlorotoluene + :widths: auto + :header-rows: 0 + + * - .. image:: /images/toluene.jpeg + :width: 400px + - .. image:: /images/dichlorotoluene.jpeg + :width: 400px + +In Toluene the two TC5 beads are equivalent and connect the same way to the SC4 +bead. Therefore the CGsmiles string below is valid. + +.. code:: + + Toluene + {[#SC4]1[#TC5][#TC5]1}.{#SC4=Cc(c[!])c[!],#TC5=[!]ccc[!]} + +However, in 2,4-dichlorotoluene there are two SX3 beads which are equivalent +except for the fact that they connect to the SC4 beads at different carbons. Once +the carbon with the chlorine connects to the SC4 and once the carbon without the +chlorine connects. To represent this connectivity correctly you need to use two +different fragments and two differently labeled bonding operators in the CGsmiles +string as shown below: + +.. code:: + + 2,4-dichlorotoluene + {[#SC4]1[#SX3][#SX3A]1}.{#SC4=Cc[$a]c[$],#SX3=Clc[$a]c[$b],#SX3A=Clc[$b]c[$]} + + +Examples +-------- +An extensive list with examples can be found in the publication. diff --git a/docs/source/images/dichlorotoluene.jpeg b/docs/source/images/dichlorotoluene.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..5be30e8e8f4cd7bc29b79977640e4cdcb58a026c GIT binary patch literal 84222 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%0D--ba=Z-~>w4x28pRQa;hY1B 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 z z&BvS#xJXcee36XJ1Oyo|IzhSw3!32&T_^y-z@E6+IIuu)e)Xr8dQB*wM05rd`mt{i zn7+3?T|%gMNKQNRvjN{R9LvMa3;%ZJFvNVBaS}~-P$Yoz77$A5mSU|(LDoF6!nTCM zKJ&XQeaX`6s(8Inpuzc}eo1W*w)qilT9)5hhy}oC*T#306O);`cLof#wXsf5T&m3+ z6hX!}eql_DfcpUbINp_cdcG=_OpuI#yjj}L5tL_+UH;W@4I|zNoGEs*H-&rt`Lc?J z`W);#mecUXtv@J&Xpn{;vlc%5q1}^ZJkL>RO&80+LUtSHlbsviAt#E{01WF>Awb>w zjUG#PF%x{K^d8MM;$<>l`}|O+LUuJ^w^K?phO3%*5&MpV)xa7?dORz!|Clqj3=*(? z^B!lH==Yysb94qM1q{#8=0udF89ev0`RC$#1KRo=nlh}w*-TL$RFZG4s$HRlSW@9< zvwX*7E5^HEMl+Lr(VZPXahR{x5;e!SPeHy5hE>LnS9J&ot*CIW(~Yq z>~YOLM76o<)Pd5(ANpY9$*Yt*6)}gX zP0(?!RXRURB|M>?yC-=;k5=t<>}ZaOD6LmM_^T)k8xU|A5u@4)%N}4bHSCRK-aNA1 zM{O4MHFu?HhJ>N*{3Xugr;{9$MoQqyMdgk9b{0eij3gf3 z?a5s&{b1+yy9e&u38k8K>;MG@3Q+nHM(4q6Ia9AE1al?j>_ax1`%uaz$Zj@`B3;@S zX^GwHnB?2&(d~3oqkT*{i?CMdL{6?m#t~@Ak zujC!LtI()>vGz9O1c!0-G|gL|iUMn;hPbNiN>?|VuL0!7Q?*(}M{_=<{tFQ}lJlE7 zP#t*kvZ)6Bb6?*vsHpbwLel5Ak%JO={)x(t*2RNPwJMX)@1!pNp_0Lf*9w5H$4t8j`7I24ff{_?rT4izpH@ir_-bR(fjon zvA`5{H;SnhB!z*nYUrmmf1>vQH|z_rTCnYdH&}l(%g@?xd5b-ORI6TtXC0G)%&^?UU-~CbLn(cXebiF%c!xq>Rt#xLC-SCfr%D>^L)tZLTW1F%nOC^IgRK)F6K)+tlI|e?Fe!I4o9=zA<1w=_0|H9Eg{in4% zJQs4e4NbI6Yt8$=e7eE$tcNuNVD&l!(r>+2saNP|pd^XR`pI>acOVo)?6l>D&-wjI|?8D;-v^2T^e^~2e}|D z^Y=zdVt6fQXm!EY%3Pvb`;~}UzDqS>=}^8G74O#1PpGFDM#EMYl&p?k(zoe5Kt-xX#xlMd|5T;#WSoLFvAwqg7uIna!OOtd8B2jMU3zt6=>TcE*0{Q172OR{6pd05qnT?YBubO9HS1=K-Lj z9-#Li4%$**OyMNocRr(%N21@e8lXWZ)qphdNb2b^ASaPPet0BUV_$fEnqH0A}Pd{OSsZX2`SD#~D7_7P6`isWuCy?_z0R zO7R{k%hk{KQ;hGG$CFfKS65Q+tKS<%Kj)u4?tmqGNV?ZI8Jyppg^kjqo!3EboO2l> zZ9u#9`bP-T1p}131rQMR1M>&5QDG1S+h2wQQ$-s697&2IC-nV=;TCb+H}JRSShxNi zWC^NtvGEI|rz*GrvX`+Y#d>yljLnp>sYAJ%gA$H|1)3=B=yIXB;1{EMy0%TBus74M z?~CWdLcADA{}3_X5##*qil$>&ABVFo)GQ9*({4(l*Ep-g{y|2tY> zE?-%;HCZjEK9BvGI-}F62pPAL-mI!hmRAZ?AB2Ce09VJjL<#wgMcBC`=|VSB?(E}h zaZ8Ec*--)){lXC7+cLBc`BB3T$`{F?>Cqp@mcF~@$<^GS5jn3`{n7M2XNmbd7-S%I#v*`5ybNw~Df_l$Oa>#db2 zN&w9zU+~ojV4En*Q_`&#j{1BTXlKd@km^|{SOW>AM0p#2gFz^z7| z(4`}z^!ub}Nh4r|4_=Jk#=Kfd%@f6KyBdLKFnRsKWZS`TAxjToz=3h+P?z0%&`BVplq_8q=kvKiKhEV4PY`f8o%aDlV6)QHYQ zUv4^j&397x-Yrhcyew@*u=OEtMiv>u0Vcw#b z;6mYpuBMTvqonJU{cV9Np>{Bqb>8VWO;B0t#(7iCcHC#Qx2`1Z->j*J$#%44xfa&+ zY*BfrWZebM$IgK)@(5;r#@28n%I#()ep9QLL5^-JXyc}EkUFUG9pV|;D@fKG@47>@ z&nn>Z0%}uEPh9u;wy#*JngCoW?(~Av1@5{*QYuQp`-;p#B~+L$ zqQ|OtLjCh)S?{Au?^V=Q?=K7(9@rLeBB_-Cq;wu>@CzfrW}oruwiB{P3C$33X1e|whyt+B{ zheO$lD1EngbG zMmfl0f(J^Oznw|E+Ea+Bi_Do47xw3-`($P_&=&q|Rq%>=_(;v9TamN<{N)RG%MSOX z@`$a8%_apF+(dT`iKAtu!z)(seBptg=j%3Hdu{^#HUP_qxG*=WcrRn}s=qx;O9|)$ zA7!cSk~X*P%<+h^OJn+0#eCbTQk?)z<&%2@dHL*B$zxLauVSfcU8RxAx+Bqx-i)t; zB%2$1yu3P?8&}7xj5dQutZijq%uFPIF)$E?wN@=^Jb=EOi^2Zn@>$C#c(F7UJf_{+ zzn-zJdW0`dh&+Y_uIiBl*|X0Cn%E25#A>Qf7llSKOO-HkXjX^2d3<`G6oYU#3xO@! z0oZ>8kPm(hK4IDaSMILc4^6LRa42jKZ1!iZqs9N2hD;O~T>HoT9N6d+REx7ZnzEL> zN#d;NSeo`9yQ=`4Gp6;Quo-aI0rtEPz`k#bO$8Pe_#As3StFUbUd}MFv;XD`hFpx% zj|=PtFXzJbiPFLZ&1tsohefZ6LvP;6$skps?o_Tz^UdPZ<}NNrVW$7Voq_$X&r@|% zXGWh*C5UXBrOodMG-7eFl9<=IKQ_(QDX#9`T`G7Z3W zh_`#do5pJD@krV(I;QKNL9(2kBj-I0iq1?MX}DgBx1ZW3UX0Xw9lJZKMIVYG@u;<* z=mSZIhSZP$s>u6)&-?05D(Xeiq`ietMxZKDs(m}!>;OSi8__Iec2X8(|Ms?V>)9`i z8On%W_@cg$xQh5K!XLNmqy^Kq_9-_le7u#4JDy-89!Bm`iRs>KBS0JW;0%?=n?Mm#A4HKZ999N#ln2aHiySHxqh(mfP;E&8%X`^U_t2TC0#k5Z(kVoTcV}3QXao*lGD=< zSpk}iOL|xC8UD>$xI?;6(F8~(@5VUv$(BZ30tJ#$%tItE(q-sv#*ouyh@j6>J5(&N z2t~j*Kl=1wLQme7dpnkasc8y|e3$Fy;37iV6UQ#eHx!&!S^ECq_WKp$ScZ*oZWc?{ z+WZ~&+|oRO&DlV0;~njh1<$v?FbHOq&krw#>CcRP>#VhcFL1oTSJ>4XtjY6CD*>E#M^n2a)9(H93nC*gYgOj<3Jkd5Pe>)bnGBv{)NquQo%x~e{P1q6sF)RV{D>gb ziue&>@7v)#mI;Ob)83s&vz_e$0A~!fYn2kEnN#f`u{?@)8j>mFS*Au&LZm!nsI4@$ zFTqf1DphE4nv|39A)u^pw7g|fhh<$#}dwqLch1at=iEPXPR_~i z3#pp<{TcePpqSyBtJlm~i+~q*<7#lR{zZNG z@|2&k>glqDu5T*;0Uv!KHc`HYNpZdB7b;vqVRaW7G$KS0ifeL<@D8qJb!_2#W;Ksg zDy=9hP^!R!KMP8tmnn3nH2801{m$eP2l(vYPQ3l$XG2w6ZisdFQK{0&oL!TXlM&xc zY@^QDrte|%LGfKt<}o`Lzh*v=NWlAV9dGTqI^Xwtp<_8wz}h6Qo4$-dEEMM9T_ir@ z@G6~k#lrFR(M1-^qVd^sFr+$hQV#aaNSPV=uLasZb5Y)ex^hKA;pHE{D1e>VxS$j_ zsGO=?R;1^0OowzwGL%0duRdAQ0ZQG9(P@*4w2%p7S~W<}d1CT+mbm;hrOZ%)@*9>P zhzZ#6je2CqI5b92TDGo4&1(eMFSU7M_GT#iv~GP0g{sYt%qAykL?51VKf!(pzSdpset50`idpZgSKxNZk zK{pk>qVq?x5WzA3IaBj@o{?bULFd1@RodE&$(>KjZ5u(~7+{dzSq-}|Jg@k`3@>~X zAykd20BjZM~1x0E8R+zqR?oW zB6iNnDg%O%dWq6&7#Rl4GYy<;bGPr54<285}%@8cek467^TtlI@QXzAclwyBvx) zpnehJ@mM$C&}_T1vSwGH{%Bi!=E5$=O>gqbTMvu(cZ+StXKO>7&{*}Z?ILgIU~Ril zHYd~(t_fy;rbLlb*Q!^ER(znM+Ezqhy+@bU^oo_Uny^0fyyKQ6@?|LD5zSi^Ul%wIj0_TXIAS%82uRx7PUw(Dl@zLJ>3b zgV!fCx{d^|LMy8%TNgvWVMO%)DeuCPlFM~ZtduALj$UB}YQ{oCgq+dnsCurZ(SwMd zxx2UhWre;}#C+{Ji&%>aK71_0jAh2BM4ytKgTfXu+sHDiigt@WXxGca>@0wOY~j3b=)h4#6Jaoqy5jMj=u z)#A+YxJgVtZfX7}lR&dgDAu~exp-lmo8}zMSjVum!edw55^tmkdvQ;-+IDg!GB0M1 zwTA61n(JT&uP4sq)!J1Fo2S)Y2dY&PMs&=y}EXPT&0Y|KZkjeJ^pO+#ustt$EQiry=919?vKzF?1% zfrGn~{iu`DhW9q|n*5L9s?w@C&Sw$w93fxSI!~Gh_LJ*1&bSe{HFD*v=Chx=pgHdA zl+Ptclc;1^2fT0=Bgh~)BPBtrTGE#2hLbzX z%%QzV?3=wj$4?T=_P^(ChZ&WAc_>^YaFRylw0h@p_i3DAH|pEYD{S8_3}U{ZM4|uQ zY5&KF3VREj>r9+$$kBf9WaF-4$s1d=#A%aG?=6D0#^NfOI0n^kpBT3K4iDw_3*B6g zcdBJh!(TN_bQY6+@>S}VamYT85NT$z^w98dGm%dalM;&SCXJ;>Y?hdsY|kGd7E_2GxV%QSx{u}(6ckbgw664c$q zB+d9J9$}3b9j}Tq{7G9}tZWtvi+XHm@ zZ6x3HK|B8HJ`+SbdnEC6FB)!lNt|R#aFe(c7Ih1XHSPFA;rZ`-+|~VqYmq)W`kntQ z;>L30^^6)1(^qEg6-?Qd_03QTj_|(z#r}Wo|Gk3-z7IS@U;r2Z27m!z02lxUfB|3t z7yt%<0bl?a00w{oU;r2Z27m!z02lxUfB|3t7yt%<0bl?a00w{oU;r2Z27m!z02lxU xfB|3t7yt%<0bl?a00w{oU;r2Z27m!z02lxUfB|3t7yt%<0bt-yHNds|_BVtnX~zHn literal 0 HcmV?d00001 diff --git a/docs/source/images/toluene.jpeg b/docs/source/images/toluene.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..b01b1e713c216fd5d574b6a7f935f49491cf89d0 GIT binary patch literal 80338 zcmeFa2UL^Yn&=-yMG!;~L}@`lX^Kh_sSywm5JL|gr56F|ornlXjdVdE(tGH=N$>;_KIm?~>mlrJ$n*)6-GY(lRh}vM?|{ zWTK^Id>6W<{rxx;mj_8!;2`{(>8i0m2x z@CP4{5p;dsy2 z`wv)H+1Pn_pYc8C7Z86bAu07rT1H7(MHQx|uAy&WXk=_+YG!L^@8Iax9FI}q~w&;wDgS3g2JNWlG3vBiu#7erskH`w)Xyk!J*-i(XsK_x%q{~ zrR9~?wcWk_gTtd^^vUV(ap8gR|2nL{jqD%BMFx!P(&fwemkEB43-6L6(D2DFU%B`6 zD!Hfv!8_|)j9i}AD8xb%^6LnhxD|IO^=$fxZZq@D-rxN_w7-n(-!`yM|DlonZD9X2 zu5l1CJ|6J!@X0_B(D5m2;zy#3b}AZv)*xS5W9#d4SvN&o(U zZSlF^w&D6Y2y`mrf1+x27aeaKHFEekQL6ya>mTH%KF7na4Ob?>)iR%hsB1H8md-(= z#gJ$)OhNa_gqy^!ea2N`o5lQb`2zULixH_bH}jFrF^R*JxVan2Y(YJ9gy0m}2D$f! zLh)4|x1+c1VDT%{cdGc)3tC)Q22z9=27 zhu^2`t5fyXcr(PLXU3YC+{l{f5BfK?Ok1;Yi+o0Rv3^DY-)I;(I-RO9=2k3caoVj- zJLLSE&J;r7kTeNVD_2toZVOxD23&M2&582C=K)P`3WXP+{8@iox@iC3&Ox3Kbe@E% zJ~G}iLB#0Yx6Py_X%VlyV<8y#{5i-k&&jrPfF~zxt*jw`@e!^jXY=F~e5I%L&TG!1jiN{VQQ`g@nrhXpFtr!i8y#_#43{sP-y9P2LoLi2^3pFI85fK6NT z@WLN2^JhhkHc6<1C1e8Gq#}u00kn)fB`D8wQXHG znEedW&&AjKV_}7A^#I)Gfg6wRR=m>@CSR-g3HGhix6TxZhwmg#sxKUN%W9XLBc0so zA%62bdp#W2oN^BOypKz$JprGC@=;dCoGpk$jgExTJ}pNK3-XZU9MpOa!UlnN;^BjQ z=OCtY&=HR-Kf#dM5hl3Vf4+8B=p2-8g_Al5ZJ7MItE913kt|s0f&&kX?i@t8j1`CP zLC!(?Qh)B1zFYKAqq>$UKe22Z4TY@HoP$EnL1)yEKlgfMLz+3Xc|sRN*#fV3J_pH9 zV-SFl==-}~3%|On=32FGn4ko4w|Y)}⩔n;1GZAR5;dat=QE=N?*cy`cI?bl~qJ1 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 Date: Wed, 8 Jan 2025 17:58:33 +0100 Subject: [PATCH 7/7] change CGSmiles to CGsmiles --- cgsmiles/tests/test_utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cgsmiles/tests/test_utils.py b/cgsmiles/tests/test_utils.py index b485e58..93f94fe 100644 --- a/cgsmiles/tests/test_utils.py +++ b/cgsmiles/tests/test_utils.py @@ -6,7 +6,7 @@ "show delocalization-induced molecular equivalency and thus " "is not considered aromatic. For example, 4-methyl imidazole " "is often written as [nH]1cc(nc1)C, but should be written as " - "[NH]1C=C(N=C1)C. A corresponding CGSmiles string would be " + "[NH]1C=C(N=C1)C. A corresponding CGsmiles string would be " "{[#A]1[#B][#C]1}.{#A=[>][<]N,#B=[$]N=C[>],#C=[$]C(C)=C[<]}") @pytest.mark.parametrize('frag_str, hatoms_ref, error_type, err_msg', (