diff --git a/.gitignore b/.gitignore index 0cd3d8b..4820502 100644 --- a/.gitignore +++ b/.gitignore @@ -211,10 +211,8 @@ __marimo__/ *.tsv *.parquet *.csv -*.png -*.json -*.owl -*.html # Results folder -!results +results/* +!results/FFM +!results/SOFTNAME diff --git a/README.md b/README.md index d64b404..b1bb9b6 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,8 @@ # mdverse_entity_norm +This project implements the normalization pipeline for molecular dynamics (MD) simulation metadata entities. Normalization standardizes entity mentions by mapping them to controlled vocabularies or reference databases (e.g., [ChEBI](https://www.ebi.ac.uk/chebi/), [PubChem](https://pubchem.ncbi.nlm.nih.gov/), [KEGG](https://www.genome.jp/kegg/)), ensuring consistency and interoperability across datasets. +Normalisation is currently supported for four entity types: molecule names (MOL),simulation times (STIME) and temperatures (STEMP), software names (SOFTNAME) and force fields and models (FFM). + ## Setup environment We use [uv](https://docs.astral.sh/uv/getting-started/installation/) to manage dependencies and the project environment. @@ -19,92 +22,107 @@ uv sync ## Usage -This project implements the normalisation pipeline for molecular dynamics simulation metadata entities. Normalisation is currently supported for four entity types: temperature, small molecules, simulation times, and software versions (not yet implemented). The scripts are located in `src/mdverse_entity_norm/scripts/` and can be executed independently. Output files are saved in the `results/` directory, which is created automatically if it does not exist. +The following scripts require an `entities.tsv` file as input. The file should contain the columns `entity`, `category`, and `json_file`. + +### Simulation temperature (STEMP) -### Normalize temperature +To normalize simulation temperatures, run: ```sh -uv run src/mdverse_entity_norm/scripts/normalize_temperature.py +uv run src/mdverse_entity_norm/scripts/normalize_stemp.py --raw-entities-path data/entities.tsv --normalized-stemp-path results/STEMP/stemp_normalized.tsv ``` -This reads temperature entities from `data/entities.tsv` and writes `results/norm_temp.tsv`, a TSV file with four columns: +This reads temperature entities from `data/entities.tsv` and writes `results/STEMP/stemp_normalized.tsv`, a TSV file with four columns: -| Column | Description | -|---|---| -| `raw_temperature` | Original temperature string | -| `normalised_temperature` | Numeric value after normalisation | -| `normalised_unit` | Unit after normalisation (Kelvin) | -| `normalized_result` | Concatenated value and unit | +| raw_temperature | normalised_temperature | normalised_unit | normalized_result | +| --------------- | ---------------------- | --------------- | ----------------- | +| 315 | 315 | K | 315 K | +| 20°C | 293,15 | K | 293,15 K | +| 310k | 310 | K | 310 K | -Special cases `room temperature` and `human body temperature` are normalised to 293 K and 310 K respectively. All Celsius values are converted to Kelvin. +> Special cases `room temperature` and `human body temperature` are normalised to 293 K and 310 K respectively. All Celsius values are converted to Kelvin. -### Ground molecules +### Simulation times (STIME) -The grounding logic is illustrated below: +The normalization of simulation times is a two-step process: first, we benchmark several candidate Large Language Models (LLMs) against a gold standard dataset to select the best performer; second, we deploy the chosen model to normalize the entire dataset. -![Grounding logic](molecules_grounding_logic.png) +> 🔑 An `OPEN_ROUTER_KEY` environment variable must be set (e.g., via a .env file) to authenticate and authorise API requests to the external LLM providers hosted on OpenRouter. + +#### Model evaluation: + +To evaluate candidate LLM models on a labelled gold standard, run: ```sh -uv run src/mdverse_entity_norm/scripts/normalize_molecules.py +uv run src/mdverse_entity_norm/scripts/evaluate_llm_models.py \ + --groundtruth-path data/groundtruth/STIME.json \ + --prompt-path data/llm_prompt.txt \ + --runs 10 \ + --model-evaluation-path results/STIME/model_evaluation.tsv ``` -This reads molecular entities from `data/entities.tsv`. Entities are first classified by type (PDB, UniProt, DNA, RNA, protein, or small molecule). PDB and UniProt entries are resolved via their respective APIs and saved to `results/ground_molecule/same_grounding_mol/pdb_uniprot_seq_entities.tsv`. Small molecules are grounded by consensus across ChEBI, PubChem, and KEGG, producing two output files: - -**`chebi_comparaison.tsv`** — ChEBI grounding results for all small molecules: +This script benchmarks 9 models accessible via OpenRouter (including `GPT-4o`, `DeepSeek V4 Pro`, and `Claude 4.7 Opus`) against a manually annotated gold standard of 100 simulation time entities. The evaluation is repeated over the specified number of runs to ensure statistical robustness. -| Column | Description | -|---|---| -| `Molecule` | Original molecule name | -| `CHEBI_ID` | ID returned directly by ChEBI | -| `CHEBI_ID_from_KEGG` | ChEBI ID resolved via KEGG | -| `CHEBI_ID_from_PubChem` | ChEBI ID resolved via PubChem synonyms | -| `Match` | `True` if at least two sources agree | +The evaluation results across the tested models are detailed below: -**`pubchem_comparaison_no_chebi_match.tsv`** — PubChem fallback for molecules with no ChEBI consensus: +| model_name | accuracy_percentage (%) | normalisation_times_sec (s) | normalisation_cost (USD/entity) | +| ---------------------------------- | ----------------------: | --------------------------: | ------------------------------: | +| openai/gpt-5.5 | 99 | 1.65 | 0.0028 | +| qwen/qwen3.6-27b | 99 | 18.80 | 0.0012 | +| minimax/minimax-m2.7 | 99 | 10.72 | 0.0096 | +| anthropic/claude-opus-4.7 | 98 | 2.85 | 0.0013 | +| **deepseek/deepseek-v4-pro** | **97** | **8.39** | **0.0022** | +| openai/gpt-4o | 95 | 1.26 | 0.0016 | +| mistralai/mistral-large-2512 | 90 | 4.19 | 0.0001 | +| moonshotai/kimi-k2.6 | 89 | 28.17 | 0.0002 | +| google/gemma-4-31b-it | 62 | 2.44 | 0.0002 | -| Column | Description | -|---|---| -| `Molecule` | Original molecule name | -| `PubChem_ID` | ID returned directly by PubChem | -| `PubChem_ID_from_KEGG` | PubChem ID resolved via KEGG | -| `Match` | `True` if both sources agree | -### Normalize simulation times +#### Entity normalization: -Two scripts are involved: one evaluates candidate LLM models on a labelled gold standard, the other applies the selected model to the full dataset. +Based on these results, **DeepSeek V4 Pro** was selected as the optimal open-weight model, offering the best balance between high accuracy (97%), reasonable latency, and cost efficiency. -**Model evaluation:** +To apply this model and normalize the entire dataset, run: ```sh -uv run src/mdverse_entity_norm/scripts/normalize_simulation_time.py \ - --ground_truth_file data/STIME_ground_truth.json \ - --runs 10 \ - --model_evaluation_file results/norm_simu_times/model_evaluation.tsv +uv run src/mdverse_entity_norm/scripts/normalize_stime_wth_llm.py --entities-path data/entities.tsv --normalization-results-path results/STIME/stime_normalized.tsv --prompt-path data/llm_prompt.txt --model-name "deepseek/deepseek-v4-pro" ``` -This benchmarks 9 models via OpenRouter (including GPT-4o, DeepSeek V4 Pro, Claude Opus 4.7, and others) on a manually annotated gold standard of 100 simulation time entities, repeated over the specified number of runs. Results are saved to the file specified by `--model_evaluation_file`: +This processes all raw STIME entities and outputs a three-column TSV with the standardized values and units: + +| STIME | value | unit | +| ------------- | ----: | :--: | +| 1 μs | 1.0 | μs | +| 1 microsecond | 1.0 | μs | +| 200-300ns | 200.0 | ns | +| 200-300ns | 300.0 | ns | -| Column | Description | -|---|---| -| `model_name` | Model identifier | -| `accuracy_percentage` | Average accuracy across runs (%) | -| `normalisation_times_sec` | Average processing time per entity (s) | -| `normalisation_cost` | Average cost per entity (USD) | +### Ground molecule names -> An `OPEN_ROUTER_KEY` environment variable must be set (e.g. via a `.env` file) for API access. +The grounding logic is illustrated below: -**Entity normalisation:** +![Grounding logic](plots/molecules_grounding_logic.png) ```sh -uv run src/mdverse_entity_norm/scripts/normalize_stime_results.py \ - --entities-file data/entities.tsv \ - --output-file results/norm_simu_times/normalized_stime_results.tsv +uv run src/mdverse_entity_norm/scripts/normalize_molecules.py ``` -This applies DeepSeek V4 Pro to all STIME entities in the input file and writes a TSV with three columns: +This reads molecular entities from `data/entities.tsv`. Entities are first classified by type (PDB, UniProt, DNA, RNA, protein, or small molecule). PDB and UniProt entries are resolved via their respective APIs and saved to `results/ground_molecule/same_grounding_mol/pdb_uniprot_seq_entities.tsv`. Small molecules are grounded by consensus across ChEBI, PubChem, and KEGG, producing two output files: + +**`chebi_comparaison.tsv`** — ChEBI grounding results for all small molecules: + +| Column | Description | +| ------------------------- | -------------------------------------- | +| `Molecule` | Original molecule name | +| `CHEBI_ID` | ID returned directly by ChEBI | +| `CHEBI_ID_from_KEGG` | ChEBI ID resolved via KEGG | +| `CHEBI_ID_from_PubChem` | ChEBI ID resolved via PubChem synonyms | +| `Match` | `True` if at least two sources agree | + +**`pubchem_comparaison_no_chebi_match.tsv`** — PubChem fallback for molecules with no ChEBI consensus: -| Column | Description | -|---|---| -| `STIME` | Original simulation time string | -| `LLM_value` | Normalised numeric value | -| `LLM_unit` | Normalised unit (`ps`, `ns`, `μs`, `ms`, or `s`) | +| Column | Description | +| ------------------------ | ------------------------------- | +| `Molecule` | Original molecule name | +| `PubChem_ID` | ID returned directly by PubChem | +| `PubChem_ID_from_KEGG` | PubChem ID resolved via KEGG | +| `Match` | `True` if both sources agree | diff --git a/data/FFM.txt b/data/FFM.txt deleted file mode 100644 index 3c263f6..0000000 --- a/data/FFM.txt +++ /dev/null @@ -1,97 +0,0 @@ -charmm36 -martini -amber -tip3p -charmm -gaff -berger -opc -slipids -spc -charmm36m -gromos -gromos 54a7 -trappe -amber99sb-ildn -amoeba+ -gafflipid -gal17 -amber99sb-ildb -c36m -c36mcu -charmm c36 -charmm27 -charmm36 (v. june 2015) -general amber force field -gromos 43a1-s3 -gromos87 -opc3 -opls-aa -rsff2 -sirah -williams 7b -amber bsc1 -amber ff03ws -amber ff14sb -amber lipid17 -c22* -c36m -charmm c36 -compass -ecc-ions -ecc-lipids -ecc-popc -ff99sb-ildn-nmr -ff99sb-ildn-phi -gaff2 -martini 2 -mpipi -opls -poger gromos 53a6_l -pys -williams -'berger -amber ff99sb -amber ff99sb-ildn -amber14 -amber14sb -amoeba -autodock -bind3p -c36m. -charmm 27 -charmm36 forcefield (version july 2020) -charmm36m. -charmm36mw -dang -ff03 -ff03* -ff03w -ff96 -ff99 -ff99sb* -ff99sb-ildn -gap -gdml -generalized amber force field -gromos 53a6 -gromos54a7 -l-opls -lipid14 -lipid17 -martini 2.2p -martini 3 -martini 3.0 -martini cg -opls-aa/m -parm99_lna -parmbsc0 -q4md-cd -rsff1 -schnet -sl -tip -tip4p-ew -tip4p/2005 -trappe-ua -βol15 diff --git a/data/MOL.txt b/data/MOL.txt deleted file mode 100644 index 0dc449c..0000000 --- a/data/MOL.txt +++ /dev/null @@ -1,1113 +0,0 @@ -popc -cholesterol -dppc -nacl -dmtap -dmpc -dopc -na+ -dna -meth -tcr -tlr4 -ace2 -calcium -h-ns -pope -cacl_2 -hno3 -atp -chat -keap1 -md-2 -phospholipid -ppsa -stp1 -actin -fus -gr -h2o -ikk -methane -phosphatidylcholine -cetp -cl- -dlpc -dmso -e2 -e6 -glycans -gq -graphene -msmo -omps -pazepc -pc -pima -ritonavir -shk -vdac1 -acyl -at1r -c11r6 -cellulose -d-serine -e3 -n-c28 -osc -p-ftaa -plp -pmhc -popg -sars-cov-2 spike -tpr -ub -arylamide -cl -cobalt -dimyristoylphosphatidylcholine -dimyristoyltrimethylammoniumpropane -furanose -gara -gpi -kerogen -kv1.3 -mcp -onooh -p53 -pa -pah -pfm1-aap -quercetin -rbd -rutin -tbp -tio2 -ubiquinone -acetonitrile -aβ42 -b-raf -bnnt -bromodomains -chitosan -corilagin -cps -diterpenes -dopamine -entf* -fp-2 -gerab -gtp -hydrogen sulfide -ind -kerogens -lbf14 -lna -mebendazole -methanol -nd -neon -pahs -pedot -pgla -pi -pim -pkng -poly-ubiquitin -qgr motif -rsme -sars-cov-2 spike -septin -sirt2 -teixobactin -ubiquitin -βcd -adp -alkyl -alpo-11 -aβ40 -carbohydrates -cationic lipid -chloroxylenol -chs -epoxy resins -glutamate -hmk -kcl -kit -l-alanine -lopinavir -mannose -mercaptopurine -metal -mgl -ncp -phosphate -pknb -protac -s -saq -spike protein -surfactants -t cell receptor -tetracycline -4a8 -aac -alanine dipeptide -alkane -alkanes -antibody -at-rich dna -benenodin-1 -caprolactam -carbohydrate -ceftazidime -ceramide -chl -congocidine -coronavirus 2019-ncov protease -cr3022 -ctab -depc -dgeba -diterpene -dlipc -e6ap -eea1 -ethanol -exbox4+ -fyve -glucoside -glutamine synthetase -glycoproteins -gsh -gssg -h-fbp21 -hsp90 -hyp -k-ras4b -kinase -lanosterol -lasso peptide -ligand -lysine -lysozyme -mt1 -nds -nh3+ -nrf2 -nucleosome -oh -oxa-163 -oxa-48 -oxyacids -pas -phospholipids -phosphonium -poly-ubiquitins -protacs -rnv66 -rsmz -sars-cov-2 spike protein -sodium -tcrs -tempo -triclosan -urea -zeolite -zeolites -2-iodomelatonin -2-methoxy group -2-tp -3g5u -4ksb -4m1m -4zud -act -agos -aldehydes -antibodies -at-rich dna -bptf -ca -cacl -carbon -carbon-based nanomaterials -carbonyl -cephalosporinase -chalcogen -chol -com -cov2 -cu -cu+ -cx50 -cytochrome c -dag -divalent cations -dlipc -dmc -dme -dspc -dspe -elastin -epoxy resins -ethyl acetate -fa2 -fdts -flavonoid -fmoc -fmoc-aa -gaba -gd3+ -gdp -gdpm -glp-1r -glucose -glutamine -glycerol -glycine receptor -glycolipids -gm1 -go -graphene oxide -h2 -hdm2 -hkv1 -hp -hsmo -hypd -k+ -kras -lanthanide -laptm4b -late endosomal protein -methacycline -methyl -mg -mrsa -na -nanodiamonds -nitrogen oxyanions -no2 -no3 -nps -ots -p-6p -p-glycoprotein -palmitoyl-coa -pdms -peg -perfluorinated alcohols -pfo-bpy -phosphoinositides -pkc -pops -potassium -protease -pyr-1gp -qb site -quinones -rubisco -sars-cov-2 -sds -sirt2 inhibitors -smoothened -sp-b -sp-c -spa -superoxide dismutase 1 -tapc -thiolate -thrombin -titanium dioxide -tricaproin -tris-benzimidazole -v7t1 -vegf -y198n -y204f -1,2-dimyristoyl-sn-glycero-3-phosphocholine -1-palmitoyl-2-oleoyl-sn-glycero-3-phosphocholine -17l -29l -33tfncs -6lu7 -6msm -6vsb -8arg -ace2. -acidic zeolites -ag -aggac -ago -air -amanitin -aminobutyric acid -amph -amyloid -amyloid beta -amyloid fibrils -ansamer amanitin -apoe2 -arylamides -aryldiazonium -au1 -aunp -austdiol -azelaoyl -azobioisostere -barnase -barstar -bax -benzene -betaine -bh4 -biliverdin ix -biocides -bok -boron nitride -btk -bv -c15 -carbapenems -carbon dioxide -carboxyl -cellulose acetate -cholesteryl ester transfer protein -choline acetyltransferase -chromatin -chromonic mesogen -chromophore -cnt -co2 -cr -csra -cx46 -cyclic peptides -cyclodextrin -cytochrome c -dapc -darunavir -dehydratase -dialanine -dihexadecylammonium -dimeric protease -dioleoyl-phosphatidylcholine -dipeptides -dma -dna aptamer -dna gq -dnpc -dodecan-1-ol -e. coli apo -ec -egg-white lysozyme -electrolyte -emre -en2 -erk -ether-o -ethylene -fa1b -fdh -formate dehydrogenase -gaba transporter bgt1 -gasdermin-d -gera -glycan -glycerol -glycosylphosphatidylinositol -gp120 -gppnhp -gqs -grs -gsk3β inhibitors -hematoporphyrin -hiv inhibitors -hiv protein -hkii-n -hmi-1a3 -human receptor ace2 -hydrocarbon -ibuprofen -indinavir -ion -ions -ire1 -kinesin -kras-4b -lam -lanosterol -lasr -lasso peptides -li+ -lipf6 -lm -lnas -macrophage migration inhibitory factor -malate dehydrogenase -mannose -mcl-1 -mdm2 -mea -metalloenzyme -methamphetamine -mfi -montmorillonite -mt1 receptor -mt2 -mt2 receptor -mucinases -n-3-oxododecanoyl homoserine lactone -n-alkanes -n-butylpyridinium tetrafluoroborate -n-dodecane -n1-methylpseudouridine -n6-methyladenosine -ncs -nelfinavir -nh4+ -nitrile butadiene -np -ntd -o-glycan -octaose -octapeptide -olefin-c -orexin receptor -orexin-a -p-divinylbenzene -palmitate -para-sexiphenyl -pcs -pedot:pss -peptides -periplasmic chaperone -peroxynitrous acid -ph-btbt-c10 -phe -phenyl -phenylalanine hydroxylase -phenylalanine-4-hydroxylase -phenytoin -phla -phosphatidylethanolamine -phosphoinositol phosphate -phospholipid translocase -pic -pnipaam -polyamine -polydimethylsiloxane -polyleucine -polystyrene -pop1 -popcs -porphyrins -ppis -pss -pupc -pyranose -pyridinylimidazoles -pyrimidoindole -saquinavir -sars-cov-2 spike protein -sbls -sept2 -sept7 -septins -serine/threonine phosphatase -silf -sod1 -sol -stearoyl-coa desaturase -sty -styrene -superoxide dismutase 1 -tcr-pmhc -tempo-pc -thermolysin -thiq -tipranavir -tma -transcriptional regulator -tri-n-butyl phosphate -tyrosine kinase -ubisemiquinone -unguisin a -villin headpiece -vrc01 -xla -xyloglucan -zwitterionic dmpc -α-conotoxin lsia -α3β2 nachr -(s)-norcoclaurine -1,2-diauroyl-sn-glycero-3-phospocholine -1,2-dioleoyl-sn-glycero-3-phosphocholine -1,2-dipalmitoyl-sn-glycero-3-phospcholine -1,2-dipalmitoyl-sn-glycero-3-phosphatidylcholine -1,4-distyrylbenzene -1-palmitoyl-2-oleoyl-sn-glycero-3-phosphoethanolamine -1afo -1fpy -1h,1h-perfluorinated alcohols -1s,2s)-trans-1,2-cyclohexanediol -2,4,6-trimethylbenzenediazonium -2-mercaptopyridine -2-mp -2-phenylpropane -2-thiopyridone -2019ncov virus protease -2hno3 -2kf7 -2m0b -2mesadp -2ojw -2rjy -3,4-ethylenedioxythiophene -3-hydroxy-4-pyridinone -3bei -3eml -3emn -3lu9 -4efl -4hkr -4ptc -4yay -5e11 -5’-gcggcgcgccgc-3’ -6aki -6bbf -6gn1 -6hyc -6m0j -6o3c -6ssz -6xr8 -7cel -8-aryl -a-amanitin -aaaqaaqaqwaqrqatwqa -abeta -abeta42 -abietic acid -ace2 receptor -acetyl -acryl -activator/regulator complexin -acyclic 2,3-oxidosqualene -aggax -alcohols -alkaloid -alkaloids -alkylsilane -alkylsilanes -am0627 -amide -amide-i -amino acid -amphetamine -amphiliphic ligands -amphiphilic np -amphiphilic nps -amps -angiotensin converting enzyme ii -angiotensin-converting enzyme 2 -anionic ubisemiquinone -anions -anti-sars-cov-2 -antibody fab -apo -apo rnases h -aptamer -arylindenols -arylurea derivatives -aspartic acid -at-rich -atad2 -atmospheric aerosols -axggax -az -az-ab42 -azobioisostere-abeta42 -aβ peptides -b-dna -b-raf inhibitors -bacterioferritin -basket-type dna g-quadruplex -basket-type g-quadruplex -baz2b -bcl-2 -benzodiazepinedione -benzylisoquinoline -berberine -beta lactamases -bisphosphates -bn -bo3 -borohydride -boron nitride nanotube -bptu -bpy1,4-tfsi-li -brd4 bd2 -bruton's tyrosine kinase -bt4244 -c-h -c16 -ca2+ -ca2+-sensor synaptotagmin -calix-[4]-arenes -calix[n]arenes -carbapenemase -carbon nanomaterial -carbon nanomaterials -carbon nanotube -carbonic anhydrase ii -carbonyl oxygen -carboxynaphthalene disulfide -cardiac troponin c -cardiac troponin protein -carotene -cathepsins -cations -cd-nbd -cdcl3 -cefoperazone -cellobiose -cephalosporin -ceramides -cetyl -cetyltrimethylammonium bromide -cftr -cg protein -cha -chabazite -chignolin -chl1 -chloride -chloride counterions -chloroform -chlorophyll a -cholesterols -cholesteryl hemisuccinate -choline chloride -chromophores -chromosomes -clathrate hydrate -clathrate hydrates -clr -collagen -connexin 46 -connexin 50 -connexin-46/50 -coo -cooh -copper -counter ions -covid2 spike -covid2spike -crac channel -crac ion channel -crd -crebbp -ctn -cu(111) -cul3 -cullin3 -curcumin azobioisostere -cyanine -cyclophane -cyclophanes -cysteine -cyt -cyt bo3 -cytochrome -cytochrome bo3 -d alpo- -d-c14-pc -d-c18-pc -d33e -dabrafenib -dec -destabilized superoxide dismutase 1 -di-, tri- and tetra-ubiquitin -di-ubiquitin -diacylglycerol -dianions -diethyl carbonate -dihydrate -dimeric main protease -dimethoxyethane -dimethyl carbonate -dimethyl sulfoxide -dimethylallyl disphosphate -dioctylfluorene -dipeptides -diphenyl diselenide -diphenyl diselenides -diphenyl ditellurides -dmf5 -dna g-quadruplex -dna gqs -dodecyl -dope -doxyl-labeled lipids -dppe -dpsm -dsdna -dtab -e-chalcone 48 -e. coli cytoplasm -e. coli ompf -e. coli ribonuclease h -e1 -e3 ligase -ec48 -ed alpo -elusive arenium -epoxy resin -ethanolamine -ethene -ethyl -ethylene carbonate -etidocaine -exendin-4 -exendin-p5 -fa2b -factor h -falcipain-2 -falcipain‑2 -fau -faujasite -fe -fe(ii) -fluorenylmethoxycarbonyl -fluorine -fluoroalkylsilane -fluorophore -fmoc-dipeptide -folic -folinic -formate -fused in sarcoma -g protein -g1-pamam -gag-hiv1 -gc-analogue sequence -gd -gdp-mannose -gelsolin -geraa -gerac -germinant receptors -gl1 -glp-1 -glu-35 -glu-37 -glucagon-like peptide receptor -glucocorticoids -glucosidases -glycerol trihexanoate -glycine -glycogen accumulation regulator -glycopeptides -glycosyl -gold nanoparticles -gpcrs -gpis -graphene oxides -gs protein -gtpase -gva -h-ras -h2s -h3k27ac -h3k9ac -hbgt1 -heat shock protein 90 -heat-shock protein 90 -heme -heme b -hexane -hexokinase-ii -hexylthiophene -histone -hiv-1 gag protein -hkii -hno2 -hp2y1r -human and e. coli thioredoxins -human cathepsins -human formin-binding protein 21 -human lysozyme -human smoothened -human thioredoxin -hydrogenated alcohols -hydronium -hydroperoxides -hydroxyl -hydroxymethyl -igg4 antibody -imidazole -indenols -isopentenyl diphosphate -isophthalate -k+ channel -kelch-like ech-associated protein-1 -ketones -l-valine -l1 -leubethanol -li-/na-ion -lipid acyl -lipoarabinomannan -lipoglycans -lipomannan -liposome -lnx2 -locked nucleic acid -lys-353 -lysines -m -malarial m1 aminopeptidase -maldh -maltopentose -mapk -mart1 -mart1 peptide-major histocompatibility complexes -mdia2 -mek -melatonin receptors -meoh -metal ion -metal ions -methicillin-resistant staphylococcus aureus -methionine -methotrexate -methylene -methyltriphenylphosphonium bromide -micelle -micelles -mif -mif180 -mil-101 -mitochondrial anion channel -mln-4760 -monoethanolamine -monoethanolamines -morphine -mrnas -mrs2268 -mrs2500 -myeloid differentiation protein 2 -n,n-dimethylformamide -n-alkane -n-doxyl -n-doxyl pc -n-hexadecane -n-isopropylacrylamide -n-methyl-d-aspartate receptors -n-octacosane -n-octane -n-pentadecane -na2s2o3 -nag26 -nanocellulose -nanodiamond -ncps -nf-e2-related factor 2 -nf-κb -nh2 -nitric acid -nitrogen -nmda receptor -nmdars -non-structural (ns) proteins 1 -norcoclaurine synthases -ns1 -ns1 zikv -ns2b-ns3 -ns2b-ns3 dengue protease -ns2b-ns3pro -nutlin-2 -nwasp -octyl trimethyl ammonium bromide -oleoyl -oligopeptide -oligophenylenes -oligophenylenevinylene -oligosaccharide -oligothiophene -ompf -oncoprotein -organic semiconductors -otab -ox2 receptor -oxa-48 β‑lactamases -oxidosqualene cyclase -oxygen -oxyntomodulin -p-loop ntpases -p-polyphenylene -p01116-2 -p0dtc2 -p2y1 receptor -pa phosphate -palmitoyl-coa. -palmitoyl-coenzyme a -papain -para-sulfonated calix-[4]-arenes -para-sulfonato-calix[4]arenes -pc phosphate -pc-lipid -pd2l4 -pentameric formyl thiophene acetic acid -pentane -pep-synthase -peptide-major histocompatibility complex -peptidyl -peroxidase -peroxynitrite -phas -phenylalanine -pheophytin -phosophoinositides -phosphates -phosphatidyl-myo-inositol -phosphatidyl-myo-inositol mannosyltransferase a -phosphatidylcholines -phosphatidylglycerol -phosphatidylinositol bisphosphate -phosphatidylinositol hexamannosides -phosphatidylinositol phosphate -phosphocholine -phosphoenolpyruvate synthase -phosphoinositol -pim6 -pims -plasmodial cysteine protease -plasmodium falciparum m1 alanyl aminopeptidase -plastoquinol -plastoquinone -plx7904 -pmhcs -pnipaam-co-acrylamide -pnipaam-co-dimethylacrylamide -poly(n‑isopropylacrylamide) -poly-phosphatidylinositols -polycyclic aromatic hydrocarbons -polyethylene -polyproline ii -polypropylene -polysaccharide -polystyrene sulfonate -popc acyl -precambrian enzymes -precambrian proteins -pristine carbon -pristine graphene -propane -propanol -propylene carbonate -proteasome -protegens -protein kinase b -protein kinase c -protein kinase g -proteinogenic amino acids -proteolysis-targeting chimera -proteolysis-targeting chimeras -pseudoisocyanine -pyrimidine -quaterphenylene dicarbonitriles -raf -receptor binding domain -rhamnose -rna -rnase -rnase h -s protein -s1 nuclease -saccharides -sars-cov-2 dimeric main protease -sars-cov-2 main protease -sars-cov-2 mpro -sars-cov-2 rbd -sars-cov-2 receptor-binding domain -sars-cov2 spike -sclareol -sd1 -sd2 -sdpc -se -selenium -sept6 -serine -serine β-lactamases -serine/threonine kinases -siap -sio2 -sirtuin 2 -sn-2 stearoyl -snares -socony mobil-5 -sodium dodecyl sulfate -sodium thiosulfate -soluble n-ethylmaleimide sensitive factor attachment protein receptor -sopc -sorafenib -spike -staphylococcal nuclease -sterol -sucrose -sugar monomers -t4 lysozyme -tba15 -te -ted alp -tellurium diphenyl dichalcogenides -terephthalate -terpenoid -tetra-alanine -tetrabutylammonium halides -tetracyclic lanosterol -tetrahydrofuran -tetrahydroisoquinoline -tetramethylammonium -tetrapyrrole chromophore -tfncs -thalictrum flavum ncs -thiqs -threonine -tk-hefu -toll-like receptor 4 -trans-4-hydroxy-l-proline -transmembrane domains -trehalose -triglyceride -tripeptides -tropocollagen -trypsin inhibitor -tryptathionine -ubiquinol -ubiquitin conjugating enzymes -ubiquitin e3 ligase -ubiquitin ligase -urea -uridine -vascular endothelial growth factor -vemurafenib -walp23 -x-linked agammaglobulinemia -zirconium nitrate -zn -zwitterionic -α-methyl -α-methyl-substituted aldehydes -α‑amylase -β-cyclodextrin -β-lactam -β-lactamase -β-lactamases -β‑cyclodextrin diff --git a/data/SOFTNAME.txt b/data/SOFTNAME.txt deleted file mode 100644 index 08c1107..0000000 --- a/data/SOFTNAME.txt +++ /dev/null @@ -1,78 +0,0 @@ -gromacs -lammps -amber -vmd -charmm-gui -lassohtp -probis -charmming -espresso++ -plumed -ambertools -openmm -avis -cgenff-webserver -mdanalysis -modeller -moe -namd -packmol -ani2x -colabfold -cp2k -haddock -amber12 -antechamber -ase -autodock -bumpy -colvars -desmond -gaussian -gaussian09 -gromacs-ls -leap -mypresto -nemo -omegagene -pydock -swiss-model -torchani -acemd -aimd -alphafold -charmm -chimerax -colvar -cpptraj -deepmd-kit -doglycans -flexaid -gaussian16 -gibbs -glide -glycam -glycam-web -hoomd-blue -insane -intermol -jupyter -lipidbook -maestro -molpro -molsim -namd2 -optim -paramchem -pathsample -pmemd -prmtop -prody -propka -pyinteraph -pymol -rdock -rosetta -swissprot -unitymol -visual molecular dynamics diff --git a/data/SOFTVERS.txt b/data/SOFTVERS.txt deleted file mode 100644 index c7c06c1..0000000 --- a/data/SOFTVERS.txt +++ /dev/null @@ -1,59 +0,0 @@ -4.5 -3.x -4.0.7 -16 -5.0.3 -5.1.4 -2020 -5.0.3 -(v. 2016.4) -16 -19 -2016.1 -2018.3 -2020.4 -21 -4 -5.1.1 -7 -(dec 2018, stable) -(v. 2020) -(v4.6) -1.9.3 -16 rev b01 -2.0 -2.11b2 -2.6.3 -2016.2-dev-20170105-4feb0be -2016.3 -2018 -2018.0 -2018.1 -2019 -2019.3 -2020.6 -2021 -2021.4 -2023.2 -3.3.3 -4.5.6 -4.6 -4.6.5 -5.0.6 -5.0.x -5.1 -5.1.5 -5.1.x -molecular dynamics -simulation engine v5 -simulation engine version 2019.4 -software (v. 2016.4) -stable dec. 2018 -tools -v.4.6.5 -v2.4 -v4.6 -v9.16 -version 2.6.0 -version 4.6 -vina diff --git a/data/STIME.txt b/data/STIME.txt deleted file mode 100644 index 040874f..0000000 --- a/data/STIME.txt +++ /dev/null @@ -1,88 +0,0 @@ -500ns -100ns -200 ns -microsecond -500 ns -1 microsecond -200ns -100 ns -110 ns -1500 ns -300 ns -315.0 k -1 μs -10 microseconds -1000 ns -2 microseconds -250 ns -3.0 us -317.0 k -319.1 k -5 μs -50 ns -550ns -700ns -us -0.5 μs -0.6 -0.633 us -0.79 ns -1 ns -1 µs -1.0 us -1.5 micro-sec -1.6 ns -1.6 μs -10 ns -10 µs -10 μs -100 -100 nanoseconds -100–200 ns -1069 ns -1080 ns -11 ns -12 μs -120ns -125 ns -167ns -190 ns -20 -20 ns -200-300ns -27ns -298k -3 μs -30 ns -300 k -300 nanoseconds -3000 ns -300ns -316.7 k -318.4 k -320.1 k -321.8 k -36 μs -360 ns -370 ns -380 ns -4.1 us -40 ns -40ns -4200 ns -450 ns -5-microsecond -500 ps -5000 ns -500ps -50ns -600 ns -60ns -6microseconds -6µs -8 microseconds -800 ns -87 microsecond -multi-microsecond -nanosecond -one hundred nanosecond diff --git a/data/TEMP.txt b/data/TEMP.txt deleted file mode 100644 index 0e6896b..0000000 --- a/data/TEMP.txt +++ /dev/null @@ -1,53 +0,0 @@ -310k -310 k -300 k -298 k -323 k -325k -288k -303k -323k -358k -298.15 -313 k -283.15 -298.15 k -318.15 k -320k -338k -363 k -130 k -200 -210 k -225 k -242 -2500 -273.15 k -285 -293 k -293.15 -295 k -298 -298k -3000 -300k -303 -310 -313.15 k -315 -315k -316.7 k -318.4 k -320 -321.8 k -322k -333 k -338 k -340 k -3500 -360 k -363k -365 k -4000 k -473.15 k -5000 k diff --git a/data/figshare_8046437.json b/data/figshare_8046437.json deleted file mode 100644 index 7c3b02b..0000000 --- a/data/figshare_8046437.json +++ /dev/null @@ -1,74 +0,0 @@ -{ - "classes": [ - "SOFTNAME", - "SOFTVERS", - "STIME", - "MOL", - "FFM", - "TEMP" - ], - "raw_text": "Short molecular dynamics of a peptide inside a pure DMPC membrane\n1 ns of molecular dynamics simulation of a 19-residue peptide inside a pure DMPC membrane, performed at the NPT ensemble - 1 atm at 310K using Berendsen (semi-isotropic) and V-rescale for pressure and temperature coupling, respectively, with 1.6 ps and 0.1ps as coupling constants. Van der Waals interactions were treated using the Verlet algorithm with a 1 nm cutoff. Electrostatics where treated with particle-mesh Ewald, with a 1 nm cutoff for the real space calculations. Both the peptide and DMPC were parameterized using the GROMOS 54A7, while SPC was used for water. LINCS restraints were used on all the bonds. Peptide sequence is AAAQAAQAQWAQRQATWQA. This sequence was not taken from any real world examples, as this is a test system created for MDAnalysis datasets. The peptide was set to have be α-helical secondary structure, and was inserted manually in the membrane before minimization and equilibration. More on MDAnalysis: https://www.mdanalysis.org/", - "entities": [ - { - "label": "MOL", - "text": "DMPC", - "start": 52, - "end": 56 - }, - { - "label": "STIME", - "text": "1 ns", - "start": 66, - "end": 70 - }, - { - "label": "MOL", - "text": "DMPC", - "start": 142, - "end": 146 - }, - { - "label": "TEMP", - "text": "310K", - "start": 198, - "end": 202 - }, - { - "label": "MOL", - "text": "DMPC", - "start": 563, - "end": 567 - }, - { - "label": "FFM", - "text": "GROMOS 54A7", - "start": 597, - "end": 608 - }, - { - "label": "FFM", - "text": "SPC", - "start": 616, - "end": 619 - }, - { - "label": "MOL", - "text": "AAAQAAQAQWAQRQATWQA", - "start": 705, - "end": 724 - }, - { - "label": "SOFTNAME", - "text": "MDAnalysis", - "start": 821, - "end": 831 - }, - { - "label": "SOFTNAME", - "text": "MDAnalysis", - "start": 993, - "end": 1003 - } - ], - "url": "https://figshare.com/articles/dataset/Short_molecular_dynamics_of_a_peptide_inside_a_pure_DMPC_membrane/8046437" -} diff --git a/data/TEMP_ground_truth.json b/data/groundtruth/STEMP.json similarity index 100% rename from data/TEMP_ground_truth.json rename to data/groundtruth/STEMP.json diff --git a/data/STIME_ground_truth.json b/data/groundtruth/STIME.json similarity index 100% rename from data/STIME_ground_truth.json rename to data/groundtruth/STIME.json diff --git a/data/zenodo_1009027.json b/data/zenodo_1009027.json deleted file mode 100644 index 5184ba5..0000000 --- a/data/zenodo_1009027.json +++ /dev/null @@ -1,284 +0,0 @@ -{ - "classes": [ - "SOFTNAME", - "SOFTVERS", - "STIME", - "MOL", - "FFM", - "TEMP" - ], - "raw_text": "Simulations DPPC bilayers (512 lipids) using charmm36 ff in gromacs\nCollection simulations of DPPC (512 lipids) bilayers in gromacs using the charmm36 force field.  Several temperatures between 315 and 338 K are included. The list of systems can be found below where the several parameter are:\n1) DPPC_512_NaCl_150mM_315K_v-rescale (500ns)\n2) DPPC_512_NaCl_150mM_320K (700ns)\n3) DPPC_512_NaCl_150mM_320K_v-rescale (500ns)\n4) DPPC_512_NaCl_150mM_322K_v-rescale (700ns)\n5) DPPC_512_NaCl_150mM_325K (500ns)\n6) DPPC_512_NaCl_150mM_325K_v-rescale (500ns)\n7) DPPC_512_NaCl_150mM_325K_cutoff09 (500ns)\n8) DPPC_512_NaCl_150mM_325K_MEMB_338K (500ns)\n9) DPPC_512_NaCl_150mM_338K (500ns)\nFor further information read the Readme file provided for each simulation.", - "entities": [ - { - "label": "MOL", - "text": "DPPC", - "start": 12, - "end": 16 - }, - { - "label": "FFM", - "text": "charmm36", - "start": 45, - "end": 53 - }, - { - "label": "SOFTNAME", - "text": "gromacs", - "start": 60, - "end": 67 - }, - { - "label": "MOL", - "text": "DPPC", - "start": 94, - "end": 98 - }, - { - "label": "SOFTNAME", - "text": "gromacs", - "start": 124, - "end": 131 - }, - { - "label": "FFM", - "text": "charmm36", - "start": 142, - "end": 150 - }, - { - "label": "TEMP", - "text": "315", - "start": 194, - "end": 197 - }, - { - "label": "TEMP", - "text": "338 K", - "start": 202, - "end": 207 - }, - { - "label": "MOL", - "text": "DPPC", - "start": 297, - "end": 301 - }, - { - "label": "MOL", - "text": "NaCl", - "start": 306, - "end": 310 - }, - { - "label": "TEMP", - "text": "315K", - "start": 317, - "end": 321 - }, - { - "label": "STIME", - "text": "500ns", - "start": 333, - "end": 338 - }, - { - "label": "MOL", - "text": "DPPC", - "start": 343, - "end": 347 - }, - { - "label": "MOL", - "text": "NaCl", - "start": 352, - "end": 356 - }, - { - "label": "TEMP", - "text": "320K", - "start": 363, - "end": 367 - }, - { - "label": "STIME", - "text": "700ns", - "start": 369, - "end": 374 - }, - { - "label": "MOL", - "text": "DPPC", - "start": 379, - "end": 383 - }, - { - "label": "MOL", - "text": "NaCl", - "start": 388, - "end": 392 - }, - { - "label": "TEMP", - "text": "320K", - "start": 399, - "end": 403 - }, - { - "label": "STIME", - "text": "500ns", - "start": 415, - "end": 420 - }, - { - "label": "MOL", - "text": "DPPC", - "start": 425, - "end": 429 - }, - { - "label": "MOL", - "text": "NaCl", - "start": 434, - "end": 438 - }, - { - "label": "TEMP", - "text": "322K", - "start": 445, - "end": 449 - }, - { - "label": "STIME", - "text": "700ns", - "start": 461, - "end": 466 - }, - { - "label": "MOL", - "text": "DPPC", - "start": 471, - "end": 475 - }, - { - "label": "MOL", - "text": "NaCl", - "start": 480, - "end": 484 - }, - { - "label": "TEMP", - "text": "325K", - "start": 491, - "end": 495 - }, - { - "label": "STIME", - "text": "500ns", - "start": 497, - "end": 502 - }, - { - "label": "MOL", - "text": "DPPC", - "start": 507, - "end": 511 - }, - { - "label": "MOL", - "text": "NaCl", - "start": 516, - "end": 520 - }, - { - "label": "TEMP", - "text": "325K", - "start": 527, - "end": 531 - }, - { - "label": "STIME", - "text": "500ns", - "start": 543, - "end": 548 - }, - { - "label": "MOL", - "text": "DPPC", - "start": 553, - "end": 557 - }, - { - "label": "MOL", - "text": "NaCl", - "start": 562, - "end": 566 - }, - { - "label": "TEMP", - "text": "325K", - "start": 573, - "end": 577 - }, - { - "label": "STIME", - "text": "500ns", - "start": 588, - "end": 593 - }, - { - "label": "MOL", - "text": "DPPC", - "start": 598, - "end": 602 - }, - { - "label": "MOL", - "text": "NaCl", - "start": 607, - "end": 611 - }, - { - "label": "TEMP", - "text": "325K", - "start": 618, - "end": 622 - }, - { - "label": "TEMP", - "text": "338K", - "start": 628, - "end": 632 - }, - { - "label": "STIME", - "text": "500ns", - "start": 634, - "end": 639 - }, - { - "label": "MOL", - "text": "DPPC", - "start": 644, - "end": 648 - }, - { - "label": "MOL", - "text": "NaCl", - "start": 653, - "end": 657 - }, - { - "label": "TEMP", - "text": "338K", - "start": 664, - "end": 668 - }, - { - "label": "STIME", - "text": "500ns", - "start": 670, - "end": 675 - } - ], - "url": "https://zenodo.org/records/1009027" -} diff --git a/data/zenodo_34415.json b/data/zenodo_34415.json deleted file mode 100644 index 03be519..0000000 --- a/data/zenodo_34415.json +++ /dev/null @@ -1,92 +0,0 @@ -{ - "classes": [ - "SOFTNAME", - "SOFTVERS", - "STIME", - "MOL", - "FFM", - "TEMP" - ], - "raw_text": "POPC_AMBER_LIPID14_CaCl2_035Mol\nMD simulation trajectory and related files for fully hydrated POPC bilayer with 0.35M CaCl2. The LIPID14 force field was used with Gromacs 5.0.3. Ions were described by AMBER99SB-ILDN force field. Conditions: T=298.15, 128 POPC molecules, 6400 tip3p waters (lipid/water 1:50), 35 Ca, 70 Cl. 200ns trajectory  (preceded by 5ns NPT equillibration) (2 files of 100ns).\nTHE TRAJECTORY \"035M_CaCl2_POPC_AMB_100_200ns.xtc\" IS CORRUPTED. FOR THE UNCORRUPTED FILE PLEASE FOLLOW THE LINK: https://zenodo.org/record/46234\nThis data is ran for the nmrlipids.blospot.fi project. More details from nmrlipids.blospot.fi and https://github.com/NMRLipids/nmrlipids.blogspot.fi", - "entities": [ - { - "label": "MOL", - "text": "POPC", - "start": 94, - "end": 98 - }, - { - "label": "MOL", - "text": "CaCl2", - "start": 118, - "end": 123 - }, - { - "label": "FFM", - "text": "LIPID14", - "start": 129, - "end": 136 - }, - { - "label": "SOFTNAME", - "text": "Gromacs", - "start": 163, - "end": 170 - }, - { - "label": "SOFTVERS", - "text": "5.0.3.", - "start": 171, - "end": 177 - }, - { - "label": "FFM", - "text": "AMBER99SB-ILDN", - "start": 201, - "end": 215 - }, - { - "label": "TEMP", - "text": "298.15", - "start": 243, - "end": 249 - }, - { - "label": "MOL", - "text": "POPC", - "start": 255, - "end": 259 - }, - { - "label": "FFM", - "text": "tip3p", - "start": 276, - "end": 281 - }, - { - "label": "MOL", - "text": "Ca", - "start": 312, - "end": 314 - }, - { - "label": "MOL", - "text": "Cl", - "start": 319, - "end": 321 - }, - { - "label": "STIME", - "text": "200ns", - "start": 323, - "end": 328 - }, - { - "label": "STIME", - "text": "100ns", - "start": 390, - "end": 395 - } - ], - "url": "https://zenodo.org/records/34415" -} diff --git a/docs/ffm.yaml b/docs/ffm.yaml deleted file mode 100644 index 60ab64c..0000000 --- a/docs/ffm.yaml +++ /dev/null @@ -1,35 +0,0 @@ -title: Force field and model - -FFM: - - AMBER - - CHARMM - - GROMOS - - OPLS - - MARTINI - - SPC - - TIP3P - - TIP4P - - TIP5P - - LIPID - - SLIPID - - PARM - - GAFF - - OPC - - BERGER - - SPC/E - - TRAPPE - - GAL - - GAFFLIPID - - 43A1-S3 - - AMOEBA - - HIPPO - - WILLIAMS - - SIRAH - - RSFF - - ECC-LIPIDS - - COMPASS - - L-OPLS - - Q4MD-CD - - GDML - - BIND3P - - PLUM diff --git a/docs/normalization_rules.md b/docs/normalization_rules.md deleted file mode 100644 index 428191b..0000000 --- a/docs/normalization_rules.md +++ /dev/null @@ -1,32 +0,0 @@ -# Normalization rules - -This document provides the guidelines for automatically normalizing entities retrieved from scientific texts related to molecular simulations. - -See the [annotation rules](annotation_rules.md) document for the definition of entities. - -## For all entities - -- Normalize text to lowercase (e.g., `POPC` → `popc`). To be confirmed !! - -## Molecule (MOL) - -## Force field and model (FFM) - -## Software name (SOFTNAME) - -## Software version (SOFTVERS) - -- Must contain at least one digit to be considered valid. - -## Simulation time (STIME) - -- Acceptable input units: `s`, `sec`, `second`, `seconds`, `ms`, `millisecond`, `microsec`, `microsecond`, `microseconds`, `ns`, `nanosecond`, `nanoseconds`, `ps`, `picosecond`, `picoseconds`. - -### Examples - -- `10 to 50 ns` → not `10-50 ns` - -## Simulation temperature (TEMP) - -- Convert all temperatures to Kelvin if standardization is required (`25 °C` → `298K`) -- Convert `room temperature` to `300 K` diff --git a/docs/softname.yaml b/docs/softname.yaml deleted file mode 100644 index 3b5f520..0000000 --- a/docs/softname.yaml +++ /dev/null @@ -1,18 +0,0 @@ -title: Software name - -SOFTNAME: - - amber - - autodock - - charmm - - colvar - - desmond - - gromacs - - lammps - - namd - - openbabel - - orcaflex - - plumed - - pymol - - qchem - - schrodinger - - vmd diff --git a/notebooks/score_distribution.ipynb b/notebooks/score_distribution.ipynb deleted file mode 100644 index d83ca46..0000000 --- a/notebooks/score_distribution.ipynb +++ /dev/null @@ -1,154 +0,0 @@ -{ - "cells": [ - { - "cell_type": "code", - "execution_count": 5, - "id": "a42be492", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "The autoreload extension is already loaded. To reload it, use:\n", - " %reload_ext autoreload\n" - ] - } - ], - "source": [ - "%load_ext autoreload\n", - "%autoreload 2" - ] - }, - { - "cell_type": "code", - "execution_count": 22, - "id": "8d607635", - "metadata": {}, - "outputs": [], - "source": [ - "import matplotlib.pyplot as plt\n", - "import pandas as pd\n" - ] - }, - { - "cell_type": "code", - "execution_count": 23, - "id": "94a15a3f", - "metadata": {}, - "outputs": [], - "source": [ - "df = pd.read_csv(\"../results/grounded_molecules.tsv\", sep=\"\\t\")\n", - "\n", - "df = df[df[\"MOL_SCORE\"] != \"Not Available\"]\n", - "dfchebi = df[df[\"MOL_TYPE\"] == \"CHEBI\"]\n", - "scores = pd.to_numeric(dfchebi[\"MOL_SCORE\"])\n" - ] - }, - { - "cell_type": "code", - "execution_count": 24, - "id": "2bc8a689", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 24, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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9uxBo0aNCk323bFjh97C0cspz9QM29qGGDoGERFJgM6FjJ2dHXr16lUeWYiIiIh0onMhs3r16vLIQaQmV+Wj9sPbAIA4F28DpyEiImNWpptGEpUns7xcfPntBAC8RQEREZVM50LGy8urxOvF/P777y8UiIiIiEhbOhcy48aN03idm5uLy5cvY//+/Zg8ebK+chERERGVSudCZuzYsUW2L1++HBcuXHjhQERERETa0tstrIODg7F9+3Z9rY6IiIioVHorZLZt2wZ7e3t9rY6IiIioVDoPLTVr1kxjsq8QAomJiXj06BG+/vprvYYjIiIiKonOhUyPHj00XsvlclSvXh0dOnRA/fr19ZWLXmL5chOs69Bf/ZyIiKg4OhcyM2bMKI8cRGp5pmZY33GAoWMQEZEE6G2ODBEREVFF0/qIjFwuL/FCeAAgk8mQl5f3wqHo5SZTqeD++D4A4F41NwOnISIiY6Z1IbNz585il8XExGDZsmVQqVR6CUUvN0VeDv7vq3AAvEUBERGVTOtCpnv37oXabty4galTp2L37t0YMGAAZs+erddwRERERCUp0xyZhIQEDBs2DE2aNEFeXh5iY2OxZs0aeHh46DsfERERUbF0KmRSU1MxZcoU1K5dG1evXsWRI0ewe/duNG7cuLzyERERERVL66GlBQsW4LPPPoOzszM2btxY5FATUXmoaqVEvkrARF7yZPMCuvQlIiJp07qQmTp1KiwsLFC7dm2sWbMGa9asKbLfjh079BaOCACszU1hIpchcudl3H+cXmJft2rWmNqzWQUlIyIiQ9O6kBk4cGCpp18Tlaf7j9MRl5hm6BhERGREtC5koqKiyjEG0d/y5SbY2qaX+jkREVFxdL5FAVF5yzM1w3+Chhg6BhERSQBvUUBERESSxSMyZHRkKhUcUx8BAJJtqxs4DRERGTMWMmR0FHk5WLskDABvUUBERCXj0BIRERFJFgsZIiIikiwWMkRERCRZLGSIiIhIsljIEBERkWSxkCEiIiLJ4unXZHRUchP82LKr+jkREVFxWMi8hPJVAiZy7W4Aqktffck1NcPyNz+okPcy9t+FPnIYS24iovLAQuYlZCKXIXLnZdx/nF5iP7dq1pjas1kFpTIMKf4utM0MGFduIqLywELmJXX/cTriEtMMHaNoQsA283m2VEubcn87o/5dFEOKmYmIygML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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "plt.hist(scores, bins=40, color=\"steelblue\", edgecolor=\"white\")\n", - "\n", - "# Adding a vertical line at the mean score value\n", - "plt.axvline(\n", - " scores.mean(), color=\"red\", linestyle=\"--\", label=f\"Mean = {scores.mean():.3f}\"\n", - ")\n", - "\n", - "plt.title(\"Distribution of Scores for Chebi\")\n", - "plt.xlabel(\"Score\")\n", - "plt.ylabel(\"Number of entries\")\n", - "plt.legend()" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "1f36dbca", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 13, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "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ycklHjc8PBw2Njbi4urqqsar0D35snxNh0BERKQyWv0Yy9/fX1z38vJCq1at4Obmhi1btsDMzKzMxw0LC8OECRPE7bS0NCY8rzA0MMTAnwYiJinmjXX9G/pj/v/mV0BUREREZaPVyc7rbG1tUbt2bcTGxqJLly7IycnBs2fP5Fp3UlJSiuzj8yqpVAqpVKrmaHVbTFIMriRceWO9uk5131iHdJChIdCnz8t1IiIdptWPsV6Xnp6OuLg4ODs7o3nz5jA2NsbRo0fF/Tdu3EBCQgK8vb01GCWRHjA1BbZuLVhMTTUdDRFRuWh1y87EiRPRo0cPuLm5ITExETNnzoShoSEGDBgAGxsbjBgxAhMmTICdnR2sra3x6aefwtvbmyOxiIiISKTVyc69e/cwYMAAPH78GPb29mjTpg3+/PNP2NvbAwCWLVsGAwMDBAQEIDs7G35+fvj22281HDURERFpE61OdjZv3lziflNTU6xatQqrVq2qoIiI3hIZGQVTRQBAejpgYaHZeIiIykGn+uwQERERKYvJDhEREek1JjtERESk15jsEBERkV5jskNERER6jckOERER6TWtHnpORBpiaAh06/ZynYhIhzHZISJFpqbA3r2ajoKISCX4GIuIiIj0GpMdIiIi0mtMdohIUUZGwRQRFhYF60REOox9doioaJmZmo6AiEgl2LJDREREeo3JDhEREek1JjtERESk15jsEBERkV5jskNERER6jaOxiEiRgQHg6/tynYhIhzHZISJFZmZAZKSmoyAiUgn+yUZERER6jckOERER6TUmO0SkKCMDsLcvWDhdBBHpOPbZIaKiPXqk6QiIiFSCLTtERESk15jsEBERkV5jskNERER6jckOERER6TUmO0RERKTXOBqLiBQZGAAtWrxcJyLSYUx2iEiRmRlw4YKmoyAiUgn+yUZERER6jckOERER6TUmO0SkKDMTcHcvWDIzNR0NEVG5sM+OmuXL8mFoYKjyukRqJQjAnTsv14mIdJhWJzvh4eHYsWMH/v33X5iZmaF169b48ssvUadOHbFO+/btceLECbnPjRo1CqtXr67ocItkaGCIgT8NRExSTIn16jnXw8aRGysoKiIioreHVic7J06cQHBwMN555x3k5eVh2rRpeO+993D9+nVYWFiI9QIDAzFnzhxx29zcXBPhFismKQZXEq5oOgwi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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# Plot the score distribution as a histogram using 40 bins (from the Gilda results)\n", - "plt.hist(scores2, bins=40, color=\"darkgreen\", edgecolor=\"white\")\n", - "\n", - "# Adding a vertical line at the mean score value\n", - "plt.axvline(\n", - " scores2.mean(), color=\"red\", linestyle=\"--\", label=f\"Mean = {scores2.mean():.3f}\"\n", - ")\n", - "\n", - "# Set the title and labels for the plot\n", - "plt.title(\"Distribution of Scores for Gilda\")\n", - "plt.xlabel(\"Score\")\n", - "plt.ylabel(\"Number of entries\")\n", - "plt.legend()" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "mdverse_entity_norm (3.12.3)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} diff --git a/notebooks/visualize temperature.ipynb b/notebooks/visualize_STEMP.ipynb similarity index 54% rename from notebooks/visualize temperature.ipynb rename to notebooks/visualize_STEMP.ipynb index 757bcf7..b485f0e 100644 --- a/notebooks/visualize temperature.ipynb +++ b/notebooks/visualize_STEMP.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "235b3f20", "metadata": {}, "outputs": [], @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "c9cfd882", "metadata": {}, "outputs": [ @@ -37,12 +37,9 @@ "start = int(df[\"normalised_temperature\"].min())\n", "end = int(df[\"normalised_temperature\"].max()) + 100\n", "plt.xticks(np.arange(0, end, 400))\n", - "\n", "plt.title(\"Distribution of Normalised Temperatures\", fontsize=16)\n", "plt.xlabel(\"Temperature\", fontsize=14)\n", "plt.ylabel(\"Frequency\", fontsize=14)\n", - "\n", - "\n", "plt.show()" ] }, @@ -51,11 +48,26 @@ "execution_count": null, "id": "e6221d02", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "OK\n" + ] + }, + { + "data": { + "image/png": "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JiwszDRq1MhkZmY69E2cODHX/f+ZJGOz2cymTZsc2tu0aWMKFSrkcOzr169vPD09zc6dO+1tWVlZ5uGHHzaSzKpVq+zt135O3bp1y7bPa8egbdu2Dsd0ypQpRpIJDAw0x44ds7df+x159NFHHcZJSUnJNvbu3buNt7e36dOnj0N7kyZNTJkyZRzartV4I9OnTzeSzPPPP+/wni9dupRt23fffddIMosWLbK3Xfu9HzlyZI7jSzJxcXH257/88oux2WymQYMGJj093d7+22+/meDgYFOmTBlz5coVh9db/fkBrsSlOiAP3n//fQUGBqpjx446c+aM/XHhwgW1a9dOR44c0f79+x1e07FjR5UpU8b+vHjx4qpcubI8PDyyzf40atRImZmZOV7WevHFFx0us9SuXVv333+/Vq1aZb9EsWDBAtlsNsXFxTnUd+bMGbVv314XL17Uxo0bHcatUaOGWrZsmW1/fn5+9v1lZGTo3LlzOnPmjGJjY5WVlaUtW7Y4d/CckFNNX3/9tU6dOqWePXvqwoULDu/t2ozEypUrLY0fExOjunXrOrQ1b95cV65csR/706dP67vvvlP79u1VvXp1+3Y2m00vv/yyJOmTTz7JNvbgwYNz3W///v0dfobXPnTQvn17lSpVyt5+7Xfkz79L/v7+kv64XJycnKwzZ87Yt/3++++tvPUbSkhIUL9+/dS2bVtNnDjR3m6z2eTn5ydJunr1qv34N2/eXJJuat+fffaZjDF68cUX5e3tbW+PjIxUz549dfToUW3fvt3hNVZ+foCrcakOyIM9e/bo4sWL9ksYOTl16pQqVapkf16+fPls2xQtWlQRERHy8fHJ1i7JYd3INdcugV2vatWqWrlypY4ePap//OMf2rNnj4wxqlKlyg3ru971tV7vypUrGj9+vP73v//pwIEDMsY49J8/fz7XfdysnGras2ePJKlXr165vu7P7y03Of1MQkNDJf3fsT98+LAk6R//+Ee2baOjo+Xh4aFDhw5l68vteOa032s/72vrgv7cd/ToUYe27du3a8SIEVqzZo1SU1Md+nIawxk//fSTunbtqmrVqumDDz6Qh4fj39cfffSR3nzzTW3fvl2ZmZkOfTfzu3Cj43yt7dChQ6pTp4693crPD3A1ghOQB8YYFS9eXAsXLsx1m+vXG0nK9aPXN/pI9p9DijP12Ww2ffXVV7mO/+d/oAoXLpzjdoMGDdLbb7+trl276uWXX1aJEiXk5eWlbdu26aWXXlJWVpalmm50w8XcFnTnVNO1YzJhwgTVrFkzx9dFRkZaqik/jv01uR3PG+03t/brazl27JgaN26soKAgjRgxQpUrV5a/v79sNpsGDBhwUwujT548qbZt28rf31/Lly/PtiZp6dKl6tq1q+677z5NmTJFpUqVkq+vr65evapWrVpZ/l1wlfz8+QG5ITgBeVCxYkXt27dP9erV+8sFr662Z88e1atXz6Ft9+7d8vT0tF8KrFixohISElS6dOkcZ6ic8d5776lx48b68MMPHdoPHDiQbdsbhaOQkBBJ0rlz5xw+XZWWlqbExERVqFDBUj3XFj77+/vneGnR1a7N4Pz888/Z+n755RdlZWXlOPORXz755BOlpKTo888/V7NmzRz6zp49m2320qpLly6pXbt2OnPmjNatW6eSJUtm2+a9996Tr6+vvvnmG4dg+Msvv2Tb1tk7k187hj///HO2DxTs3r3bYRugILHGCciDJ598UllZWRo6dGiO/VYvFeXF66+/7vDX9LZt27Rq1Sq1aNHCHuK6d+8uSRo2bFi2j8s7W5+np2e2v95TU1M1adKkbNte2/+5c+ey9V27dLVq1SqH9kmTJjk1UxEbG6sSJUpo/PjxOe7n8uXL2W7lcDNKlCih+vXra9myZdq1a5e93RijcePGSZIeeughl+3vr1ybZfnzz2T27Nk6efJknsY0xuiJJ57Q1q1b9f7776t27dq57ttmszn8vIwxGjNmTLZtb/S7kJP27dvLZrNpwoQJDpcAExMTNW/ePJUpU0b33HOPM28LyBfMOAF5cO0WBFOnTtW2bdvUtm1bFStWTL/++qs2btyoAwcO5LjuxRWOHj2q2NhYtW/fXomJiZo6dar8/Pw0YcIE+zb33nuv4uPjFR8fr5o1a+rhhx9WZGSkEhMTtXXrVn355ZfKyMiw/F5nzpyprl27qmXLljp16pTmzp1rX0tyvXvvvVceHh4aO3aszp8/L39/f5UrV05169ZVy5YtVblyZb3yyis6e/asypUrp/Xr12vTpk0qVqyY5ffv7++v//3vf+rYsaMqV66sXr16qUKFCrpw4YJ++eUXLV26VJ988kmOd6vOqylTpqhJkyZq1KiR/XYEy5cv14oVK/T444+rRYsWLtvXX2ndurUKFy6s7t2767nnnlPRokW1YcMGffnll4qKirJ8H6vrzZgxQ5988omaNGmi1NRUvf/++w799evXV/ny5dWlSxctWbJEzZs315NPPqnMzEx9+umnunTpUrYxQ0NDVaFCBX344YeKioqy38/q+ntxXa9y5cr697//rddff12NGzdW165d7bcjSElJ0YIFC7jTONwCwQnIo7lz56pZs2aaNWuWxo0bp4yMDIWHh6tWrVr2mYj8kJCQoEGDBmnkyJG6fPmy6tWrpwkTJjh84kuSRo4cqTp16uitt97S5MmTlZqaqhIlSqhatWp66623LO9v4sSJCgwM1EcffaTPPvtMpUqV0tNPP617770326Wy0qVLa+7cuXrttdfUp08fZWZmKi4uTnXr1pWnp6c+//xz9e/fX2+//ba8vb31wAMPaO3atWrQoIFTxyA2NlabN2/W+PHj9f777+v3339X0aJFFRUVpUGDBmU7FjerTp06+u677zRy5EhNnz5dqampKl++vF577TW98MILLt3XX4mKitJXX32lYcOG6dVXX5Wnp6caNGigtWvX6rnnnsvTp8muzUCuXbtWa9euzdY/b948lS9fXo8++qguXryoSZMmafDgwSpatKjatWun8ePH5xikFyxYoIEDB2rYsGG6dOmSypQpk2twkqTXXntNFSpU0PTp0zVkyBB5e3urbt26WrhwYbavOwIKis2wgg64JfTo0UP//e9/WfQKAAWINU4AAAAWEZwAAAAsIjgBAABYxBonAAAAi5hxAgAAsIjgBAAAYBHBCQAAwCKCEwAAgEUEJwAAAIsITgAAABYRnAAAACwiOAEAAFhEcAIAALCI4AQAAGARwQkAAMAighMAAIBFBCcAAACL7ojgZIxRcnKyjDEFXQoAALiF3RHB6eLFiwoODtbFixcLuhQAAHALuyOCEwAAgCsQnAAAACwiOAEAAFhEcAIAALCI4AQAAGARwQkAAMAighMAAIBFBCcAAACLCE4AAAAWEZwAAAAsIjgBAABYRHACAACwiOAEAABgEcEJAADAIoITAACARQQnAAAAiwhOAAAAFhGcAAAALHKL4PTbb7/piSeeUGhoqPz8/HT33Xdry5Yt9n5jjF555RVFRETIz89PLVu21P79+wuwYgAAcCcq8OB0/vx5NWjQQF5eXvrqq6+0e/duvfnmmypatKh9m9dff11vvfWWZsyYoe+//17+/v6KjY1VWlpaAVYOAADuNDZjjCnIAoYMGaINGzbo22+/zbHfGKPIyEi98MILGjx4sCQpKSlJYWFhmj9/vh599NG/3EdycrKCg4OVlJSkoKAgl9YPAADuHAU+4/T555+rTp06evjhh1WiRAndc889mj17tr3/8OHDOnnypFq2bGlvCw4OVt26dbVx48aCKBkAANyhCjw4HTp0SO+8844qVqyoFStWqE+fPurfv7/++9//SpJOnjwpSQoLC3N4XVhYmL3vz9LT05WcnOzwAAAAuFmFCrqArKws1alTR6+++qok6Z577tGuXbs0Y8YMxcXF5WnMcePGadSoUa4sEwAAoOBnnCIiIlS1alWHtujoaB07dkySFB4eLkk6deqUwzanTp2y9/3Z0KFDlZSUZH8cP348HyoHAAB3mgIPTg0aNNDevXsd2vbt26cyZcpIksqVK6fw8HCtXr3a3p+cnKzvv/9eMTExOY7p4+OjoKAghwcAAMDNKvBLdQMHDlT9+vX16quv6pFHHtEPP/ygWbNmadasWZIkm82mAQMGaMyYMapYsaLKlSunESNGKDIyUh07dizY4gEAwB2lwG9HIEnLly/X0KFDtX//fpUrV06DBg3SU089Ze83xmjkyJGaNWuWLly4oIYNG2r69OmqVKmSpfG5HQEAAHAFtwhO+Y3gBAAAXKHA1zgBAADcKghOAAAAFhGcAAAALCI4AQAAWERwAgAAsIjgBAAAYBHBCQAAwCKCEwAAgEUEJwAAAIsITgAAABYRnAAAACwiOAEAAFhEcAIAALCI4AQAAGARwQkAAMAighMAAIBFBCcAAACLCE4AAAAWEZwAAAAsIjjBJdasWSObzZbjY9OmTfbtXn31VdWrV0/FixeXr6+vKlasqAEDBuj3338vwOoBALCmUEEXgNtL//79de+99zq0VahQwf7fW7duVc2aNfXoo48qMDBQe/bs0ezZs/XFF19ox44d8vf3/7tLBgDAMoITXKpRo0bq0qVLrv1LlizJ1hYTE6MuXbpo2bJlevTRR/OzPAAAbgqX6uByFy9e1JUrVyxvX7ZsWUnShQsX8qcgAABchOAEl+rZs6eCgoLk6+urZs2aacuWLdm2McbozJkzOnnypL799lv1799fnp6eatq06d9fMAAATuBSHVzC29tbnTt3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ePzfJNWfA9M1f74xZkFLTk5WcHCwkpKSFBQUlC/7+P3iFc3ecCFfxsad46kGRVQ8ME/fvQ0A+Bs4fYaeO3duroEJAADgduZ0cOrRo0c+lAEAAOD+nF4cDgAAcKdyesapXLlyf3mp7tChQ3kuCAAAwF05HZw6dOiQLTidP39ea9eulTFGnTp1cllxAAAA7sTp4DR58uQc2zMyMtSxY0eVK1fuZmsCAABwSy5b4+Tt7a3nnntOEyZMcNWQAAAAbsWli8PPnDmjixcvunJIAAAAt+H0pbqlS5dma8vIyNCePXs0depUNW/e3CWFAQAAuBung1OXLl1ybPfy8lKnTp309ttv33RRAAAA7sjp4HT48OFsbb6+vipRogR3FAcAALc1p4PT0aNHVatWLQUEBGTrS01N1datW9W4cWOXFAcAAOBOnF4c3qxZM+3evTvHvl9++UXNmjW76aIAAADckdPByRiTa19qaqr8/PxuqiAAAAB3ZelS3aZNm/Tdd9/Zny9cuFDr16932CYtLU2fffaZoqOjXVshAACAm7AUnFasWKFRo0ZJkmw2m956661s23h5eSk6OlrTp093bYUAAABuwtKlupEjRyorK0tZWVkyxmjTpk3259ce6enp2rFjh+rXr5/fNQMAABQIpz9Vl5WVlR91AAAAuD2ng9M1aWlpOnTokNLS0rL11apV66aKAgAAcEdOB6eMjAz16dNH77//vq5cuZLjNlevXr3pwgAAANyN07cjGDVqlFauXKn58+fLGKOpU6dq3rx5atGihcqWLatly5blR50AAAAFzungtHjxYsXHx+uRRx6RJN1333168skntXLlSjVs2JDgBADADYwdO1Y2m03VqlVzaF+5cqV69+6tatWqydPTU2XLli2YAnFDTgenX3/9VZUqVZKnp6d8fX11/vx5e98TTzyhxYsXu7RAAABuF7/++qteffVV+fv7Z+tbuHChFi5cqODgYEVGRhZAdbDC6eAUERGhCxcuSJLKlSunNWvW2Pv27dvnqroAALjtDB48WPXq1VOdOnWy9b366qtKTk7Whg0bVKNGjQKoDlY4vTi8adOm+vbbb9WuXTs99dRTGjx4sPbs2SNvb299+umnevzxx/OjTgAAbmnr1q3Txx9/rO3bt6tfv37Z+pllujU4HZzGjh2rM2fOSJIGDBggY4w+/vhjXb58Wf3799crr7zi8iIBALiVXb16Vf369dM///lP3X333QVdDm6CU8HpypUrOnHihEqVKmVvGzhwoAYOHOjywgAAuF3MmDFDR48e1apVqwq6FNwkp9Y4eXh4qF69etq5c2d+1QMAwG3l7NmzeuWVVzRixAgVL168oMvBTXI6OJUvX97hk3QAACB3w4cPV0hISI7rmnDrcfpTdcOGDdN//vMfnThxIj/qAQDgtrF//37NmjVL/fv314kTJ3TkyBEdOXJEaWlpyszM1JEjR3Tu3LmCLhNOcHpx+OLFi/X777+rfPnyql69usLCwmSz2ez9NptNn332mUuLBADgVvTbb78pKytL/fv3V//+/bP1lytXTs8//7wmT5789xeHPHE6OKWkpKhKlSoOzwEAQHbVqlXTJ598kq19+PDhunjxoqZMmaKoqKgCqAx55XRw+uabb/KjDgAAbjvFihVTx44ds7Vfm2G6vu/HH3/U559/Lkk6cOCAkpKSNGbMGElSjRo11K5du/wuFxY4HZwAAIDrbdu2TSNGjHBou/Y8Li6O4OQmnF4cLkk///yzHn30UUVFRcnHx0fbtm2TJL388sv66quvXFogAAC3mzVr1mjXrl0ObT169JAxJsfH/PnzC6ZQZON0cPr66691zz336OjRo+rWrZsyMzPtfV5eXpo+fbpLCwQAAHAXTgenoUOH6tFHH9XGjRuzfb3KPffco+3bt7usOAAAAHfidHDatWuXunfvLkkOtyGQpCJFiti/xw4AAOB243RwCgkJyfXml/v27VNERMRNFwUAAOCOnA5OHTt21MiRI7V37157m81m08mTJ/XGG2+oc+fOLi0QAADAXTgdnMaNG6fixYurevXqqlu3riSpV69eqly5soKDgxUfH+/qGgEAANyC0/dxCg4O1nfffaf3339fX3/9tUJCQhQSEqK+ffvqySeflLe3d37UCQC4BWVmXdWp9IsFXQZucWE+gfLy8CzoMiTl8QaYXl5e6tmzp3r27OnqegAAt5FT6Rc1bNeXBV0GbnGvVmujkn5FCroMSXm8Aab0x0Lw999/XxMmTNCCBQsc1jzl1fjx42Wz2TRgwAB7W1pamvr27avQ0FAFBASoc+fOOnXq1E3vCwAAwFl5+pLfp59+Wh999JGysrLk6+urtLQ0eXh46OGHH9bs2bMVEBDgdCGbN2/WzJkzVb16dYf2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Qffdd1+urwsLC1NkZKQOHTqkbt263dwbuU50dLSOHz+u48eP22eddu/erQsXLqhq1aou2881GzZsUI8ePfTQQw9J+uMEeW0xvRXGGD3xxBPKysrSe++9Zz9RSdLZs2e1d+9ezZ49W40aNZIkrV+/3uH11/5ivHr1aq77iIqKkre3tzZs2KAyZcpI+uMTO5s3b77j7nMCWHHlyhX973//05tvvqkHHnjAoa9jx4764IMP9K9//SvH13p5eWX7/7FWrVrau3evwx+u+SU6Olrz589XamqqPRxt2LBBHh4eqly5ssv3t2HDBk2fPl1t2rSR9McHjM6cOWP59WlpaerQoYOqVKmiiRMnOvT98ssvOnv2rMaPH28/n2/ZssVhGyvnwOjoaF25ckXff/+96tevL+n/zq/58e9CQSE45YP09HSdPHlS0h+fMJg6dapSUlLsn5Tr1q2bJkyYoA4dOtg/JXf06FEtXbpUL774okqWLJnjuP/+9781cuRIRUVFqWbNmpo3b5527NiR48zV9UaNGqX+/fsrODhYrVq1Unp6urZs2aLz589r0KBBeXqPLVu21N13361u3bpp8uTJunLlip599lk1adLE8jS4MypWrKilS5eqXbt2stlsGjFihFN/+cXHx2vVqlVauXKlUlJS7DNxwcHBKlq0qEJDQzVr1ixFRETo2LFjGjJkiMPrS5QoIT8/PyUkJKhkyZLy9fXN9pewv7+/+vTpo3//+98KCQlR6dKl9frrr+vSpUsOf90B+MPy5ct1/vx59e7dO9v/T507d9acOXNyDU5ly5bV6tWr1aBBA/n4+Kho0aJ65ZVX1LZtW5UuXVpdunSRh4eHdu7cqV27dmnMmDEurb1bt24aOXKk4uLiFB8fr99//139+vVT9+7d7ZfeXKlixYr2T74lJyfr3//+t/z8/Cy//plnntHx48e1evVqh0/rXTtXeXt76+2339a//vUv7dq1K9u9mcqUKSObzably5erTZs28vPzU0BAQLYaO3TooKeeekozZ85UYGCghgwZorvuuksdOnS4uQPgRljjlA8SEhIUERGhiIgI1a1bV5s3b9bixYvtH2EvXLiw1q1bp9KlS6tTp06Kjo5W7969lZaWdsMZqP79+2vQoEF64YUXdPfddyshIUGff/65KlaseMN6/vnPf+rdd9/VvHnzdPfdd6tJkyaaP3++/SP2eWGz2fTZZ5+paNGiaty4sVq2bKny5ctr0aJFeR7zRiZOnKiiRYuqfv36ateunWJjY1WrVi3Lr1+7dq1SUlJUv359+88mIiJCixYtkoeHhz788ENt3bpV1apV08CBAzVhwgSH1xcqVEhvvfWWZs6cqcjIyFxPAuPHj1fnzp3VvXt31apVSwcOHNCKFStUtGjRm3r/wO1ozpw5atmyZY6X4zp37qwtW7bkevPgN998U19//bVKlSqle+65R5IUGxur5cuXa+XKlbr33ntVr149TZo0yT4D7EqFCxfWihUrdO7cOd17773q0qWLWrRooalTp7p8X9Ifx+r8+fOqVauWunfvbr/tiVVr165VYmKiqlat6nAO/O6771S8eHHNnz9fixcvVtWqVTV+/Hi98cYbDq+/6667NGrUKA0ZMkRhYWF67rnnctzPvHnzVLt2bbVt21YxMTEyxujLL7/MdnnuVmYzt9KFRQAAgALEjBMAAIBFBCcAAACLCE4AAAAWEZwAAAAsIjgBAABYRHACAACwiOAEAABgEcEJAADAIoITAACARQQnAAAAiwhOAAAAFhGcAAAALPr/ANLl+0TcSa1gAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ - "import matplotlib.pyplot as plt\n", - "import pandas as pd\n", - "\n", "df = pd.read_csv(\"../results/norm_temp/norm_temp.tsv\", sep=\"\\t\")\n", "\n", "before = df[\"raw_temperature\"].nunique()\n", @@ -72,7 +84,7 @@ " linewidth=1.2,\n", ")\n", "\n", - "for bar, val in zip(bars, [before, after]):\n", + "for bar, val in zip(bars, [before, after], strict=False):\n", " ax.text(\n", " bar.get_x() + bar.get_width() / 2,\n", " bar.get_height() + 0.5,\n", @@ -89,49 +101,15 @@ " \"Temperature normalization\", fontsize=13, fontweight=\"500\", color=\"black\", pad=14\n", ")\n", "ax.set_ylim(0, max(before, after) * 1.2)\n", - "ax.grid(False)\n", + "ax.grid(visible=False)\n", "ax.spines[[\"top\", \"right\"]].set_visible(False)\n", "ax.spines[\"left\"].set_color(\"black\")\n", "ax.spines[\"bottom\"].set_color(\"black\")\n", "ax.tick_params(colors=\"black\", labelsize=10)\n", "\n", "plt.tight_layout()\n", - "plt.savefig(\"histo_temp.png\", dpi=150, bbox_inches=\"tight\")\n", "print(\"OK\")" ] - }, - { - "cell_type": "code", - "execution_count": 19, - "id": "c115440c", - "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "370" - ] - }, - "execution_count": 19, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "df = pd.read_csv(\"../data/entities.tsv\", sep=\"\\t\")\n", - "NOT_WANTED_WORD = [\"inhibitor\", \"agonist\", \"activator\"]\n", - "\n", - "mol_entities = df[df[\"category\"] == \"MOL\"]\n", - "mol_entities = list(mol_entities[\"entity\"].unique())\n", - "molecule_liste = []\n", - "for molecule in mol_entities:\n", - " if len(molecule) > 3:\n", - " for word in NOT_WANTED_WORD:\n", - " if word not in molecule:\n", - " molecule_liste.append(molecule)\n", - "molecule_liste = list(set(molecule_liste))\n", - "len(molecule_liste)" - ] } ], "metadata": { diff --git a/notebooks/visualize_time_simu.ipynb b/notebooks/visualize_STIME.ipynb similarity index 87% rename from notebooks/visualize_time_simu.ipynb rename to notebooks/visualize_STIME.ipynb index e9bd688..a4e046c 100644 --- a/notebooks/visualize_time_simu.ipynb +++ b/notebooks/visualize_STIME.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 2, + "execution_count": 15, "id": "92adb128", "metadata": {}, "outputs": [], @@ -14,7 +14,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 16, "id": "a894cadc", "metadata": {}, "outputs": [], @@ -29,7 +29,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 17, "id": "138da561", "metadata": {}, "outputs": [ @@ -103,7 +103,7 @@ "4 1 microsecond 1.0 μs" ] }, - "execution_count": 4, + "execution_count": 17, "metadata": {}, "output_type": "execute_result" } @@ -114,7 +114,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 18, "id": "9b2ebc3d", "metadata": {}, "outputs": [], @@ -125,7 +125,7 @@ }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 19, "id": "156223d8", "metadata": {}, "outputs": [ @@ -135,7 +135,7 @@ "111" ] }, - "execution_count": 17, + "execution_count": 19, "metadata": {}, "output_type": "execute_result" } @@ -146,7 +146,7 @@ }, { "cell_type": "code", - "execution_count": 18, + "execution_count": 20, "id": "9755418d", "metadata": {}, "outputs": [ @@ -156,7 +156,7 @@ "88" ] }, - "execution_count": 18, + "execution_count": 20, "metadata": {}, "output_type": "execute_result" } @@ -167,7 +167,7 @@ }, { "cell_type": "code", - "execution_count": 19, + "execution_count": 21, "id": "cell-7", "metadata": {}, "outputs": [ @@ -227,105 +227,8 @@ "ax.set_title(\"Distribution of simulation times\")\n", "ax.legend()\n", "plt.tight_layout()\n", - "plt.savefig(\"stime_distribution.png\")\n", - "plt.show()" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "475d25ab", - "metadata": {}, - "outputs": [ - { - "ename": "NameError", - "evalue": "name 'valid_ns_llm' is not defined", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mNameError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[1]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m global_min = \u001b[38;5;28mmin\u001b[39m(\u001b[38;5;28mmin\u001b[39m(\u001b[43mvalid_ns_llm\u001b[49m), \u001b[38;5;28mmin\u001b[39m(valid_ns_reg))\n\u001b[32m 2\u001b[39m global_max = \u001b[38;5;28mmax\u001b[39m(\u001b[38;5;28mmax\u001b[39m(valid_ns_llm), \u001b[38;5;28mmax\u001b[39m(valid_ns_reg))\n\u001b[32m 3\u001b[39m shared_bins = np.logspace(np.log10(global_min), np.log10(global_max), \u001b[32m30\u001b[39m)\n", - "\u001b[31mNameError\u001b[39m: name 'valid_ns_llm' is not defined" - ] - } - ], - "source": [ - "global_min = min(min(valid_ns_llm), min(valid_ns_reg))\n", - "global_max = max(max(valid_ns_llm), max(valid_ns_reg))\n", - "shared_bins = np.logspace(np.log10(global_min), np.log10(global_max), 30)\n", - "\n", - "fig, ax = plt.subplots(figsize=(10, 6))\n", - "\n", - "ax.hist(\n", - " valid_ns_llm,\n", - " bins=shared_bins,\n", - " color=\"royalblue\",\n", - " alpha=0.45,\n", - " linewidth=2,\n", - " hatch=\"///\",\n", - " label=\"AI-based STIME extraction (LLM)\",\n", - ")\n", - "ax.hist(\n", - " valid_ns_llm,\n", - " bins=shared_bins,\n", - " histtype=\"step\",\n", - " edgecolor=\"navy\",\n", - " linewidth=2.0,\n", - ")\n", - "ax.hist(\n", - " valid_ns_reg,\n", - " bins=shared_bins,\n", - " color=\"gold\",\n", - " edgecolor=\"darkgoldenrod\",\n", - " alpha=0.65,\n", - " linewidth=2,\n", - " label=\"Naïve STIME extraction (regex)\",\n", - ")\n", - "\n", - "ax.set_xscale(\"log\")\n", - "ax.set_xticks([1e0, 1e3, 1e6])\n", - "ax.set_xticklabels([\"1 ns\", \"1 μs\", \"1 ms\"])\n", - "ax.set_xlabel(\"Simulation time\")\n", - "ax.set_ylabel(\"Count\")\n", - "ax.set_title(\"Distribution of simulation times\")\n", - "ax.legend()\n", - "plt.tight_layout()\n", - "plt.savefig(\"stime_distribution.png\")\n", "plt.show()" ] - }, - { - "cell_type": "code", - "execution_count": 45, - "id": "29054502", - "metadata": {}, - "outputs": [ - { - "ename": "ParserError", - "evalue": "Error tokenizing data. C error: Expected 1 fields in line 48, saw 2\n", - "output_type": "error", - "traceback": [ - "\u001b[31m---------------------------------------------------------------------------\u001b[39m", - "\u001b[31mParserError\u001b[39m Traceback (most recent call last)", - "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[45]\u001b[39m\u001b[32m, line 1\u001b[39m\n\u001b[32m----> \u001b[39m\u001b[32m1\u001b[39m df = \u001b[43mpd\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread_csv\u001b[49m\u001b[43m(\u001b[49m\u001b[33;43m\"\u001b[39;49m\u001b[33;43m../data/entities.tsv\u001b[39;49m\u001b[33;43m\"\u001b[39;49m\u001b[43m)\u001b[49m\n\u001b[32m 2\u001b[39m df = [df[\u001b[33m\"\u001b[39m\u001b[33mcategory\u001b[39m\u001b[33m\"\u001b[39m] == \u001b[33m\"\u001b[39m\u001b[33mSOFTNAMES\u001b[39m\u001b[33m\"\u001b[39m]\n\u001b[32m 3\u001b[39m \u001b[38;5;28mlen\u001b[39m(df)\n", - "\u001b[36mFile \u001b[39m\u001b[32m/data/zenati/mdverse_entity_norm/.venv/lib/python3.12/site-packages/pandas/io/parsers/readers.py:873\u001b[39m, in \u001b[36mread_csv\u001b[39m\u001b[34m(filepath_or_buffer, sep, delimiter, header, names, index_col, usecols, dtype, engine, converters, true_values, false_values, skipinitialspace, skiprows, skipfooter, nrows, na_values, keep_default_na, na_filter, skip_blank_lines, parse_dates, date_format, dayfirst, cache_dates, iterator, chunksize, compression, thousands, decimal, lineterminator, quotechar, quoting, doublequote, escapechar, comment, encoding, encoding_errors, dialect, on_bad_lines, low_memory, memory_map, float_precision, storage_options, dtype_backend)\u001b[39m\n\u001b[32m 861\u001b[39m kwds_defaults = _refine_defaults_read(\n\u001b[32m 862\u001b[39m dialect,\n\u001b[32m 863\u001b[39m delimiter,\n\u001b[32m (...)\u001b[39m\u001b[32m 869\u001b[39m dtype_backend=dtype_backend,\n\u001b[32m 870\u001b[39m )\n\u001b[32m 871\u001b[39m kwds.update(kwds_defaults)\n\u001b[32m--> \u001b[39m\u001b[32m873\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_read\u001b[49m\u001b[43m(\u001b[49m\u001b[43mfilepath_or_buffer\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43mkwds\u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[36mFile \u001b[39m\u001b[32m/data/zenati/mdverse_entity_norm/.venv/lib/python3.12/site-packages/pandas/io/parsers/readers.py:306\u001b[39m, in \u001b[36m_read\u001b[39m\u001b[34m(filepath_or_buffer, kwds)\u001b[39m\n\u001b[32m 303\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m parser\n\u001b[32m 305\u001b[39m \u001b[38;5;28;01mwith\u001b[39;00m parser:\n\u001b[32m--> \u001b[39m\u001b[32m306\u001b[39m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43mparser\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnrows\u001b[49m\u001b[43m)\u001b[49m\n", - "\u001b[36mFile \u001b[39m\u001b[32m/data/zenati/mdverse_entity_norm/.venv/lib/python3.12/site-packages/pandas/io/parsers/readers.py:1947\u001b[39m, in \u001b[36mTextFileReader.read\u001b[39m\u001b[34m(self, nrows)\u001b[39m\n\u001b[32m 1940\u001b[39m nrows = validate_integer(\u001b[33m\"\u001b[39m\u001b[33mnrows\u001b[39m\u001b[33m\"\u001b[39m, nrows)\n\u001b[32m 1941\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 1942\u001b[39m \u001b[38;5;66;03m# error: \"ParserBase\" has no attribute \"read\"\u001b[39;00m\n\u001b[32m 1943\u001b[39m (\n\u001b[32m 1944\u001b[39m index,\n\u001b[32m 1945\u001b[39m columns,\n\u001b[32m 1946\u001b[39m col_dict,\n\u001b[32m-> \u001b[39m\u001b[32m1947\u001b[39m ) = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_engine\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread\u001b[49m\u001b[43m(\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;66;43;03m# type: ignore[attr-defined]\u001b[39;49;00m\n\u001b[32m 1948\u001b[39m \u001b[43m \u001b[49m\u001b[43mnrows\u001b[49m\n\u001b[32m 1949\u001b[39m \u001b[43m \u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 1950\u001b[39m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mException\u001b[39;00m:\n\u001b[32m 1951\u001b[39m \u001b[38;5;28mself\u001b[39m.close()\n", - "\u001b[36mFile \u001b[39m\u001b[32m/data/zenati/mdverse_entity_norm/.venv/lib/python3.12/site-packages/pandas/io/parsers/c_parser_wrapper.py:215\u001b[39m, in \u001b[36mCParserWrapper.read\u001b[39m\u001b[34m(self, nrows)\u001b[39m\n\u001b[32m 213\u001b[39m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[32m 214\u001b[39m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mself\u001b[39m.low_memory:\n\u001b[32m--> \u001b[39m\u001b[32m215\u001b[39m chunks = \u001b[38;5;28;43mself\u001b[39;49m\u001b[43m.\u001b[49m\u001b[43m_reader\u001b[49m\u001b[43m.\u001b[49m\u001b[43mread_low_memory\u001b[49m\u001b[43m(\u001b[49m\u001b[43mnrows\u001b[49m\u001b[43m)\u001b[49m\n\u001b[32m 216\u001b[39m \u001b[38;5;66;03m# destructive to chunks\u001b[39;00m\n\u001b[32m 217\u001b[39m data = _concatenate_chunks(chunks, \u001b[38;5;28mself\u001b[39m.names)\n", - "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/parsers.pyx:832\u001b[39m, in \u001b[36mpandas._libs.parsers.TextReader.read_low_memory\u001b[39m\u001b[34m()\u001b[39m\n", - "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/parsers.pyx:897\u001b[39m, in \u001b[36mpandas._libs.parsers.TextReader._read_rows\u001b[39m\u001b[34m()\u001b[39m\n", - "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/parsers.pyx:868\u001b[39m, in \u001b[36mpandas._libs.parsers.TextReader._tokenize_rows\u001b[39m\u001b[34m()\u001b[39m\n", - "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/parsers.pyx:885\u001b[39m, in \u001b[36mpandas._libs.parsers.TextReader._check_tokenize_status\u001b[39m\u001b[34m()\u001b[39m\n", - "\u001b[36mFile \u001b[39m\u001b[32mpandas/_libs/parsers.pyx:2084\u001b[39m, in \u001b[36mpandas._libs.parsers.raise_parser_error\u001b[39m\u001b[34m()\u001b[39m\n", - "\u001b[31mParserError\u001b[39m: Error tokenizing data. C error: Expected 1 fields in line 48, saw 2\n" - ] - } - ], - "source": [ - "df = pd.read_csv(\"../data/entities.tsv\")\n", - "df = [df[\"category\"] == \"SOFTNAMES\"]\n", - "len(df)" - ] } ], "metadata": { diff --git a/molecules_grounding_logic.dot b/plots/molecules_grounding_logic.dot similarity index 100% rename from molecules_grounding_logic.dot rename to plots/molecules_grounding_logic.dot diff --git a/molecules_grounding_logic.png b/plots/molecules_grounding_logic.png similarity index 100% rename from molecules_grounding_logic.png rename to plots/molecules_grounding_logic.png diff --git a/pytest.toml b/pytest.toml deleted file mode 100644 index 6240b87..0000000 --- a/pytest.toml +++ /dev/null @@ -1,5 +0,0 @@ -[pytest] -addopts = ["--strict-markers"] -markers = [ - "network: network tests that require internet access and could be slow", -] diff --git a/data/FFM_ground.json b/results/FFM/FFM_grounded.json similarity index 100% rename from data/FFM_ground.json rename to results/FFM/FFM_grounded.json diff --git a/data/software/amber/codemeta.json b/results/SOFTNAME/amber/codemeta.json similarity index 100% rename from data/software/amber/codemeta.json rename to results/SOFTNAME/amber/codemeta.json diff --git a/data/software/charmm-gui/codemeta.json b/results/SOFTNAME/charmm-gui/codemeta.json similarity index 100% rename from data/software/charmm-gui/codemeta.json rename to results/SOFTNAME/charmm-gui/codemeta.json diff --git a/data/software/espresso++/codemeta.json b/results/SOFTNAME/espresso++/codemeta.json similarity index 100% rename from data/software/espresso++/codemeta.json rename to results/SOFTNAME/espresso++/codemeta.json diff --git a/data/software/gromacs/codemeta.json b/results/SOFTNAME/gromacs/codemeta.json similarity index 100% rename from data/software/gromacs/codemeta.json rename to results/SOFTNAME/gromacs/codemeta.json diff --git a/data/software/lammps/codemeta.json b/results/SOFTNAME/lammps/codemeta.json similarity index 100% rename from data/software/lammps/codemeta.json rename to results/SOFTNAME/lammps/codemeta.json diff --git a/data/software/lassohtp/codemeta.json b/results/SOFTNAME/lassohtp/codemeta.json similarity index 100% rename from data/software/lassohtp/codemeta.json rename to results/SOFTNAME/lassohtp/codemeta.json diff --git a/data/software/plumed/codemeta.json b/results/SOFTNAME/plumed/codemeta.json similarity index 100% rename from data/software/plumed/codemeta.json rename to results/SOFTNAME/plumed/codemeta.json diff --git a/data/software/probis/codemeta.json b/results/SOFTNAME/probis/codemeta.json similarity index 100% rename from data/software/probis/codemeta.json rename to results/SOFTNAME/probis/codemeta.json diff --git a/data/software/vmd/codemeta.json b/results/SOFTNAME/vmd/codemeta.json similarity index 100% rename from data/software/vmd/codemeta.json rename to results/SOFTNAME/vmd/codemeta.json diff --git a/src/mdverse_entity_norm/.gitignore b/src/mdverse_entity_norm/.gitignore deleted file mode 100644 index cb16962..0000000 --- a/src/mdverse_entity_norm/.gitignore +++ /dev/null @@ -1 +0,0 @@ -notebooks/tests.ipynb diff --git a/src/mdverse_entity_norm/scripts/count_llm_errors_from_logs.py b/src/mdverse_entity_norm/scripts/count_llm_errors_from_logs.py new file mode 100644 index 0000000..f75bc60 --- /dev/null +++ b/src/mdverse_entity_norm/scripts/count_llm_errors_from_logs.py @@ -0,0 +1,93 @@ +"""Script to count the number of LLM errors from logs.""" + +from pathlib import Path + +import click +import pandas as pd +from loguru import logger + + +def extract_errors(lines: list[str]) -> list: + """Extract the model, input, and output from the log lines. + + Returns + ------- + list + A list of lists, where each inner list contains the model, input, and output for + each error found in the log lines. + """ + results = [] + logger.info("Extracting errors from log lines...") + for i in range(len(lines)): + if "failed" in lines[i]: + context = lines[max(0, i - 2) : i + 1] + + model = "" + input_val = "" + output = "" + + for line in context: + if "Normalizing" in line: + parts_pipe = line.split("|") + if len(parts_pipe) > 2: + model = parts_pipe[2].strip() + + parts_colon = line.split(":") + if len(parts_colon) > 3: + input_val = parts_colon[3].strip() + + if "SimulationTime" in line: + parts_colon = line.split(":") + if len(parts_colon) > 3: + output = parts_colon[3].strip() + + if "failed after" in line: + output = "failed after 3 attempts" + + results.append([model, input_val, output]) + + logger.success(f"Extracted {len(results)} errors from log lines.") + return results + + +@click.command() +@click.option( + "--log-file", + type=click.Path(exists=True, dir_okay=False, path_type=Path), + help="Path to the TSV file containing the entities.", +) +def main(log_file: Path): + """Count the number of LLM errors from logs.""" + logger.info(f"Counting LLM errors from log file: {log_file}") + # Load the log file and extract the lines + try: + with open(log_file, encoding="utf-8") as f: + lines = f.readlines() + except FileNotFoundError: + logger.error("Log file not found.") + return + except UnicodeDecodeError as e: + logger.error(f"Error reading log file: {e}") + return + + errors = extract_errors(lines) + df_errors = pd.DataFrame(errors, columns=["model", "input", "result"]) + # Count the occurrences of each unique combination of model, input, and result + counts = df_errors.value_counts().reset_index() + counts.columns = ["model", "input", "result", "count"] + # Add a total count for each model + counts["total"] = counts.groupby("model")["count"].transform("sum") + # Sort the DataFrame by total count and then by individual count + sorted_df = counts.sort_values(by=["total", "count"], ascending=False) + # Rearrange the columns to have total and count first + sorted_df = sorted_df[["total", "count", "model", "input", "result"]] + logger.debug(f"Sorted DataFrame:\n{sorted_df}") + # Save the sorted DataFrame to a TSV file + output_path = Path("results/STIME_normalized/llm_error_count.tsv") + if not output_path.parent.exists(): + output_path.parent.mkdir(parents=True, exist_ok=True) + sorted_df.to_csv(output_path, sep="\t", index=False) + + +if __name__ == "__main__": + main() diff --git a/src/mdverse_entity_norm/scripts/normalize_simulation_time.py b/src/mdverse_entity_norm/scripts/evaluate_llm_models.py similarity index 86% rename from src/mdverse_entity_norm/scripts/normalize_simulation_time.py rename to src/mdverse_entity_norm/scripts/evaluate_llm_models.py index 917c2c9..1b2f42e 100644 --- a/src/mdverse_entity_norm/scripts/normalize_simulation_time.py +++ b/src/mdverse_entity_norm/scripts/evaluate_llm_models.py @@ -19,8 +19,8 @@ load_dotenv() -# We deifine the list of model that we are going to test -MODEL = [ +# list of model that we are going to test +MODELS = [ "openai/gpt-4o", "openai/gpt-5.5", "deepseek/deepseek-v4-pro", @@ -32,13 +32,6 @@ "mistralai/mistral-large-2512", ] -# We creat a pydantic class that will define the structure the llm output -# - The SimulationTime class will define the structure of a simulation time : -# value, unit -# - The NormSimuTime class will define the structure of the llm output : -# input (simluation time given to normalize) -# output (structured simulation time -> defined by the previous class) - class SimulationTime(BaseModel): """Define the structure of simulation time entity.""" @@ -60,23 +53,6 @@ class NormSimuTime(BaseModel): ) -# We create the prompt containning the normalisation guideline for the llm -PROMPT = """You are a unit normalization assistant for molecular dynamics simulation times. -Your tasks: -- Convert all time units to standard time abbreviations (ps, ns, μs, ms, s) -- Separate numerical values from time units - -Rules: -- No markdown, no explanation -- Use only standard time units: ps (picoseconds), ns (nanoseconds), μs (microseconds), ms (milliseconds), s (seconds) -- Always separate value and unit (e.g. "500ns" → value: 500, unit: "ns") -- Take in consideration values written in letter (e.g. "one hundred"), and convert it to numeric value -- If the simulation time is an interval, separate each simulation time in the interval. -- If the unit is missing or the unit is not a time unit, output the normalized unit to "None" -- If the numerical value is missing, output the normalized value to "None" - """ - - # We load the simulation times from the file in a list of simulatuion time to enable # slicing the list def load_simulation_times(ground_truth_file: Path) -> list: @@ -320,7 +296,12 @@ def normalize_all_entities( return normalised_entity, normalisation_time, normalisation_cost -def evaluate_all_models(raw_simulation_times: list, ground_truth_file: Path, runs: int, prompt_file_path: Path): +def evaluate_all_models( + raw_simulation_times: list, + ground_truth_file: Path, + runs: int, + prompt_file_path: Path, +): """Evaluate all models and save results to TSV file. Parameters @@ -345,7 +326,7 @@ def evaluate_all_models(raw_simulation_times: list, ground_truth_file: Path, run results = [] - for model in MODEL: + for model in MODELS: logger.info("-" * 20) logger.info(f"Model: {model.replace('-', '_')}") total_correct = 0 @@ -412,29 +393,24 @@ def save_evaluation_results_in_tsv( runs (int): The number of runs to perform for each model to calculate the average accuracy. """ - results = evaluate_all_models(raw_simulation_times, ground_truth_file, runs, prompt_file_path) + results = evaluate_all_models( + raw_simulation_times, ground_truth_file, runs, prompt_file_path + ) with open(model_evaluation_file, "w") as f: f.write( "model_name\taccuracy_percentage\tnormalisation_times_sec\tnormalisation_cost\n" ) f.writelines( - f"{result['model_name']}\t{result['accuracy_percentage']}\t{result['normalisation_time']}\t{result['normalisation_cost']}\n" + f"{result['model_name']}\t{result['accuracy_percentage']}\t{result['inference_time_by_entity']}\t{result['inference_cost_by_entity_USD']}\n" for result in results ) @click.command() @click.option( - "--normalized_simulation_time", - default="results/normalized_simulation_time_gpt.json", - type=click.Path(file_okay=True, path_type=Path), - help="Path to the JSON output file containing the normalized simulation times", -) -@click.option( - "--ground_truth_file", - default="data/STIME_ground_truth.json", + "--groundtruth-path", type=click.Path(exists=True, file_okay=True, path_type=Path), - help="Path to the groundtruth file", + help="Path to the groundtruth file containing manually normalized simulation times", ) @click.option( "--runs", @@ -443,24 +419,30 @@ def save_evaluation_results_in_tsv( help="Number of runs of the script", ) @click.option( - "--model_evaluation_file", - default="results/norm_simu_times/model_evaluation.tsv", + "--model-evaluation-path", type=click.Path(file_okay=True, path_type=Path), help="Path to the TSV file for model evaluation results", ) +@click.option( + "--prompt-path", + type=click.Path(file_okay=True, path_type=Path), + help="Path to the llm prompt file", +) def main_normalizing_simulation_times( - normalized_simulation_time: Path, - ground_truth_file: Path, + groundtruth_path: Path, runs: int, - model_evaluation_file: Path, + model_evaluation_path: Path, + prompt_path: Path, ): """Normalize the simulation times entities bu running all annexe functions.""" - times = load_simulation_times(ground_truth_file) + times = load_simulation_times(groundtruth_path) times = times[:] - # normalisation_output = format_norm_simulation_time(times, model_name=MODEL[0]) - # save_norm_simulation_results(normalisation_output, normalized_simulation_time) save_evaluation_results_in_tsv( - model_evaluation_file, times, ground_truth_file, Path("PROMPT"), runs + model_evaluation_path, + times, + groundtruth_path, + prompt_path, + runs, ) diff --git a/src/mdverse_entity_norm/scripts/llm_errors.py b/src/mdverse_entity_norm/scripts/llm_errors.py deleted file mode 100644 index ab1ca41..0000000 --- a/src/mdverse_entity_norm/scripts/llm_errors.py +++ /dev/null @@ -1,89 +0,0 @@ -import sys - -import pandas as pd -from loguru import logger - - -def check_argument(): - if len(sys.argv) < 2: - print("Usage: uv run script.py ") - sys.exit(1) - - file_name = sys.argv[1] - - with open(file_name) as f: - lines = f.readlines() - - return lines - - -def extract_errors(lines): - results = [] - logger.info("Extarcting input and output") - for i in range(len(lines)): - if "failed" in lines[i]: - context = lines[max(0, i - 2) : i + 1] - - model = "" - input_val = "" - output = "" - - for line in context: - if "Normalizing" in line: - parts_pipe = line.split("|") - if len(parts_pipe) > 2: - model = parts_pipe[2].strip() - - parts_colon = line.split(":") - if len(parts_colon) > 3: - input_val = parts_colon[3].strip() - - if "SimulationTime" in line: - parts_colon = line.split(":") - if len(parts_colon) > 3: - output = parts_colon[3].strip() - - if "failed after" in line: - output = "failed after 3 attempts" - - results.append([model, input_val, output]) - - return results - - -def compute_occurrences(df): - counts = df.value_counts().reset_index() - counts.columns = ["model", "input", "result", "count"] - return counts - - -def add_model_totals(df): - df["total"] = df.groupby("model")["count"].transform("sum") - return df - - -def sort_results(df): - return df.sort_values(by=["total", "count"], ascending=False) - - -def save_to_tsv(df, output_path="results/norm_simu_times/llm_error_count.tsv"): - df.to_csv(output_path, sep="\t", index=False) - - -def main(): - lines = check_argument() - - data = extract_errors(lines) - df = pd.DataFrame(data, columns=["model", "input", "result"]) - - occurrences = compute_occurrences(df) - occurrences = add_model_totals(occurrences) - sorted_df = sort_results(occurrences) - sorted_df = sorted_df[["total", "count", "model", "input", "result"]] - - print(sorted_df) - save_to_tsv(sorted_df) - - -if __name__ == "__main__": - main() diff --git a/src/mdverse_entity_norm/scripts/normalize_temperature.py b/src/mdverse_entity_norm/scripts/normalize_stemp.py similarity index 69% rename from src/mdverse_entity_norm/scripts/normalize_temperature.py rename to src/mdverse_entity_norm/scripts/normalize_stemp.py index e482917..fb4437c 100644 --- a/src/mdverse_entity_norm/scripts/normalize_temperature.py +++ b/src/mdverse_entity_norm/scripts/normalize_stemp.py @@ -1,11 +1,9 @@ -"""Module for using regex. - -This module provides regular expression matching operations. -""" +"""Script to normalize simulation temperature entities.""" import re +from pathlib import Path -import matplotlib.pyplot as plt +import click import pandas as pd from loguru import logger @@ -35,7 +33,7 @@ def norm_temp(temp_str: str) -> tuple: # an optional unit because of the "?" symbol at the end of the group. # This group consists of zero or more spaces, because of the "*" symbol, # an optional degree symbol, then zero or more spaces, and zero or more letters. - logger.info("Normalising temperature entities ...") + logger.info("Normalising temperature entities...") # If the temperatue is anotated as room temperature or body temperature # we normalize it to the standard value if temp_str == "room temperature": @@ -71,19 +69,22 @@ def norm_temp(temp_str: str) -> tuple: return temperature_value, temperature_unit -def create_norm_temp_file(raw_temp_file: str, norm_temp_file: str): +def create_norm_temp_file(raw_temp_file: Path, norm_temp_file: Path) -> None: """Create a .tsv file containing the raw temperature value. the normalised temperature value and the normalised unit. Parameters ---------- - raw_temp_file (str) : name of the input file containing the raw values - norm_temp_file (str) : name of the input file with the normalised informations + raw_temp_file (Path) : path to the input file containing the raw values + norm_temp_file (Path) : path to the input file with the normalised informations """ df = pd.read_csv(raw_temp_file, sep="\t") - temp_entities = df[df["category"] == "TEMP"]["entity"].tolist() + temp_entities = df[df["category"] == "STEMP"]["entity"].tolist() + + if not norm_temp_file.parent.exists(): + norm_temp_file.parent.mkdir(parents=True, exist_ok=True) with open(norm_temp_file, "w") as f2: f2.write( @@ -95,47 +96,28 @@ def create_norm_temp_file(raw_temp_file: str, norm_temp_file: str): if temperature_value is not None: f2.write( - f"{raw_temp}\t{temperature_value}\t{temperature_unit}\t{str(temperature_value) + temperature_unit}\n" + f"{raw_temp}\t{temperature_value}\t{temperature_unit}" + f"\t{str(temperature_value) + temperature_unit}\n" ) else: f2.write(f"{raw_temp}\tERROR\tERROR\tERROR\n") -def visualize_entity_count(file_path): - - temp_normalisation_results = pd.read_csv(file_path, sep="\t") - - before = len(temp_normalisation_results["raw_temperature"].unique()) - after = len(temp_normalisation_results["normalized_result"].unique()) - - plt.figure(figsize=(6, 5)) - - labels = ["Before Grounding", "After Grounding"] - values = [before, after] - - plt.bar(labels, values) - - plt.ylabel("Number of unique temperatures") - plt.title("Unique Temperature Count") - plt.tight_layout() - plt.savefig("results/norm_temp/entity_count.png") +@click.command() +@click.option( + "--raw-entities-path", + type=click.Path(exists=True, dir_okay=False, path_type=Path), + help="Path to the input file containing raw temperature entities.", +) +@click.option( + "--normalized-stemp-path", + type=click.Path(dir_okay=False, path_type=Path), + help="Path to the output file for normalized temperature entities.", +) +def main(raw_entities_path: Path, normalized_stemp_path: Path): + """Normalize all the temperature entities in the input file and visualization.""" + create_norm_temp_file(raw_entities_path, normalized_stemp_path) if __name__ == "__main__": - # Testing different cases of temp normalisation - examples_temperature = [ - "300", - "300 k", - "27", - "300k", - "0c", - "37 celsius", - "37°C", - "310.15°K", - "20 Celsius", - ] - for temperature in examples_temperature: - print(f"norm_temp('{temperature}') = {norm_temp(temperature)}") - - create_norm_temp_file("data/entities.tsv", "results/norm_temp.tsv") - visualize_entity_count("results/norm_temp.tsv") + main() diff --git a/src/mdverse_entity_norm/scripts/normalize_stime_wth_llm.py b/src/mdverse_entity_norm/scripts/normalize_stime_wth_llm.py index 9c1f3e2..12b6e53 100644 --- a/src/mdverse_entity_norm/scripts/normalize_stime_wth_llm.py +++ b/src/mdverse_entity_norm/scripts/normalize_stime_wth_llm.py @@ -6,13 +6,10 @@ import click import pandas as pd -from mdverse_entity_norm.scripts.normalize_simulation_time import ( +from mdverse_entity_norm.scripts.evaluate_llm_models import ( normalize_simulation_time, ) -MODEL_NAME = "deepseek/deepseek-v4-pro" -PROMPT_PATH = Path("data/llm_prompt.txt") - UNITS_TO_NS = { "ps": 1e-3, "ns": 1, @@ -39,9 +36,7 @@ def get_stime_entities(entities_file: Path) -> pd.DataFrame: return stime_entities -def normalize_row( - raw_time, model_name: str = MODEL_NAME, prompt_path: Path = PROMPT_PATH -) -> str | None: +def normalize_row(raw_time, model_name: str, prompt_path: Path) -> str | None: """Normalize a single raw simulation time string using the LLM. Parameters @@ -66,21 +61,22 @@ def normalize_row( def normalize_dataframe_times( stime_entities: pd.DataFrame, + prompt_path: Path, + model_name: str, column: str = "entity", ) -> pd.DataFrame: """Apply LLM normalization to the specified column of the DataFrame. - Parameters - ---------- - stime_entities: DataFrame containing the entities to normalize. - column: The name of the column containing the raw simulation time strings. - Returns ------- pd.DataFrame: The input DataFrame with an additional column 'normalized_time' containing the normalized values. """ - stime_entities["normalized_time"] = stime_entities[column].apply(normalize_row) + stime_entities["normalized_time"] = stime_entities[column].apply( + lambda x: normalize_row( + raw_time=x, model_name=model_name, prompt_path=prompt_path + ) + ) return stime_entities @@ -136,29 +132,36 @@ def save_results_to_tsv(results: list, output_file: Path): @click.command() @click.option( - "--entities-file", + "--entities-path", type=click.Path(exists=True, dir_okay=False, path_type=Path), - default=Path("../data/ground_entities.tsv"), help="Path to the TSV file containing the entities.", ) @click.option( - "--output-file", + "--normalization-results-path", type=click.Path(dir_okay=False, path_type=Path), - default=Path("../results/norm_simu_times/normalized_stime_results.tsv"), help="Path to the output TSV file for normalized results.", ) -def main(entities_file: Path, output_file: Path): - """Execute the normalization process. - - Parameters - ---------- - entities_file: Path to the TSV file containing the entities. - output_file: Path to the output TSV file for normalized results. - """ - stime_entities = get_stime_entities(entities_file) - stime_entities = normalize_dataframe_times(stime_entities) +@click.option( + "--prompt-path", + type=click.Path(file_okay=True, path_type=Path), + help="Path to the llm prompt file", +) +@click.option( + "--model-name", + type=str, + help="Name of the LLM model to use for normalization.", +) +def main( + entities_path: Path, + normalization_results_path: Path, + prompt_path: Path, + model_name: str, +): + """Execute the normalization process.""" + stime_entities = get_stime_entities(entities_path) + stime_entities = normalize_dataframe_times(stime_entities, prompt_path, model_name) results = get_llm_normalization_results(stime_entities) - save_results_to_tsv(results, output_file) + save_results_to_tsv(results, normalization_results_path) if __name__ == "__main__": diff --git a/src/mdverse_entity_norm/scripts/scrap_lipids.py b/src/mdverse_entity_norm/scripts/scrap_lipids.py deleted file mode 100644 index a5a9ba8..0000000 --- a/src/mdverse_entity_norm/scripts/scrap_lipids.py +++ /dev/null @@ -1,411 +0,0 @@ -"""Scraps CSML molecules""" - -import csv -import os -import re -from datetime import UTC, datetime -from pathlib import Path - -import click -import httpx -import pandas as pd -from bs4 import BeautifulSoup -from ground_molecule import call_chebi, call_pubchem -from loguru import logger - -URL = "https://charmm-gui.org/?doc=archive&lib=csml" -PAGE = httpx.get(URL) -SOUP = BeautifulSoup(PAGE.content, "html.parser") - - -def get_lipid_family_name(th) -> str | None: - """Give the family name from a tag. - - Parameters - ---------- - th : beautifulsoup tag - Represent the element. - - Returns - ------- - str | None - The text in bold -> the family name - """ - bold_tag = th.find("b") - if bold_tag: - return bold_tag.get_text(strip=True) - return None - - -def build_grounding_name(short_name, long_name): - """Build the grounding_name from the short_name and long_name. - - Parameters - ---------- - short_name : str - The short name of the lipid/residue. - long_name : str - The long name of the lipid/residue - - Returns - ------- - str - The grounding name, built from the short name and the long name(cleaned). - """ - long_name = long_name.replace("+ LONEPAIR", "").strip() - long_name = long_name.replace("+ LONE PAIR", "").strip() - elements = [] - formula_regex = re.compile(r"^[A-Z]+[0-9]+([A-Z]{1,2}[0-9]*)*$") - - # We split the long name on ", " - if long_name: - for e in long_name.split(", "): - if e: - elements.append(e) - - formula = None - full_name = None - - if len(elements) == 0: - pass - - # If there is only one element, we check if it's a formula or a full name - elif len(elements) == 1: - if formula_regex.match(elements[0]): - formula = elements[0] - else: - logger.info(f"For {elements} adding full name '{elements[0]}'") - full_name = elements[0] - - else: - # If there are multiple elements, we check if the first one is a formula or a full name - if formula_regex.match(elements[0]): - formula = elements[0] - logger.info(f"For {elements} adding full name '{elements[0]}'") - - full_name = elements[1] - else: - logger.info(f"For {elements} adding full name '{elements[0]}'") - full_name = elements[0] - - # We create the grounding name - grounding_name = [] - # if short_name: - # grounding_name.append(short_name) - # if formula: - # grounding_name.append(formula) - if full_name: - logger.info( - f"Adding full name '{full_name}' to grounding name for short name '{short_name}'" - ) - grounding_name.append(full_name) - - return " ".join(grounding_name), formula - - -def parse_data_row(row, current_family): - """Extract short name and long name from a data row. - - Parameters - ---------- - row : beautifulsoup tag - Represent the element containing the data ( = row). - current_family : str - The family name associated with the current data row, extracted from the header. - - Returns - ------- - dict | None - A dictionary with keys 'family', 'short_name', 'long_name', and 'grounding_name' - or None if there is not valid informations. - """ - cells = row.find_all("td") - if len(cells) < 2: - return None - - # We extract the short name from the first cell. If it's empty, we skip this row. - short_name = cells[0].get_text(strip=True) - if not short_name: - return None - # looks for a tag inside the second cell to extract the long name, - # because some long names are in a tag. - font_tag = cells[1].find("font") - if font_tag: - long_name = font_tag.get_text(strip=True) - else: - long_name = cells[1].get_text(strip=True) - if not long_name: - return None - - query_name, formula = build_grounding_name(short_name, long_name) - - return { - "short_name": short_name, - "formula": formula, - "long_name": long_name, - "query_name": query_name, - } - - -def scrape_table(soup): - """Scrape the table from the webpage and extract the relevant data. - - Parameters - ---------- - soup : BeautifulSoup - The BeautifulSoup object representing the parsed HTML content of the webpage. - - Returns - ------- - list of dict - A list of dictionaries, each containing the family, short name, long name, - and grounding name for each lipid/residue entry found in the table. - """ - results = [] - for header_th in soup.find_all("th", class_="header"): - current_family = get_lipid_family_name(header_th) - if not current_family: - continue - - tbody = header_th.find_next_sibling("tbody") - if not tbody: - continue - - for row in tbody.find_all("tr"): - entry = parse_data_row(row, current_family) - if entry: - results.append(entry) - - return results - - -def save_to_csv(results, filename): - """Save the scrapped data to a CSV file. - - Parameters - ---------- - results : list of dict - The list of dictionaries containing the scrapped data. - filename : Path - The path to the output CSV file where the scraped data will be saved. - """ - Path(filename).parent.mkdir(parents=True, exist_ok=True) - scrapping_results = pd.DataFrame(results) - scrapping_results.to_csv(filename, index=False) - - -def ground_one(grounding_name: str) -> dict: - """Try to ground a single grounding_name via ChEBI then PubChem. - - Parameters - ---------- - grounding_name : str - The grounding name to look up. - - Returns - ------- - dict - A dict with keys: MOL, MOL_TYPE, ERRORS, MOL_ID, MOL_SCORE, MOL_FULL_NAME - """ - result = call_chebi(grounding_name) - if result and "error" not in result: - return { - "MOL": grounding_name, - "MOL_TYPE": "CHEBI", - "ERRORS": "No errors", - "MOL_ID": result.get("chebi_id", "Not Available"), - "MOL_SCORE": result.get("score", "Not Available"), - "MOL_FULL_NAME": result.get("name", "Not Available"), - } - - result = call_pubchem(grounding_name) - if result and "error" not in result: - return { - "MOL": grounding_name, - "MOL_TYPE": "PUBCHEM", - "ERRORS": "No errors", - "MOL_ID": result.get("id", "Not Available"), - "MOL_SCORE": "Not Available", - "MOL_FULL_NAME": result.get("name", "Not Available"), - } - - return { - "MOL": grounding_name, - "MOL_TYPE": "Not found", - "ERRORS": "No match in ChEBI or PubChem", - "MOL_ID": "Not Available", - "MOL_SCORE": "Not Available", - "MOL_FULL_NAME": "Not Available", - } - - -def ground_both( - grounding_name: str, short_name: str, long_name: str, formula: str -) -> dict: - """Ground a single grounding_name via ChEBI and PubChem. - - Parameters - ---------- - grounding_name : str - The grounding name to look up. - short_name : str - The short name of the lipid/residue. - long_name : str - The long name of the lipid/residue. - formula : str - The chemical formula of the lipid/residue. - - Returns - ------- - dict - A dict with keys: short_name, formula, long_name, chebi_query, pubchem_query, - chebi_id, chebi_score, chebi_name, pubchem_id, pubchem_name - """ - chebi = call_chebi(grounding_name) or {} - pubchem = call_pubchem(grounding_name) or {} - - chebi_ok = ( - chebi.get("chebi_id") and chebi.get("formula", "Not Available") == formula - ) - pubchem_ok = ( - pubchem.get("id") - and pubchem.get("molecular_formula", "Not Available") == formula - ) - - if not chebi_ok: - if short_name != grounding_name: - chebi_fallback = call_chebi(short_name) or {} - if ( - chebi_fallback.get("chebi_id") - and chebi_fallback.get("formula", "Not Available") == formula - ): - chebi = chebi_fallback - chebi_ok = True - chebi_query = short_name - else: - chebi_query = grounding_name - - if not pubchem_ok: - if short_name != grounding_name: - pubchem_fallback = call_pubchem(short_name) or {} - if ( - pubchem_fallback.get("id") - and pubchem_fallback.get("molecular_formula", "Not Available") - == formula - ): - pubchem = pubchem_fallback - pubchem_ok = True - pubchem_query = short_name - else: - pubchem_query = grounding_name - - return { - "short_name": short_name, - "formula": formula, - "long_name": long_name, - "chebi_query": chebi_query, - "pubchem_query": pubchem_query, - "chebi_id": chebi.get("chebi_id", "Not found") if chebi_ok else "Not found", - "chebi_score": chebi.get("score", "Not found") if chebi_ok else "Not found", - "chebi_name": chebi.get("name", "Not found") if chebi_ok else "Not found", - "pubchem_id": pubchem.get("id", "Not found") if pubchem_ok else "Not found", - "pubchem_name": pubchem.get("name", "Not found") if pubchem_ok else "Not found", - } - - -def _grounding_sort_key(entry: dict) -> int: - """Sort grounding results, prioritizing entries that were found in either ChEBI or PubChem. - - Parameters - ---------- - entry : dict - A dict with keys: query_name, chebi_id, chebi_score, chebi_name, pubchem_id, pubchem_name - - Returns - ------- - int - 0 if the entry was found in either ChEBI or PubChem, 1 otherwise. - """ - found = entry["chebi_id"] != "Not found" or entry["pubchem_id"] != "Not found" - return 0 if found else 1 - - -def save_grounding_to_tsv(grounding_results: list[dict], output_file: Path) -> None: - """Save grounding results to a TSV file. - - Parameters - ---------- - grounding_results : list[dict] - List of grounding result dicts, one per lipid. - output_file : Path - Path to the output TSV file. - """ - Path(output_file).parent.mkdir(parents=True, exist_ok=True) - with open(output_file, "w", newline="") as f: - writer = csv.DictWriter( - f, - fieldnames=[ - "short_name", - "formula", - "long_name", - "chebi_query", - "pubchem_query", - "chebi_id", - "chebi_score", - "chebi_name", - "pubchem_id", - "pubchem_name", - ], - delimiter="\t", - ) - writer.writeheader() - writer.writerows(grounding_results) - - -@click.command() -@click.option( - "--scraped_file", - default="results/lipid_scrapping/csml_lipids.csv", - type=click.Path(file_okay=True, path_type=Path), - help="Path to the input file containing molecular identifiers", -) -@click.option( - "--grounded_file", - default="results/lipid_scrapping/csml_lipids_grounded.tsv", - type=click.Path(file_okay=True, path_type=Path), - help="Path to the output TSV file with grounding results", -) -def scrap_lipids(scraped_file, grounded_file): - """Scrape the lipid/residue data from the webpage and save it to a CSV file. - - Parameters - ---------- - scraped_file : Path - The path to the output CSV file where the scraped data will be saved. - grounded_file : Path - The path to the output TSV file where the grounding results will be saved. - """ - lipid_scrapping = scrape_table(SOUP) - save_to_csv(lipid_scrapping, scraped_file) - - lipid_grounding_results = [] - for entry in lipid_scrapping: - grounded = ground_both( - grounding_name=entry["query_name"], - short_name=entry["short_name"], - formula=entry["formula"], - long_name=entry["long_name"], - ) - lipid_grounding_results.append(grounded) - lipid_grounding_results.sort(key=_grounding_sort_key) - save_grounding_to_tsv(lipid_grounding_results, grounded_file) - - -if __name__ == "__main__": - timestamp = datetime.now(UTC).strftime("%Y%m%d_%H%M%S") - os.makedirs("logs", exist_ok=True) - logger.add( - f"logs/ground_molecule_{timestamp}.log", - level="DEBUG", - ) - scrap_lipids()