{"id":9983,"date":"2023-06-17T11:21:26","date_gmt":"2023-06-17T10:21:26","guid":{"rendered":"https:\/\/www.blopig.com\/blog\/?p=9983"},"modified":"2023-07-12T16:08:02","modified_gmt":"2023-07-12T15:08:02","slug":"customising-mcs-mapping-in-rdkit","status":"publish","type":"post","link":"https:\/\/www.blopig.com\/blog\/2023\/06\/customising-mcs-mapping-in-rdkit\/","title":{"rendered":"Customising MCS mapping in RDKit"},"content":{"rendered":"\n<p>Finding the parts in common between two molecules appears to be a straightforward, but actually is a maze of layers. The task, maximum common substructure (MCS) searching, in RDKit is done by <code>Chem.rdFMCS.FindMCS<\/code>, which is highly customisable with lots of presets. What if one wanted to control in minute detail if a given atom X and is a match for atom Y? There is a way and this is how.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">Aim<\/h2>\n\n\n\n<p>First, there are several resources on the basics of MCS, such as <a rel=\"noreferrer noopener\" href=\"https:\/\/greglandrum.github.io\/rdkit-blog\/posts\/2022-06-23-3d-mcs.html\" target=\"_blank\">Greg Landrum&#8217;s tutorial on the subject<\/a>. Herein, I want to discuss a specific use case: full control over the atom matching itself.<br>There are two options: calculate all combinations and filter them, or create a custom atom matcher. The first option will grind to a halt very quickly, so the second option is often the only viable one.<br>For <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/matteoferla\/Fragmenstein\" data-type=\"URL\" data-id=\"https:\/\/github.com\/matteoferla\/Fragmenstein\" target=\"_blank\">Fragmenstein<\/a> in order to map a molecule onto overlapping 3D molecules I had to resort to the latter <a href=\"https:\/\/github.com\/matteoferla\/Fragmenstein\/tree\/master\/fragmenstein\/monster\/_place_modes\/_mappings.py\" data-type=\"URL\" data-id=\"https:\/\/github.com\/matteoferla\/Fragmenstein\/tree\/master\/fragmenstein\/monster\/_place_modes\/_mappings.py\" target=\"_blank\" rel=\"noreferrer noopener\">deep in the code<\/a>.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2023\/06\/fig_SI2-MCS.png?ssl=1\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"381\" height=\"428\" loading=\"lazy\" src=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2023\/06\/fig_SI2-MCS.png?resize=381%2C428&#038;ssl=1\" alt=\"\" class=\"wp-image-9984\" srcset=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2023\/06\/fig_SI2-MCS.png?w=381&amp;ssl=1 381w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2023\/06\/fig_SI2-MCS.png?resize=267%2C300&amp;ssl=1 267w\" sizes=\"auto, (max-width: 381px) 100vw, 381px\" \/><\/a><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Basics<\/h2>\n\n\n\n<p>As the basics are well covered elsewhere, I am going to wizz through them.<\/p>\n\n\n\n<p>To find the MCS between two molecules, the following code can be used.<br>The function `name2mol` (get a mol from a name via NIH Cactus) and `flatgrid` (a `Draw.MolsToGrid` wrapper) are defined in the footnote.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from rdkit import Chem\nfrom rdkit.Chem import rdFMCS\n\ncapsaicin = name2mol('capsaicin')\njasminaldehyde = name2mol('jasminaldehyde')\nresult: rdFMCS.MCSResult = rdFMCS.FindMCS([capsaicin, jasminaldehyde])\nflatgrid([capsaicin, jasminaldehyde, Chem.MolFromSmarts(result.smartsString)])<\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><a href=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2023\/06\/first.png?ssl=1\"><img data-recalc-dims=\"1\" decoding=\"async\" width=\"600\" height=\"200\" loading=\"lazy\" src=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2023\/06\/first.png?resize=600%2C200&#038;ssl=1\" alt=\"\" class=\"wp-image-9985\" srcset=\"https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2023\/06\/first.png?w=600&amp;ssl=1 600w, https:\/\/i0.wp.com\/www.blopig.com\/blog\/wp-content\/uploads\/2023\/06\/first.png?resize=300%2C100&amp;ssl=1 300w\" sizes=\"auto, (max-width: 600px) 100vw, 600px\" \/><\/a><\/figure>\n\n\n\n<p>This is not what one would expect, because one needs to tweak the parameters of the search to match that expectation.<br>The function <code>rdFMCS.FindMCS<\/code> went throw several evolutions and is overloaded. The first older\/messier way is that it accepts several arguments, which I would say fall into two groups:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The boolean arguments <code>matchValences<\/code>, <code>ringMatchesRingOnly<\/code>, <code>completeRingsOnly<\/code>, <code>matchChiralTag<\/code><\/li>\n\n\n\n<li>The compare enum arguments <code>atomCompare<\/code>, <code>bondCompare<\/code>, <code>ringCompare<\/code><\/li>\n<\/ul>\n\n\n\n<p>The two are mostly mutually exclusive but don&#8217;t have a univocal mapping (<code>ringMatchesRingOnly<\/code>, <code>completeRingsOnly<\/code> are both covered by <code>ringCompare<\/code>). The compare enums are more powerful. <br>For example, here are the values they can take:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>atomCompare<\/code> argument accepts the <code>rdFMCS.AtomCompare<\/code> enum, which has the values<br>\u2767 <code>CompareAny<\/code>, <code>CompareElements<\/code>, <code>CompareIsotopes<\/code>, <code>CompareAnyHeavyAtom<\/code>.<\/li>\n\n\n\n<li><code>bondCompare<\/code> wants <code>rdFMCS.BondCompare<\/code> <br>\u2767 <code>CompareAny<\/code>, <code>CompareOrder<\/code>, <code>CompareOrderExact<\/code><\/li>\n\n\n\n<li><code>ringCompare<\/code> wants <code>rdFMCS.RingCompare<\/code> <br>\u2767 <code>IgnoreRingFusion<\/code>, <code>PermissiveRingFusion<\/code>, <code>StrictRingFusion<\/code><\/li>\n<\/ul>\n\n\n\n<p>The newer one, accepts a single parameter object (<code>rdFMCS.MCSParameters<\/code>). This objects two attributes with boolean-holding namedtuple-like objects (<code>AtomCompareParameters<\/code> and <code>BondCompareParameters<\/code>), which are a reshuffle of the boolean arguments; and two &#8220;typer&#8221; attributes (<code>AtomTyper<\/code> and <code>BondTyper<\/code>), which can be more than just the enums and allows some nice stuff.<\/p>\n\n\n\n<p>For Fragmenstein, I was in need of transpiling the former system into the latter as arguments may be passed in the former system but the latter was used. As a result I had <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/matteoferla\/Fragmenstein\/blob\/7394775c72c6359cbd66025024aca6a74b773661\/fragmenstein\/monster\/mcs_mapping\/utils.py#L64\" data-type=\"URL\" data-id=\"https:\/\/github.com\/matteoferla\/Fragmenstein\/blob\/7394775c72c6359cbd66025024aca6a74b773661\/fragmenstein\/monster\/mcs_mapping\/utils.py#L64\" target=\"_blank\">a function to do so<\/a>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def transmute_FindMCS_parameters(\n        **mode: Unpack[ExtendedFMCSMode]) -&gt; rdFMCS.MCSParameters:  # noqa lowercase not applicable\n    \"\"\"\n    The function ``rdFMCS.FindMCS`` has two ways of being used.\n    In one, a series of arguments are passed,\n    in another a ``rdFMCS.MCSParameters`` object is passed (a wrapped C++ structure).\n    Unfortunately, there does not seem to be a way to transmute the former into the other.\n\n    Hence, this function\n\n    The ``params.AtomTyper`` and ``params.BondTyper`` members\n    can be either\n\n    * the enum ``rdFMCS.AtomCompare`` or ``rdFMCS.BondCompare``\n    * or a subclass of ``rdFMCS.MCSBondCompare`` or ``rdFMCS.MCSAtomCompare``\n\n    The ``rdFMCS.RingCompare`` appears to be absorbed into the ``params.BondCompareParameters``\n    member.\n    \"\"\"\n    params = rdFMCS.MCSParameters()\n    # three integers: https:\/\/github.com\/rdkit\/rdkit\/blob\/b208da471f8edc88e07c77ed7d7868649ac75100\/Code\/GraphMol\/FMCS\/FMCS.h\n    # they are not rdFMCS.AtomCompare rdFMCS.BondCompare and rdFMCS.RingCompare enums?\n    # atom parameters\n    atomCompare: rdFMCS.AtomCompare = mode.get('atomCompare', rdFMCS.AtomCompare.CompareElements)\n    params.AtomTyper = atomCompare  # int or a callable\n    params.AtomCompareParameters.MatchIsotope = atomCompare == rdFMCS.AtomCompare.CompareIsotopes\n    params.AtomCompareParameters.CompleteRingsOnly = mode.get('completeRingsOnly', False)\n    params.AtomCompareParameters.MatchChiralTag = mode.get('matchChiralTag', False)\n    params.AtomCompareParameters.MatchValences = mode.get('matchValences', False)\n    params.AtomCompareParameters.RingMatchesRingOnly = mode.get('ringMatchesRingOnly', False)\n    # bond parameters\n    bondCompare: rdFMCS.BondCompare = mode.get('bondCompare', rdFMCS.BondCompare.CompareOrder)\n    ringCompare: rdFMCS.RingCompare = mode.get('ringCompare', rdFMCS.RingCompare.IgnoreRingFusion)\n    params.BondTyper = bondCompare\n    params.BondCompareParameters.CompleteRingsOnly = mode.get('completeRingsOnly', False)\n    params.BondCompareParameters.MatchFusedRings = ringCompare != rdFMCS.RingCompare.IgnoreRingFusion\n    params.BondCompareParameters.MatchFusedRingsStrict = ringCompare == rdFMCS.RingCompare.StrictRingFusion\n    params.BondCompareParameters.RingMatchesRingOnly = mode.get('ringMatchesRingOnly', False)\n    params.Threshold = mode.get('threshold', 1.0)\n    params.MaximizeBonds = mode.get('maximizeBonds', True)\n    params.Timeout = mode.get('timeout', 3600)\n    params.Verbose = mode.get('verbose', False)\n    params.InitialSeed = mode.get('seedSmarts', '')\n    # parameters with no equivalence (i.e. made up)\n    params.BondCompareParameters.MatchStereo = mode.get('matchStereo', False)\n    params.AtomCompareParameters.MatchFormalCharge = mode.get('matchFormalCharge', False)\n    params.AtomCompareParameters.MaxDistance = mode.get('maxDistance', -1)\n    # params.ProgressCallback Deprecated\n    return params<\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Comparators<\/h2>\n\n\n\n<p>This meandering around legacy code-burden is to get to the new comparator objects.  The <code>atomTyper<\/code> attribute accepts the aforementioned enums, but also can accept a callable&#8230; such as a subclass of <code>rdFMCS.MCSAtomCompare<\/code> or <code>rdFMCS.MCSBondCompare<\/code>. These are callable class instances that when called with the parameter obejct, the two molecules and the two atoms or bonds that are to be compare, they return a boolean verdict. The largest number of contiguous truths wins!<\/p>\n\n\n\n<p>Obviously, there&#8217;s a catch. RDKit is PyBoost11\u2013wrapped C++ code \u2014as is evident by its flagrant disregard for PEP8 guidelines turned dogmas. In the case of subclassing a wrapped C++-class one cannot do super calls to unwrapped methods of the base class as the Python signatures will not match the C++ ones.<br>As a result the method __call__ needs to be written without parent calls:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">class VanillaCompareAtoms(rdFMCS.MCSAtomCompare):\n    \"\"\"\n    Give than the following does not work, one has to do it in full:\n\n    .. code-block:: python\n        :caption: This will raise an error:\n        super().__call__(parameters, mol1, atom_idx1, mol2, atom_idx2)\n\n    This class replicates the vanilla functionality\n    \"\"\"\n\n    def __init__(self, comparison: rdFMCS.AtomCompare = rdFMCS.AtomCompare.CompareAnyHeavyAtom):\n        \"\"\"\n        Whereas the atomCompare is an enum, this is a callable class.\n        But in parameters there is no compareElement booleans etc. only Isotope...\n        In https:\/\/github.com\/rdkit\/rdkit\/blob\/master\/Code\/GraphMol\/FMCS\/Wrap\/testFMCS.py\n        it is clear one needs to make one's own.\n        \"\"\"\n        super().__init__()  # noqa the p_obect (python object aka ctypes.py_object) is not used\n        self.comparison = comparison\n\n    def __call__(self,  # noqa signature matches... it is just Boost being Boost\n                 parameters: rdFMCS.MCSAtomCompareParameters,\n                 mol1: Chem.Mol,\n                 atom_idx1: int,\n                 mol2: Chem.Mol,\n                 atom_idx2: int) -&gt; bool:\n        a1: Chem.Atom = mol1.GetAtomWithIdx(atom_idx1)\n        a2: Chem.Atom = mol2.GetAtomWithIdx(atom_idx2)\n        # ------- isotope ------------------------\n        if parameters.MatchIsotope and a1.GetIsotope() != a2.GetIsotope():  # noqa\n            return False\n        elif self.comparison == rdFMCS.AtomCompare.CompareIsotopes and a1.GetIsotope() != a2.GetIsotope():  # noqa\n            return False\n        elif self.comparison == rdFMCS.AtomCompare.CompareElements and a1.GetAtomicNum() != a2.GetAtomicNum():  # noqa\n            return False\n        elif self.comparison == rdFMCS.AtomCompare.CompareAnyHeavyAtom \\\n                and (a1.GetAtomicNum() == 1 or a2.GetAtomicNum() == 1):  # noqa\n            return False\n        elif self.comparison == rdFMCS.AtomCompare.CompareAny:\n            pass\n        # ------- valence ------------------------\n        if parameters.MatchValences and a1.GetTotalValence() != a2.GetTotalValence():  # noqa\n            return False\n        # ------- chiral ------------------------\n        if parameters.MatchChiralTag and not self.CheckAtomChirality(parameters, mol1, atom_idx1, mol2, atom_idx2):\n            return False\n        # ------- formal ------------------------\n        if parameters.MatchFormalCharge and not self.CheckAtomCharge(parameters, mol1, atom_idx1, mol2, atom_idx2):\n            return False\n        # ------- ring ------------------------\n        if parameters.RingMatchesRingOnly and not self.CheckAtomRingMatch(parameters, mol1, atom_idx1, mol2, atom_idx2):\n            return False\n        # ------- complete ------------------------\n        if parameters.CompleteRingsOnly:\n            # don't know its intended to be used\n            pass\n        # ------- distance ------------------------\n        if parameters.MaxDistance:  # integer...\n            # todo fill!\n            pass\n        return True<\/pre>\n\n\n\n<p>Now, we can subclass it and change the call as we please.<\/p>\n\n\n\n<p>In my case, I wrote <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/matteoferla\/Fragmenstein\/blob\/7394775c72c6359cbd66025024aca6a74b773661\/fragmenstein\/monster\/mcs_mapping\/compare_atom.py#L75\" data-type=\"URL\" data-id=\"https:\/\/github.com\/matteoferla\/Fragmenstein\/blob\/7394775c72c6359cbd66025024aca6a74b773661\/fragmenstein\/monster\/mcs_mapping\/compare_atom.py#L75\" target=\"_blank\">a class that accepts a mapping that has to be obeyed<\/a>. For that I still however, need to add <a rel=\"noreferrer noopener\" href=\"https:\/\/github.com\/matteoferla\/Fragmenstein\/blob\/7394775c72c6359cbd66025024aca6a74b773661\/fragmenstein\/monster\/mcs_mapping\/compare_atom.py#LL193C9-L193C26\" data-type=\"URL\" data-id=\"https:\/\/github.com\/matteoferla\/Fragmenstein\/blob\/7394775c72c6359cbd66025024aca6a74b773661\/fragmenstein\/monster\/mcs_mapping\/compare_atom.py#LL193C9-L193C26\" target=\"_blank\">a method to filter out the matching substructures<\/a>, but that is an easy feat as there is already a method that says when two atoms match, i.e. the <code>__call__ <\/code>method of the comparator!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Edit<\/h2>\n\n\n\n<p>In recent versions, the comparisons are called by rdFMCS.FindMCS not only between molA to molB atoms, but also molB to molB atoms, presumably to address isomorphisms.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Footnote<\/h2>\n\n\n\n<p>The following snippet to gets me a molecule from a name using Cactus:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import requests\nfrom urllib.parse import quote\nfrom rdkit import Chem\nfrom rdkit.Chem import AllChem\n\ndef name2mol(name) -&gt; Chem.Mol:\n    \"\"\"\n    Using Cactus get a molecule from a name.\n    \"\"\"\n    response: requests.Response = requests.get(\n        f'https:\/\/cactus.nci.nih.gov\/chemical\/structure\/{quote(name)}\/smiles')\n    response.raise_for_status()\n    smiles: str = response.text\n    mol: Chem.Mol = Chem.MolFromSmiles(smiles)\n    mol.SetProp('_Name', name)\n    AllChem.EmbedMolecule(mol)\n    return mol<\/pre>\n\n\n\n<p>I always want to see the molecules on a grid but in 2D, so:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from rdkit.Chem import Draw\nfrom typing import List\nfrom IPython.display import Image\n\ndef flatgrid(mols, *args, **kwargs) -&gt; Image:\n    copies: List[Chem.Mol] = [Chem.Mol(m) for m in mols]\n    *map(AllChem.Compute2DCoords, copies),   # noqa, it's in place\n    if 'legends' not in kwargs:\n        kwargs['legends'] = [m.GetProp('_Name') if m.HasProp('_Name') else '-' for m in mols]\n    return Draw.MolsToGridImage(copies, *args, **kwargs)<\/pre>\n\n\n\n<p>Obviously, in the main text I have a figure, so the code was in reality:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from IPython.display import Image\n\nimg: Image = flatgrid([capsaicin, jasminaldehyde, Chem.MolFromSmarts(result.smartsString)])\nwith open('molecules.png', 'wb') as fh:\n    fh.write(image.data)<\/pre>\n","protected":false},"excerpt":{"rendered":"<p>Finding the parts in common between two molecules appears to be a straightforward, but actually is a maze of layers. The task, maximum common substructure (MCS) searching, in RDKit is done by Chem.rdFMCS.FindMCS, which is highly customisable with lots of presets. What if one wanted to control in minute detail if a given atom X [&hellip;]<\/p>\n","protected":false},"author":102,"featured_media":0,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"nf_dc_page":"","wikipediapreview_detectlinks":true,"_monsterinsights_skip_tracking":false,"_monsterinsights_sitenote_active":false,"_monsterinsights_sitenote_note":"","_monsterinsights_sitenote_category":0,"ngg_post_thumbnail":0,"_jetpack_memberships_contains_paid_content":false,"footnotes":""},"categories":[187,227,201],"tags":[],"ppma_author":[701],"class_list":["post-9983","post","type-post","status-publish","format-standard","hentry","category-cheminformatics","category-python-code","category-small-molecules"],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"authors":[{"term_id":701,"user_id":102,"is_guest":0,"slug":"nuben","display_name":"Matteo Ferla","avatar_url":"https:\/\/secure.gravatar.com\/avatar\/2185fc527a4ace0863c903d27646996994413c31d80e8e4c175477b836e7715c?s=96&d=mm&r=g","0":null,"1":"","2":"","3":"","4":"","5":"","6":"","7":"","8":""}],"_links":{"self":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/9983","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/users\/102"}],"replies":[{"embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/comments?post=9983"}],"version-history":[{"count":4,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/9983\/revisions"}],"predecessor-version":[{"id":10125,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/posts\/9983\/revisions\/10125"}],"wp:attachment":[{"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/media?parent=9983"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/categories?post=9983"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/tags?post=9983"},{"taxonomy":"author","embeddable":true,"href":"https:\/\/www.blopig.com\/blog\/wp-json\/wp\/v2\/ppma_author?post=9983"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}