def test_sanitize_mol_multiple_conformers_no_warning(caplog): # Generate a mol with props and a conformer smiles = "CCC[N+](=O)[O-]" mol = dm.to_mol(smiles) mol = dm.conformers.generate(mol, n_confs=10) # Check no warning log dm.sanitize_mol(mol, verbose=False) assert caplog.text == ""
def all_fragment_on_bond(mol, asMols=False, max_num_action=float("Inf"), break_aromatic=True): """Fragment all possible bond in a molecule and return the set of resulting fragments This is similar to `random_bond_cut`, but is not stochastic as it does not return a random fragment but all the fragments resulting from all potential bond break in the molecule. .. note:: This will always be a subset of all_bond_remove, the main difference being that all_bond_remove, allow decreasing bond count, while this one will always break a molecule into two. Args: mol: <Chem.Mol> input molecule asMols: bool, optional Whether to return results as mols or smiles max_num_action: float, optional Maximum number of action to reduce complexity break_aromatic: bool, optional Whether to attempt to break even aromatic bonds (Default: True) Returns: set of fragments """ mol.GetRingInfo().AtomRings() fragment_set = set([]) bonds = list(mol.GetBonds()) stop = False if bonds: if break_aromatic: Chem.Kekulize(mol, clearAromaticFlags=True) for bond in bonds: if stop: break if break_aromatic or not bond.GetIsAromatic(): truncate = Chem.FragmentOnBonds(mol, [bond.GetIdx()], addDummies=False) truncate = dm.sanitize_mol(truncate) if truncate is not None: for frag in rdmolops.GetMolFrags(truncate, asMols=True): frag = dm.sanitize_mol(frag) if frag: if not asMols: frag = dm.to_smiles(frag) fragment_set.add(frag) if len(fragment_set) > max_num_action: stop = True break return fragment_set
def test_sanitize_mol_multiple_conformers_warning(caplog): # Generate a mol with props and a conformer smiles = "CCC[N+](=O)[O-]" mol = dm.to_mol(smiles) mol = dm.conformers.generate(mol, n_confs=10) # Check warning log dm.sanitize_mol(mol) assert "WARNING" != caplog.text assert ( "The molecule contains multiple conformers. Only the first one will be preserved." in caplog.text)
def recap( mol: Chem.Mol, remove_parent: bool = False, sanitize: bool = True, fix: bool = True, ): """Fragment the molecule using the recap algorithm. Args: mol: a molecule. remove_parent: Remove parent from the fragments. sanitize: Wether to sanitize the fragments. fix: Wether to fix the fragments. """ res = Recap.RecapDecompose(mol) frags = [dm.to_mol(x) for x in res.GetAllChildren().keys()] if fix: frags = [dm.fix_mol(x) for x in frags] if sanitize: frags = [dm.sanitize_mol(x) for x in frags] frags = [x for x in frags if x is not None] if remove_parent: return frags return [mol] + frags
def frag( mol: Chem.Mol, remove_parent: bool = False, sanitize: bool = True, fix: bool = True, ): """Generate all possible fragmentation of a molecule. Args: mol: a molecule. remove_parent: Remove parent from the fragments. sanitize: Wether to sanitize the fragments. fix: Wether to fix the fragments. """ frags = FraggleSim.generate_fraggle_fragmentation(mol) smiles = set([]) for seq in frags: smiles |= {s.strip() for s in seq.split(".")} smiles = list(sorted(smiles, reverse=True)) frags = [dm.to_mol(s) for s in smiles] if fix: frags = [dm.fix_mol(x) for x in frags] if sanitize: frags = [dm.sanitize_mol(x) for x in frags] frags = [x for x in frags if x is not None] if remove_parent: return frags return [mol] + frags
def brics( mol: Chem.Mol, singlepass: bool = True, remove_parent: bool = False, sanitize: bool = True, fix: bool = True, ): """Run BRICS on the molecules and potentially fix dummy atoms. Args: mol: a molecule. singlepass: Single pass for `BRICSDecompose`. remove_parent: Remove parent from the fragments. sanitize: Wether to sanitize the fragments. fix: Wether to fix the fragments. """ frags = BRICS.BRICSDecompose(mol, returnMols=True, singlePass=singlepass) frags = list(frags) if fix: frags = [dm.fix_mol(x) for x in frags] if sanitize: frags = [dm.sanitize_mol(x) for x in frags] if remove_parent: frags.pop(0) frags = [x for x in frags if x is not None] return frags
def update_bond(mol, bond, bond_type): """Update bond type between atoms""" new_mol = dm.copy_mol(mol) with dm.without_rdkit_log(): new_bond = new_mol.GetBondWithIdx(bond.GetIdx()) new_bond.SetBondType(bond_type) return dm.sanitize_mol(new_mol)
def _preprocess(i, row): # print('hello') mol = dm.to_mol(str(row[smiles_column]), ordered=True) mol = dm.fix_mol(mol) mol = dm.sanitize_mol(mol, sanifix=True, charge_neutral=False) mol = dm.standardize_mol(mol, disconnect_metals=False, normalize=True, reionize=True, uncharge=False, stereo=True) fingerprint_function = rdMolDescriptors.GetMorganFingerprintAsBitVect pars = { "radius": 2, "nBits": 8192, "invariants": [], "fromAtoms": [], "useChirality": True, "useBondTypes": True, "useFeatures": False, } fp = fingerprint_function(mol, **pars) row["standard_smiles"] = dm.standardize_smiles(dm.to_smiles(mol)) row["selfies"] = dm.to_selfies(mol) row["inchi"] = dm.to_inchi(mol) row["inchikey"] = dm.to_inchikey(mol) row["onbits_fp"] =list(fp.GetOnBits()) return row
def _compute_fragment_join( mol, fragment, mol_atom_count, bond_between_rings=True, asMols=True, ): """List all posibilities of where a fragment can be attached to a mol""" fragment = copy.copy( fragment ) # need to copy the fragment copy is faster than all the other methods with dm.without_rdkit_log(): combined = Chem.CombineMols(mol, fragment) for i1 in range(mol.GetNumAtoms()): a1 = combined.GetAtomWithIdx(i1) if a1.GetImplicitValence() == 0: continue for i2 in range(fragment.GetNumAtoms()): i2 += mol_atom_count a2 = combined.GetAtomWithIdx(i2) if a2.GetImplicitValence() == 0: continue # no bond between atoms already in rings if not bond_between_rings and a1.IsInRing() and a2.IsInRing(): continue # no bond to form large rings else: possibilities = _all_atom_join(combined, a1, a2) for x in possibilities: x = dm.sanitize_mol(x) if x is not None: if not asMols: x = dm.to_smiles(x) yield x
def _run_at_all_rct(rxn, mol1, mol2): library = [] rxn = rdChemReactions.ReactionFromSmarts(rdChemReactions.ReactionToSmarts(rxn)) # display(rxn) m1 = rxn.GetReactantTemplate(0) m2 = rxn.GetReactantTemplate(1) mol1_valid = mol1 is not None mol2_valid = mol2 is not None isR1 = mol1_valid and mol1.HasSubstructMatch(m1) isR2 = mol1_valid and mol1.HasSubstructMatch(m2) if isR1 and mol2_valid and mol2.HasSubstructMatch(m2): library.extend(rxn.RunReactants((mol1, mol2))) if isR2 and mol2_valid and mol2.HasSubstructMatch(m1): library.extend(rxn.RunReactants((mol2, mol1))) if library: library = list(itertools.chain(*library)) for m in library: mol = None mSmi = "" try: mSmi = Chem.MolToSmiles(m) mol = dm.to_mol(mSmi) except: pass if mol is None: try: mol.UpdatePropertyCache() mol = dm.sanitize_mol(mol) mSmi = Chem.MolToSmiles(m) mol = dm.to_mol(mSmi) except: pass if mSmi: yield mol, mSmi
def test_sanitize(): smiles = "CC(=O)Oc1ccccc1C(=O)O" mol = dm.to_mol(smiles, sanitize=False) mol = dm.sanitize_mol(mol, charge_neutral=True) assert dm.to_smiles(mol) == "CC(=O)Oc1ccccc1C(=O)O" mol = dm.sanitize_mol(None, charge_neutral=True) assert mol is None smiles_list = ( "CC.[H][N:1]1(C)=CC(O)=CC2CCCCC12", # broken "O=c1ccc2ccccc2n1", # sanitize "Cc1nnnn1C", # none "CCc1ccc2nc(=O)c(cc2c1)Cc1nnnn1C1CCCCC1", # sanitize "c1cnc2cc3ccnc3cc12", # none "c1cc2cc3ccnc3cc2n1", # none "O=c1ccnc(c1)-c1cnc2cc3ccnc3cc12", # sanitize "O=c1ccnc(c1)-c1cc1", # broken ) # check sanitize_mol assert dm.to_mol(smiles_list[1]) is None assert dm.to_mol(smiles_list[2]) is not None assert dm.sanitize_mol(None) is None assert dm.sanitize_mol(dm.to_mol(smiles_list[0], sanitize=False)) is None assert dm.sanitize_mol(dm.to_mol(smiles_list[1], sanitize=False)) is not None assert dm.sanitize_mol(dm.to_mol(smiles_list[2], sanitize=False)) is not None mol_2 = dm.sanitize_mol(dm.to_mol(smiles_list[1], sanitize=False)) assert dm.to_smiles(mol_2) == dm.sanitize_smiles("O=c1ccc2ccccc2[nH]1") fixed_smiles = [dm.sanitize_smiles(smiles) for smiles in smiles_list] assert len([x for x in fixed_smiles if x is not None]) == 6
def compute_reaction_product(out, single_output=True): """Compute the product of a reaction""" out = [dm.fix_mol(x[0], n_iter=0) for x in out] if not single_output: return [dm.sanitize_mol(x) for x in out] # Might be a important to make a tradeoff decision in selecting products for greater speed. # product = sorted(out, key=lambda x: MoleculeEnv.compute_reward_from_mol(x, True))[-1] # sampling from list of products is an alternative return dm.sanitize_first(np.random.permutation(out))
def _preprocess(i, row): # print('hello') try: mol = dm.to_mol(str(row[smiles_column]), ordered=True) mol = dm.fix_mol(mol) mol = dm.sanitize_mol(mol, sanifix=True, charge_neutral=False) mol = dm.standardize_mol(mol, disconnect_metals=False, normalize=True, reionize=True, uncharge=False, stereo=True) opts = StereoEnumerationOptions(unique=True, maxIsomers=20, rand=0xf00d) isomers = EnumerateStereoisomers(mol, options=opts) enum_smiles = sorted( Chem.MolToSmiles(y, isomericSmiles=True) for y in isomers) smiles_list = [] for count, smi in enumerate(enum_smiles): smiles_string = smi smiles_list.append(smiles_string) # fingerprint_function = rdMolDescriptors.GetMorganFingerprintAsBitVect # pars = { "radius": 2, # "nBits": 8192, # "invariants": [], # "fromAtoms": [], # "useChirality": False, # "useBondTypes": True, # "useFeatures": False, # } # fp = fingerprint_function(mol, **pars) row["standard_smiles"] = dm.standardize_smiles(dm.to_smiles(mol)) row["selfies"] = dm.to_selfies(mol) row["inchi"] = dm.to_inchi(mol) row["inchikey"] = dm.to_inchikey(mol) row["enumerated_smiles"] = smiles_list # row["onbits_fp"] =list(fp.GetOnBits()) return row except ValueError: row["standard_smiles"] = 'dropped' row["selfies"] = 'dropped' row["inchi"] = 'dropped' row["inchikey"] = 'dropped' row["enumerated_smiles"] = list('dropped') return row
def all_transform_apply( mol, rxns, max_num_action=float("Inf"), asMols=True, **kwargs, ): """ Apply a transformation defined as a reaction from a set of reaction to the input molecule. The reaction need to be one reactant-only Arguments ---------- mol: <Chem.Mol> Input molecule rnxs: list list of reactions/ reaction smarts max_num_action: int, optional Maximum number of result to return (Default: inf) asMols: bool, optional Whether to return smiles or mols Returns ------- Products obtained from applying the chemical reactions """ mols = set([]) with dm.without_rdkit_log(): for rxn in rxns: if len(mols) >= max_num_action: break if isinstance(rxn, str): rxn = AllChem.ReactionFromSmarts(rxn) try: pcdts = [products[0] for products in rxn.RunReactants([mol])] pcdts = [dm.sanitize_mol(x) for x in pcdts] mols.update([dm.to_smiles(x) for x in pcdts if x]) except: pass mols = [x for x in mols if x is not None] if np.isfinite(max_num_action): mols = mols[:max_num_action] mols = [dm.to_mol(x) for x in mols] if not asMols: mols = [dm.to_smiles(x) for x in mols if x is not None] return mols
def all_atom_add( mol, atom_types=["C", "N", "O", "F", "Cl", "Br"], asMols=True, max_num_action=float("Inf"), **kwargs, ): """Add a new atom on the mol, by considering all bond type .. warning:: This is computationally expensive Args: mol: <Chem.Mol> Input molecule atom_types: list List of atom symbol to use as replacement (Default: ["C", "N", "O", "F", "Cl", "Br"]) asMols: bool, optional Whether to return output as molecule or smiles max_num_action: float, optional Maximum number of action to reduce complexity Returns: All possible molecules with one additional atom added """ new_mols = [] stop = False with dm.without_rdkit_log(): for atom in mol.GetAtoms(): if stop: break if atom.GetImplicitValence() == 0: continue for atom_symb in atom_types: emol = Chem.RWMol(mol) new_index = emol.AddAtom(Chem.Atom(atom_symb)) emol.UpdatePropertyCache(strict=False) new_mols.extend( _all_atom_join(emol, atom, emol.GetMol().GetAtomWithIdx(new_index))) if len(new_mols) > max_num_action: stop = True break new_mols = [dm.sanitize_mol(mol) for mol in new_mols] new_mols = [mol for mol in new_mols if mol is not None] if not asMols: return [dm.to_smiles(x) for x in new_mols if x] return new_mols
def all_atom_replace(mol, atom_types=["C", "N", "S", "O"], asMols=True, max_num_action=float("Inf"), **kwargs): """Replace all non-hydrogen atoms by other possibilities. .. warning:: This is computationally expensive Args: mol: <Chem.Mol> Input molecule atom_types: list List of atom symbol to use as replacement (Default: ['C', 'N', 'S', 'O']) asMols: bool, optional Whether to return output as molecule or smiles max_num_action: float, optional Maximum number of action to reduce complexity Returns: All possible molecules with atoms replaced """ new_mols = [] stop = False with dm.without_rdkit_log(): for atom in mol.GetAtoms(): if stop: break if atom.GetAtomicNum() > 1: for atom_symb in atom_types: emol = Chem.RWMol(mol) emol.ReplaceAtom(atom.GetIdx(), Chem.Atom(atom_symb)) new_mols.append(emol) if len(new_mols) > max_num_action: stop = True break # Sanitize and remove bad molecules new_mols = [dm.sanitize_mol(mol) for mol in new_mols] new_mols = [mol for mol in new_mols if mol is not None] if not asMols: # Return SMILES return [dm.to_smiles(x) for x in new_mols] return new_mols
def test_sanitize_mol_keep_props_and_conformers(): # Generate a mol with props and a conformer props = dict(test_int=588, test_str="hello") smiles = "CCC[N+](=O)[O-]" mol = dm.to_mol(smiles) mol = dm.set_mol_props(mol, props) mol = dm.conformers.generate(mol, n_confs=1) pos = mol.GetConformer().GetPositions() # Sanitize sane_mol = dm.sanitize_mol(mol) # Check properties assert sane_mol.GetPropsAsDict() == props # Check conformer conf = sane_mol.GetConformer() assert sane_mol.GetNumConformers() == 1 assert conf.Is3D() np.testing.assert_almost_equal(conf.GetPositions(), pos, decimal=4)
def sanitize_smiles(smiles: str, isomeric: bool = True) -> Optional[str]: """Takes SMILES string and returns its sanitized version. Args: smiles: smiles to be sanitized. isomeric: Whether to include information about stereochemistry in the SMILES. Returns: sanitized smiles. """ try: mol = dm.to_mol(smiles, sanitize=False) mol = dm.sanitize_mol(mol, False) except Exception: return None if mol is None: return None try: smiles = dm.to_smiles(mol, isomeric=isomeric) # type: ignore except: return None return smiles
def _preprocess(i, row): '''Takes a smiles string and generates a clean rdkit mol with datamol. The stereoisomers are then enumerated while holding defined stereochemistry. Morgan fingerprints are then generated using RDkit with and without stereochemistry. The try/except logic deals with RDkit mol failures on conversion of an invalid smiles string. Smarts are added for later searching.''' try: mol = dm.to_mol(str(row[smiles_column]), ordered=True) mol = dm.fix_mol(mol) mol = dm.sanitize_mol(mol, sanifix=True, charge_neutral=False) mol = dm.standardize_mol(mol, disconnect_metals=False, normalize=True, reionize=True, uncharge=False, stereo=True) opts = StereoEnumerationOptions(unique=True,maxIsomers=20,rand=0xf00d) isomers = EnumerateStereoisomers(mol, options=opts) enum_smiles = sorted(Chem.MolToSmiles(y,isomericSmiles=True) for y in isomers) # enum_dm_smiles = sorted(dm.standardize_smiles(dm.to_smiles(x)) for x in isomers) smiles_list = [] achiral_fp_lis = [] chiral_fp_lis = [] # standard_smiles_list = [] for count, smi in enumerate(enum_smiles): smiles_string = smi mol = dm.to_mol(smi, ordered=True) mol = dm.fix_mol(mol) mol = dm.sanitize_mol(mol, sanifix=True, charge_neutral=False) mol = dm.standardize_mol(mol, disconnect_metals=False, normalize=True, reionize=True, uncharge=False, stereo=True) fingerprint_function = rdMolDescriptors.GetMorganFingerprintAsBitVect pars = { "radius": 2, "nBits": 8192, "invariants": [], "fromAtoms": [], "useChirality": True, "useBondTypes": True, "useFeatures": False, } pars2 = { "radius": 2, "nBits": 8192, "invariants": [], "fromAtoms": [], "useChirality": False, "useBondTypes": True, "useFeatures": False, } fp = fingerprint_function(mol, **pars) fp1 = fingerprint_function(mol, **pars2) smiles_list.append(dm.standardize_smiles(smiles_string)) achiral_fp_lis.append(list(fp1.GetOnBits())) chiral_fp_lis.append(list(fp.GetOnBits())) row["standard_smiles"] = dm.standardize_smiles(dm.to_smiles(mol)) row["smarts"] = dm.to_smarts(mol) row["selfies"] = dm.to_selfies(mol) row["enumerated_smiles"] = smiles_list row["achiral_fp"] = achiral_fp_lis row["chiral_fp"] = chiral_fp_lis # row["dm_enumerated_smiles"] = enum_dm_smiles_lis # row["onbits_fp"] =list(fp.GetOnBits()) return row except ValueError: # row["standard_smiles"] = 'dropped' # row["selfies"] = 'dropped' # row["inchi"] = 'dropped' # row["inchikey"] = 'dropped' row["standard_smiles"] = 'dropped' row["smarts"] = 'dropped' row["selfies"] = 'dropped' row["enumerated_smiles"] = list('dropped') row["achiral_fp"] = list('dropped') row["chiral_fp"] = list('dropped') # row["dm_enumerated_smiles"] = 'dropped' return row
def add_bond_between(mol, a1, a2, bond_type): """Add a new bond between atom""" emol = Chem.EditableMol(mol) emol.AddBond(a1.GetIdx(), a2.GetIdx(), bond_type) return dm.sanitize_mol(emol.GetMol())
def break_mol( mol: Chem.Mol, minFragmentSize: int = 1, silent: bool = True, onlyUseReactions: list = [], randomize: bool = False, mode: str = "brics", returnTree: bool = False, ): """Breaks a molecules into a list of fragment.""" if mode.lower() == "brics": all_reactions = ALL_BRICS all_reactions_type = ALL_BRICS_TYPE elif mode.lower() == "rxn": all_reactions = ALL_RXNS all_reactions_type = ALL_RXNS_TYPE else: all_reactions = ALL_BRICS + ALL_RXNS all_reactions_type = ALL_BRICS_TYPE + ALL_RXNS_TYPE if randomize: p = np.random.permutation(len(all_reactions)) all_reactions = [all_reactions[ind] for ind in p] all_reactions_type = [all_reactions_type[ind] for ind in p] nx = dm.graph._get_networkx() mSmi = Chem.MolToSmiles(mol, isomericSmiles=True) G = nx.DiGraph() node_num = 0 G.add_node(node_num, smiles=mSmi, mol=mol) allNodes = set() activePool = {mSmi: node_num} allNodes.add(mSmi) while activePool: nSmi = list(activePool.keys())[0] parent = activePool.pop(nSmi) node = G.nodes[parent] mol = node["mol"] for rxnIdx, reaction in zip(all_reactions_type, all_reactions): if onlyUseReactions and rxnIdx not in onlyUseReactions: continue ps = reaction.RunReactants((mol,)) if ps: all_pass = [ all([prod.GetNumAtoms(onlyExplicit=True) > minFragmentSize for prod in p_]) for p_ in ps ] nz_i = 0 while nz_i < len(all_pass) and not all_pass[nz_i]: nz_i += 1 if not silent: print(nSmi, "->", len(ps), "products and selected ", nz_i) # display(MolsToGridImage(list(itertools.chain(*list(ps))), molsPerRow=2)) prodSeq = ps[nz_i % len(all_pass)] seqOk = True # we want to disqualify small fragments, so sort the product sequence by size prodSeq = [(prod.GetNumAtoms(onlyExplicit=True), prod) for prod in prodSeq] prodSeq.sort(key=lambda x: x[0]) for _, prod in prodSeq: prod.sanitized = True try: Chem.SanitizeMol(prod) except: if dm.sanitize_mol(prod) is None: seqOk = False break continue pSmi = Chem.MolToSmiles(prod, isomericSmiles=True) seqOk = seqOk and (dm.to_mol(pSmi) is not None) notDummies = sum([atm.GetSymbol() != "*" for atm in prod.GetAtoms()]) # nDummies = pSmi.count('*') # if minFragmentSize > 0 and (nats - nDummies < minFragmentSize): if minFragmentSize > 0 and notDummies < minFragmentSize: seqOk = False break prod.pSmi = pSmi if seqOk: for _, prod in prodSeq: if not prod.sanitized: continue pSmi = prod.pSmi node_num += 1 usmi = Chem.MolToSmiles(dm.fix_mol(prod), isomericSmiles=True) G.add_node(node_num, smiles=usmi, mol=prod) G.add_edge(parent, node_num) if usmi not in allNodes: activePool[pSmi] = node_num allNodes.add(usmi) G.nodes[parent]["rxn"] = rxnIdx break # at least one reaction matches leaves_smiles = [ G.nodes[n]["smiles"] for n in G.nodes() if G.in_degree(n) != 0 and G.out_degree(n) == 0 ] if returnTree: return leaves_smiles, allNodes, G return leaves_smiles, allNodes
def all_bond_remove( mol: Chem.rdchem.Mol, as_mol: bool = True, allow_bond_decrease: bool = True, allow_atom_trim: bool = True, max_num_action=float("Inf"), ): """Remove bonds from a molecule Warning: This can be computationally expensive. Args: mol: Input molecule allow_bond_decrease: Allow decreasing bond type in addition to bond cut max_num_action: Maximum number of action to reduce complexity allow_atom_trim: Allow bond removal even when it results in dm.SINGLE_BOND Returns: All possible molecules from removing bonds """ new_mols = [] try: Chem.Kekulize(mol, clearAromaticFlags=True) except: pass for bond in mol.GetBonds(): if len(new_mols) > max_num_action: break original_bond_type = bond.GetBondType() emol = Chem.RWMol(mol) emol.RemoveBond(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()) new_mol = dm.sanitize_mol(emol.GetMol()) if not new_mol: continue frag_list = list(rdmolops.GetMolFrags(new_mol, asMols=True)) has_single_atom = any([x.GetNumAtoms() < 2 for x in frag_list]) if not has_single_atom or allow_atom_trim: new_mols.extend(frag_list) if allow_bond_decrease: if original_bond_type in [dm.DOUBLE_BOND, dm.TRIPLE_BOND]: new_mol = update_bond(mol, bond, dm.SINGLE_BOND) if new_mol is not None: new_mols.extend( list(rdmolops.GetMolFrags(new_mol, asMols=True))) if original_bond_type == dm.TRIPLE_BOND: new_mol = update_bond(mol, bond, dm.DOUBLE_BOND) if new_mol is not None: new_mols.extend( list(rdmolops.GetMolFrags(new_mol, asMols=True))) new_mols = [mol for mol in new_mols if mol is not None] if not as_mol: return [dm.to_smiles(x) for x in new_mols if x] return new_mols