11"""
2- build_properties.py — measured boiling point / vapor pressure from PubChem.
2+ build_properties.py — measured properties + identity from PubChem.
33
4- PubChem experimental properties are **public domain** (no commercial restriction), so
5- this is a commercial-clean source. It pulls each molecule's experimental Boiling Point
4+ PubChem experimental properties AND compound identity are **public domain** (no commercial
5+ restriction), so this is a commercial-clean source. For each molecule it pulls the common
6+ name (Title) and IUPAC name from the property endpoint, plus the experimental Boiling Point
67(and, best-effort, Vapor Pressure) from PUG-View, parses to °C / Pa, and writes
7- properties.parquet — the lookup table predict.py reads for MEASURED volatility. We use
8- a measured lookup here on purpose: structure-based BP (Joback) was evaluated and
9- rejected (~90 °C error), so a number is only ever reported when it's a real measurement.
8+ properties.parquet — the enrichment table predict.py reads for MEASURED volatility and the
9+ demo reads for instant offline names. We use a measured lookup for BP on purpose: structure-
10+ based BP (Joback) was evaluated and rejected (~90 °C error), so a number is only ever
11+ reported when it's a real measurement. Names default to live PubChem in the app; precomputing
12+ them here makes the substitution / palette / neighbor reads instant for the whole labeled set.
1013
1114Usage:
1215 python build_properties.py # default: taste_master.parquet
@@ -125,12 +128,21 @@ def parse_vp_pa(strings):
125128 return round (vals [len (vals ) // 2 ], 2 ) if vals else None
126129
127130
131+ def _names_for_cid (cid ):
132+ """(common Title, IUPAC) from PubChem's property endpoint — public-domain identity."""
133+ d = _get (f"{ _BASE } /pug/compound/cid/{ cid } /property/Title,IUPACName/JSON" )
134+ p = ((d or {}).get ("PropertyTable" , {}).get ("Properties" ) or [{}])[0 ]
135+ return p .get ("Title" ), p .get ("IUPACName" )
136+
137+
128138def fetch_props (inchikey ):
129139 cid = _cid (inchikey )
130140 if not cid :
131- return None , None , None
141+ return None , None , None , None , None
132142 bp , bp_press = parse_bp (_heading_strings (cid , "Boiling Point" ))
133- return bp , bp_press , parse_vp_pa (_heading_strings (cid , "Vapor Pressure" ))
143+ vp = parse_vp_pa (_heading_strings (cid , "Vapor Pressure" ))
144+ common , iupac = _names_for_cid (cid )
145+ return bp , bp_press , vp , common , iupac
134146
135147
136148def load_keys (path ):
@@ -165,9 +177,10 @@ def load_keys(path):
165177 print (f"{ smi } : unparseable" )
166178 continue
167179 ik = Chem .MolToInchiKey (mol )
168- bp , bp_press , vp = fetch_props (ik )
180+ bp , bp_press , vp , common , iupac = fetch_props (ik )
169181 cond = f" @{ bp_press } mmHg" if bp_press else ""
170- print (f"{ smi :32s} ik={ ik } bp_c={ bp } { cond } vp_pa={ vp } " )
182+ print (f"{ smi :32s} ik={ ik } name={ common !r} iupac={ iupac !r} "
183+ f"bp_c={ bp } { cond } vp_pa={ vp } " )
171184 time .sleep (0.3 )
172185 sys .exit (0 )
173186
@@ -177,19 +190,25 @@ def load_keys(path):
177190 sys .exit (1 )
178191 keys = load_keys (src )
179192 rows = []
193+ cols = ["inchikey" , "common_name" , "iupac_name" , "boiling_point_c" ,
194+ "boiling_point_pressure_mmhg" , "vapor_pressure_pa" ]
180195 for i , ik in enumerate (keys ):
181- bp , bp_press , vp = fetch_props (ik )
182- if bp is not None or vp is not None :
183- rows .append ({"inchikey" : ik , "boiling_point_c" : bp ,
184- "boiling_point_pressure_mmhg" : bp_press , "vapor_pressure_pa" : vp })
196+ bp , bp_press , vp , common , iupac = fetch_props (ik )
197+ if bp is not None or vp is not None or common or iupac :
198+ rows .append ({"inchikey" : ik , "common_name" : common , "iupac_name" : iupac ,
199+ "boiling_point_c" : bp , "boiling_point_pressure_mmhg" : bp_press ,
200+ "vapor_pressure_pa" : vp })
185201 if (i + 1 ) % 50 == 0 :
186- print (f" { i + 1 } /{ len (keys )} processed, { len (rows )} with measured props" )
202+ named = sum (1 for r in rows if r ["common_name" ])
203+ print (f" { i + 1 } /{ len (keys )} processed, { len (rows )} enriched ({ named } named)" )
187204 time .sleep (0.3 )
188- new = pd .DataFrame (rows , columns = ["inchikey" , "boiling_point_c" ,
189- "boiling_point_pressure_mmhg" , "vapor_pressure_pa" ])
205+ new = pd .DataFrame (rows , columns = cols )
190206 if Path (a .out ).exists (): # accumulate across molecule sets
191207 old = pd .read_parquet (a .out )
192208 new = pd .concat ([old , new ], ignore_index = True ).drop_duplicates ("inchikey" , keep = "last" )
209+ new = new .reindex (columns = cols ) # keep a stable schema as older tables get names
193210 new .to_parquet (a .out )
194- print (f"{ a .out } : { len (new )} molecules total with measured BP/VP "
211+ named = int (new ["common_name" ].notna ().sum ())
212+ print (f"{ a .out } : { len (new )} molecules total (names: { named } , "
213+ f"BP: { int (new ['boiling_point_c' ].notna ().sum ())} ) "
195214 f"(+{ len (rows )} from { src } ; public-domain PubChem data)" )
0 commit comments