1414from fairmofsyncondition .read_write import filetyper , coords_library
1515from fairmofsyncondition .call_model .utils_model import get_models , ensemble_predictions
1616
17+ smile_to_iupac = filetyper .smile_names_iupac ()
1718
1819convert_struct = {'cubic' : 0 ,
1920 'hexagonal' : 1 ,
2627
2728device = "cpu"
2829
29- def get_ligand_iupacname (ligand_inchi ):
30+ def get_ligand_iupacname (ligand_inchi , smiles ):
3031 name = load_iupac_names ().get (ligand_inchi , None )
31- print ("tesst" , name )
3232
3333 if name is None :
34- pubchem = cheminfo2iupac .pubchem_to_inchikey (ligand_inchi , name = 'inchikey' )
34+ name = smile_to_iupac .get (smiles , None )
35+ if name is None :
36+ pubchem = cheminfo2iupac .pubchem_to_inchikey (smiles , name = 'smiles' )
3537 if pubchem is None :
36- name = ligand_inchi
38+ name = None
3739 else :
38- pubchem .get ('iupac_name' , ligand_inchi )
40+ name = pubchem .get ('iupac_name' , None )
3941 return name
4042
4143class Synconmodel (object ):
4244 def __init__ (self ,
4345 ase_atom ,
44- outfile = "mof_prediction.txt" ,
45- module = "best_model.pth" ,
46+ outfile = "mof_prediction.txt"
4647 ):
4748 """
4849 A class to run the metal salt prediction GNN model and for
4950 extracting organic ligands and predicting metal salts.
5051 """
5152 if isinstance (ase_atom , Atoms ):
5253 self .ase_atom = ase_atom
54+ self .basename = 'MOF Data'
5355 elif isinstance (ase_atom , str ) and os .path .isfile (ase_atom ):
5456 self .ase_atom = read (ase_atom )
57+ self .basename = os .path .basename (ase_atom ).split ('.' )[0 ]
5558 else :
5659 print ("sorry input type is not recorgnised" )
5760 self .outfile = outfile
58- self .gnn_model = module
5961 self .structure = MOFstructure (self .ase_atom )
6062 self .pymat = AseAtomsAdaptor .get_structure (self .ase_atom )
6163 self .torch_data = coords_library .ase_to_pytorch_geometric (self .ase_atom )
@@ -103,11 +105,12 @@ def get_coordination_and_oms(self):
103105 return emb , oms , metals
104106
105107 def get_organic_ligands (self ):
108+ ligands_data = {}
106109 _ , ligands = self .structure .get_ligands ()
107110 inchikeys = [ligand .info .get ('inchikey' ) for ligand in ligands ]
108- ligands_names = [get_ligand_iupacname ( i ) for i in inchikeys ]
109- print ( ligands_names )
110-
111+ smiles = [ligand . info . get ( 'smi' ) for ligand in ligands ]
112+ ligands_names = [ get_ligand_iupacname ( i , j ) for i , j in zip ( inchikeys , smiles )]
113+ return inchikeys , smiles , ligands_names
111114
112115 def get_space_group (self ):
113116 "Get space group embedding"
@@ -118,13 +121,13 @@ def get_space_group(self):
118121 emb_sg [space_group_number ] = 1
119122 get_crystal_system = sga .get_crystal_system ()
120123 emb_cs [convert_struct [get_crystal_system ]] = 1
121- return emb_sg , emb_cs
124+ return emb_sg , emb_cs , space_group_number , get_crystal_system
122125
123126 def complete_torch_data (self ):
124127 '''
125128 Get torch data
126129 '''
127- emb_sg , emb_cs = self .get_space_group ()
130+ emb_sg , emb_cs , _ , _ = self .get_space_group ()
128131 cn_emb , oms , metals = self .get_coordination_and_oms ()
129132 atom_conc = self .get_species_conc ()
130133 self .torch_data .atomic_one_hot = atom_conc
@@ -134,27 +137,124 @@ def complete_torch_data(self):
134137 self .torch_data .oms = torch .tensor ([[oms ]], dtype = torch .float )
135138 return self .torch_data .to (device ), metals
136139
137-
138140 def predict_condition (self ):
139141 '''
140142 predict conditions
141143 '''
142144 torch_data , metals = self .complete_torch_data ()
143145 torch_data = torch_data .to (device )
144- torch_data .cordinates = torch_data .cordinates [0 ] # small cleaning
145- models = get_models (torch_data , device = device ) # load models (5 seeds)
146+ torch_data .cordinates = torch_data .cordinates [0 ]
147+ models = get_models (torch_data , device = device )
146148 models = models [0 :1 ]
147149
148150 category_names = filetyper .category_names ()["metal_salts" ]
149- # the function to get ensemble predictions, i.e. the average probability for each class over the 5 models
150151 pred_list = ensemble_predictions (models , torch_data , category_names , device = device )
151-
152- for name , prob in pred_list [:10 ]:
153- print (f"{ name } : { prob :.3f} " )
152+ # for name, prob in pred_list[:10]:
153+ # print(f"{name}: {prob:.3f}")
154154
155155 #print(pred_list)
156+ return pred_list
157+
158+ def compile_data (self ):
159+ data = []
160+ inchikeys , smiles , ligands_names = self .get_organic_ligands ()
161+ _ , _ , space_group_number , get_crystal_system = self .get_space_group ()
162+ pred_list = self .predict_condition ()[:5 ]
163+
164+ data .append ("\n " )
165+ data .append (f"Predicted Synthetic Data Report\n " )
166+ data .append (f"For: { self .basename } \n " )
167+ data .append ("=" * 80 + "\n " )
168+ data .append (f"{ 'Space group number:' :25} { space_group_number } \n " )
169+ data .append (f"{ 'Crystal system:' :25} { get_crystal_system } \n \n " )
170+
171+
172+ data .append ("Organic Ligands\n " )
173+ data .append ("-" * 80 + "\n " )
174+ data .append (f"{ 'InChIKey' :<28} { 'SMILES' :<30} { 'IUPAC Name' :<20} \n " )
175+ data .append ("-" * 80 + "\n " )
176+ for inchi , smi , iupac in zip (inchikeys , smiles , ligands_names ):
177+ inchi_str = str (inchi ) if inchi is not None else "N/A"
178+ smi_str = str (smi ) if smi is not None else "N/A"
179+ iupac_str = str (iupac ) if iupac is not None else "N/A"
180+ data .append (f"{ inchi_str :<28} { smi_str :<30} { iupac_str :<20} \n " )
181+ data .append ("\n " )
182+
183+
184+ data .append ("Top 5 Predicted Metal Salts\n " )
185+ data .append ("-" * 80 + "\n " )
186+ data .append (f"{ 'Metal Salt' :<40} { 'Probability' :>15} \n " )
187+ data .append ("-" * 80 + "\n " )
188+ for metal_salt , prob in pred_list :
189+ salt_str = str (metal_salt ) if metal_salt is not None else "N/A"
190+ prob_str = f"{ prob :.4f} " if prob is not None else "N/A"
191+ data .append (f"{ salt_str :<40} { prob_str :>15} \n " )
192+ data .append ("=" * 80 + "\n " )
193+
194+ data .append ("\n " )
195+ data .append ("Report generated by fairmofsyncondition\n " )
196+ data .append ("Authors: Dinga Wonanke & Antonio Longa\n " )
197+ data .append ("=" * 80 + "\n " )
198+
199+
200+ filetyper .put_contents (self .outfile , data )
201+
202+
203+ def print_helpful_information ():
204+ '''
205+ Prints helpful information about using the fairmofsyncondition script.
206+ '''
207+ help_text = """
208+ Usage: fairmofsyncondition.py [CIF_FILE] [OPTIONS]
209+
210+ This script predicts synthetic conditions for MOFs given a CIF file
211+ or any ase readable fileformat.,
212+ extracts organic ligand information, space group, and top predicted
213+ metal salts, and writes a formatted report to a text file.
214+
215+ Positional Arguments:
216+ CIF_FILE Path to the input CIF file.
217+
218+ Optional Arguments:
219+ -o, --output FILE The path to the output report file (default: prediction_report.txt).
220+
221+ Examples:
222+ fairmofsyncondition_syncon my_mof.cif -o my_mof_report.txt
223+ fairmofsyncondition_syncon ../data/sample.cif
224+ """
225+ print (help_text )
226+
227+ def main ():
228+ parser = argparse .ArgumentParser (
229+ description = "Predict synthetic conditions for MOFs from CIF files."
230+ )
231+ parser .add_argument (
232+ 'cif_file' ,
233+ type = str ,
234+ nargs = '?' ,
235+ default = None ,
236+ help = 'Path to the CIF file to analyze.'
237+ )
238+ parser .add_argument (
239+ '-o' , '--output' ,
240+ type = str ,
241+ default = 'mof_prediction_report.txt' ,
242+ help = 'Path to the output report file (default: prediction_report.txt).'
243+ )
244+
245+ args = parser .parse_args ()
246+
247+ if args .cif_file is None :
248+ print_helpful_information ()
249+ sys .exit (1 )
156250
251+ try :
252+ ml_data = Synconmodel (args .cif_file )
253+ ml_data .outfile = args .output
254+ ml_data .compile_data ()
255+ except Exception as e :
256+ print (f"Error: { e } " )
257+ sys .exit (1 )
157258
158- ml_data = Synconmodel ('../../tests/test_data/EDUSIF.cif' )
159- # print(ml_data.predict_condition())
160- print (ml_data .get_organic_ligands ())
259+ if __name__ == '__main__' :
260+ main ()
0 commit comments