mat-ret 0.1.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {mat_ret-0.1.0 → mat_ret-0.3.0}/PKG-INFO +49 -5
- {mat_ret-0.1.0 → mat_ret-0.3.0}/README.md +46 -4
- {mat_ret-0.1.0 → mat_ret-0.3.0}/pyproject.toml +7 -2
- {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret/__init__.py +6 -0
- {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret/api.py +83 -0
- {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret/databases.py +217 -2
- mat_ret-0.3.0/src/mat_ret/gui/__init__.py +20 -0
- mat_ret-0.3.0/src/mat_ret/gui/__main__.py +11 -0
- mat_ret-0.3.0/src/mat_ret/gui/main.py +110 -0
- mat_ret-0.3.0/src/mat_ret/gui/main_window.py +549 -0
- mat_ret-0.3.0/src/mat_ret/gui/utils.py +417 -0
- mat_ret-0.3.0/src/mat_ret/gui/widgets/__init__.py +11 -0
- mat_ret-0.3.0/src/mat_ret/gui/widgets/database_selector.py +506 -0
- mat_ret-0.3.0/src/mat_ret/gui/widgets/results_view.py +611 -0
- mat_ret-0.3.0/src/mat_ret/gui/widgets/structure_viewer.py +1041 -0
- mat_ret-0.3.0/src/mat_ret/gui/workers.py +234 -0
- mat_ret-0.3.0/src/mat_ret/optimade/__init__.py +9 -0
- mat_ret-0.3.0/src/mat_ret/optimade/cli.py +114 -0
- mat_ret-0.3.0/src/mat_ret/optimade/harvester.py +422 -0
- mat_ret-0.3.0/src/mat_ret/optimade/registry.py +189 -0
- {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret/property_mapping.py +10 -0
- {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret.egg-info/PKG-INFO +49 -5
- mat_ret-0.3.0/src/mat_ret.egg-info/SOURCES.txt +27 -0
- mat_ret-0.3.0/src/mat_ret.egg-info/entry_points.txt +2 -0
- {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret.egg-info/requires.txt +2 -0
- mat_ret-0.1.0/src/mat_ret.egg-info/SOURCES.txt +0 -12
- {mat_ret-0.1.0 → mat_ret-0.3.0}/LICENSE +0 -0
- {mat_ret-0.1.0 → mat_ret-0.3.0}/setup.cfg +0 -0
- {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret.egg-info/dependency_links.txt +0 -0
- {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: mat_ret
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Unified retrieval and property mapping for materials databases (Materials Project, JARVIS, AFLOW, Alexandria, Materials Cloud, MPDS)
|
|
5
5
|
Author-email: Aadhityan A <aadhityan1995@gmail.com>
|
|
6
6
|
License: CeCILL FREE SOFTWARE LICENSE AGREEMENT
|
|
@@ -532,6 +532,7 @@ Requires-Dist: scipy
|
|
|
532
532
|
Requires-Dist: mp-api
|
|
533
533
|
Requires-Dist: jarvis-tools
|
|
534
534
|
Requires-Dist: optimade[http_client]
|
|
535
|
+
Requires-Dist: zstandard
|
|
535
536
|
Requires-Dist: ase
|
|
536
537
|
Requires-Dist: aflow
|
|
537
538
|
Requires-Dist: matplotlib
|
|
@@ -539,6 +540,7 @@ Requires-Dist: pandas
|
|
|
539
540
|
Requires-Dist: tqdm
|
|
540
541
|
Requires-Dist: urllib3
|
|
541
542
|
Requires-Dist: mpds-client
|
|
543
|
+
Requires-Dist: PyQt6>=6.4.0
|
|
542
544
|
Dynamic: license-file
|
|
543
545
|
|
|
544
546
|
# mat_ret
|
|
@@ -554,6 +556,7 @@ This project is intended to extract materials data from various databases (see t
|
|
|
554
556
|
5. MATERIALS CLOUD
|
|
555
557
|
6. MPDS
|
|
556
558
|
7. OQMD
|
|
559
|
+
8. OPTIMADE providers (registry search)
|
|
557
560
|
|
|
558
561
|
## Installation
|
|
559
562
|
|
|
@@ -616,6 +619,10 @@ if results:
|
|
|
616
619
|
for key, value in entry.items():
|
|
617
620
|
if key != 'structure':
|
|
618
621
|
print(f"{key}: {value}")
|
|
622
|
+
|
|
623
|
+
## OPTIMADE Search
|
|
624
|
+
|
|
625
|
+
mat_ret can query OPTIMADE providers through the registry when you select **OPTIMADE** in the database list. The GUI shows OPTIMADE providers as a tree: select the parent to toggle all providers or pick specific providers. The same formula filter (e.g., `MgO`) applies, and the **limit per DB** is applied **per provider**.
|
|
619
626
|
```
|
|
620
627
|
|
|
621
628
|
## Running in a Virtual Environment (.venv)
|
|
@@ -636,6 +643,34 @@ Then run scripts using the environment's Python interpreter:
|
|
|
636
643
|
.venv/bin/python comprehensive_database_test.py --formula Al2O3 --limit 2
|
|
637
644
|
```
|
|
638
645
|
|
|
646
|
+
## Graphical User Interface (GUI)
|
|
647
|
+
|
|
648
|
+
mat_ret includes a modern PyQt6-based graphical interface for easy materials retrieval.
|
|
649
|
+
|
|
650
|
+

|
|
651
|
+
|
|
652
|
+
### Launching the GUI
|
|
653
|
+
|
|
654
|
+
```bash
|
|
655
|
+
# After installation, use the command-line entry point:
|
|
656
|
+
mat-ret-gui
|
|
657
|
+
|
|
658
|
+
# Or using Python module syntax:
|
|
659
|
+
python -m mat_ret.gui
|
|
660
|
+
```
|
|
661
|
+
|
|
662
|
+
### GUI Features
|
|
663
|
+
|
|
664
|
+
- **Database Selection**: Checkbox interface to select which databases to query
|
|
665
|
+
- **OPTIMADE Provider Tree**: Select OPTIMADE providers individually or all at once
|
|
666
|
+
- **Composition Search**: Enter any chemical formula (e.g., MgO, Fe2O3, LiFePO4)
|
|
667
|
+
- **Results Table**: Interactive table showing material properties from all selected databases
|
|
668
|
+
- **Structure Viewer**: 3D/2D visualization of crystal structures
|
|
669
|
+
- Multiple view projections (3D, XY, XZ, YZ planes)
|
|
670
|
+
- Adjustable atom sizes
|
|
671
|
+
- Toggle bonds and unit cell display
|
|
672
|
+
- **Export Options**: Save results as JSON/CSV, export structures as CIF files
|
|
673
|
+
|
|
639
674
|
## Project Structure
|
|
640
675
|
|
|
641
676
|
```
|
|
@@ -644,10 +679,19 @@ mat_ret/
|
|
|
644
679
|
│ ├── api.py # High-level fetch helpers
|
|
645
680
|
│ ├── property_mapping.py # Mapping config and helpers
|
|
646
681
|
│ └── databases.py # Database client implementations
|
|
682
|
+
├── gui/ # Graphical user interface
|
|
683
|
+
│ ├── main.py # GUI entry point
|
|
684
|
+
│ ├── main_window.py # Main application window
|
|
685
|
+
│ ├── workers.py # Async fetch workers
|
|
686
|
+
│ ├── utils.py # Utility functions and styling
|
|
687
|
+
│ └── widgets/ # UI widgets
|
|
688
|
+
│ ├── database_selector.py # Database selection panel
|
|
689
|
+
│ ├── results_view.py # Results table and JSON view
|
|
690
|
+
│ └── structure_viewer.py # Crystal structure visualization
|
|
647
691
|
├── doc/ # Documentation assets (CSV, guides)
|
|
648
|
-
├── example_fetch.py # Demo script intended to
|
|
649
|
-
├── example_single_fetch.py
|
|
650
|
-
├── README
|
|
692
|
+
├── example_fetch.py # Demo script intended to retrieve information from all databases
|
|
693
|
+
├── example_single_fetch.py # Demo script intended to retrieve information from one database
|
|
694
|
+
├── README.md # Project overview
|
|
651
695
|
├── requirements.txt # Python dependencies
|
|
652
696
|
└── LICENSE # CeCILL license
|
|
653
697
|
```
|
|
@@ -658,4 +702,4 @@ Keep in touch to contribute !!!
|
|
|
658
702
|
We welcome others to develop/fix the functionalities of this python library with these existing databases and/or provide new databases.
|
|
659
703
|
https://github.com/Aadhityan-A/mat_ret
|
|
660
704
|
|
|
661
|
-
*Note:* It's still in the developing phase. If you face any issues let us know through github issues. Also there are some lines of code not in use are
|
|
705
|
+
*Note:* It's still in the developing phase. If you face any issues let us know through github issues. Also there are some lines of code not in use are for future development purpose.
|
|
@@ -11,6 +11,7 @@ This project is intended to extract materials data from various databases (see t
|
|
|
11
11
|
5. MATERIALS CLOUD
|
|
12
12
|
6. MPDS
|
|
13
13
|
7. OQMD
|
|
14
|
+
8. OPTIMADE providers (registry search)
|
|
14
15
|
|
|
15
16
|
## Installation
|
|
16
17
|
|
|
@@ -73,6 +74,10 @@ if results:
|
|
|
73
74
|
for key, value in entry.items():
|
|
74
75
|
if key != 'structure':
|
|
75
76
|
print(f"{key}: {value}")
|
|
77
|
+
|
|
78
|
+
## OPTIMADE Search
|
|
79
|
+
|
|
80
|
+
mat_ret can query OPTIMADE providers through the registry when you select **OPTIMADE** in the database list. The GUI shows OPTIMADE providers as a tree: select the parent to toggle all providers or pick specific providers. The same formula filter (e.g., `MgO`) applies, and the **limit per DB** is applied **per provider**.
|
|
76
81
|
```
|
|
77
82
|
|
|
78
83
|
## Running in a Virtual Environment (.venv)
|
|
@@ -93,6 +98,34 @@ Then run scripts using the environment's Python interpreter:
|
|
|
93
98
|
.venv/bin/python comprehensive_database_test.py --formula Al2O3 --limit 2
|
|
94
99
|
```
|
|
95
100
|
|
|
101
|
+
## Graphical User Interface (GUI)
|
|
102
|
+
|
|
103
|
+
mat_ret includes a modern PyQt6-based graphical interface for easy materials retrieval.
|
|
104
|
+
|
|
105
|
+

|
|
106
|
+
|
|
107
|
+
### Launching the GUI
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
# After installation, use the command-line entry point:
|
|
111
|
+
mat-ret-gui
|
|
112
|
+
|
|
113
|
+
# Or using Python module syntax:
|
|
114
|
+
python -m mat_ret.gui
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### GUI Features
|
|
118
|
+
|
|
119
|
+
- **Database Selection**: Checkbox interface to select which databases to query
|
|
120
|
+
- **OPTIMADE Provider Tree**: Select OPTIMADE providers individually or all at once
|
|
121
|
+
- **Composition Search**: Enter any chemical formula (e.g., MgO, Fe2O3, LiFePO4)
|
|
122
|
+
- **Results Table**: Interactive table showing material properties from all selected databases
|
|
123
|
+
- **Structure Viewer**: 3D/2D visualization of crystal structures
|
|
124
|
+
- Multiple view projections (3D, XY, XZ, YZ planes)
|
|
125
|
+
- Adjustable atom sizes
|
|
126
|
+
- Toggle bonds and unit cell display
|
|
127
|
+
- **Export Options**: Save results as JSON/CSV, export structures as CIF files
|
|
128
|
+
|
|
96
129
|
## Project Structure
|
|
97
130
|
|
|
98
131
|
```
|
|
@@ -101,10 +134,19 @@ mat_ret/
|
|
|
101
134
|
│ ├── api.py # High-level fetch helpers
|
|
102
135
|
│ ├── property_mapping.py # Mapping config and helpers
|
|
103
136
|
│ └── databases.py # Database client implementations
|
|
137
|
+
├── gui/ # Graphical user interface
|
|
138
|
+
│ ├── main.py # GUI entry point
|
|
139
|
+
│ ├── main_window.py # Main application window
|
|
140
|
+
│ ├── workers.py # Async fetch workers
|
|
141
|
+
│ ├── utils.py # Utility functions and styling
|
|
142
|
+
│ └── widgets/ # UI widgets
|
|
143
|
+
│ ├── database_selector.py # Database selection panel
|
|
144
|
+
│ ├── results_view.py # Results table and JSON view
|
|
145
|
+
│ └── structure_viewer.py # Crystal structure visualization
|
|
104
146
|
├── doc/ # Documentation assets (CSV, guides)
|
|
105
|
-
├── example_fetch.py # Demo script intended to
|
|
106
|
-
├── example_single_fetch.py
|
|
107
|
-
├── README
|
|
147
|
+
├── example_fetch.py # Demo script intended to retrieve information from all databases
|
|
148
|
+
├── example_single_fetch.py # Demo script intended to retrieve information from one database
|
|
149
|
+
├── README.md # Project overview
|
|
108
150
|
├── requirements.txt # Python dependencies
|
|
109
151
|
└── LICENSE # CeCILL license
|
|
110
152
|
```
|
|
@@ -115,4 +157,4 @@ Keep in touch to contribute !!!
|
|
|
115
157
|
We welcome others to develop/fix the functionalities of this python library with these existing databases and/or provide new databases.
|
|
116
158
|
https://github.com/Aadhityan-A/mat_ret
|
|
117
159
|
|
|
118
|
-
*Note:* It's still in the developing phase. If you face any issues let us know through github issues. Also there are some lines of code not in use are
|
|
160
|
+
*Note:* It's still in the developing phase. If you face any issues let us know through github issues. Also there are some lines of code not in use are for future development purpose.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "mat_ret"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Unified retrieval and property mapping for materials databases (Materials Project, JARVIS, AFLOW, Alexandria, Materials Cloud, MPDS)"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Aadhityan A", email = "aadhityan1995@gmail.com" }
|
|
@@ -20,15 +20,20 @@ dependencies = [
|
|
|
20
20
|
"mp-api",
|
|
21
21
|
"jarvis-tools",
|
|
22
22
|
"optimade[http_client]",
|
|
23
|
+
"zstandard",
|
|
23
24
|
"ase",
|
|
24
25
|
"aflow",
|
|
25
26
|
"matplotlib",
|
|
26
27
|
"pandas",
|
|
27
28
|
"tqdm",
|
|
28
29
|
"urllib3",
|
|
29
|
-
"mpds-client"
|
|
30
|
+
"mpds-client",
|
|
31
|
+
"PyQt6>=6.4.0"
|
|
30
32
|
]
|
|
31
33
|
|
|
34
|
+
[project.scripts]
|
|
35
|
+
mat-ret-gui = "mat_ret.gui:main"
|
|
36
|
+
|
|
32
37
|
[project.urls]
|
|
33
38
|
Homepage = "https://github.com/Aadhityan-A/mat_ret"
|
|
34
39
|
BugTracker = "https://github.com/Aadhityan-A/mat_ret/issues"
|
|
@@ -28,7 +28,10 @@ from .api import (
|
|
|
28
28
|
fetch_materials_cloud,
|
|
29
29
|
fetch_oqmd,
|
|
30
30
|
fetch_mpds,
|
|
31
|
+
fetch_optimade,
|
|
31
32
|
fetch_all_databases,
|
|
33
|
+
list_optimade_providers,
|
|
34
|
+
harvest_optimade,
|
|
32
35
|
)
|
|
33
36
|
|
|
34
37
|
__all__ = [
|
|
@@ -55,5 +58,8 @@ __all__ = [
|
|
|
55
58
|
"fetch_materials_cloud",
|
|
56
59
|
"fetch_oqmd",
|
|
57
60
|
"fetch_mpds",
|
|
61
|
+
"fetch_optimade",
|
|
58
62
|
"fetch_all_databases",
|
|
63
|
+
"list_optimade_providers",
|
|
64
|
+
"harvest_optimade",
|
|
59
65
|
]
|
|
@@ -14,7 +14,10 @@ from .databases import (
|
|
|
14
14
|
MaterialsDatabaseRetriever,
|
|
15
15
|
MaterialsProjectClient,
|
|
16
16
|
OQMDClient,
|
|
17
|
+
OptimadeSearchClient,
|
|
17
18
|
)
|
|
19
|
+
from .optimade.harvester import OptimadeHarvester
|
|
20
|
+
from .optimade.registry import fetch_registry_links
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
def _sanitize_output_directory(output_directory: Optional[Path]) -> Optional[Path]:
|
|
@@ -109,6 +112,86 @@ def fetch_mpds(
|
|
|
109
112
|
return client.get_structures(formula, limit=limit)
|
|
110
113
|
|
|
111
114
|
|
|
115
|
+
def fetch_optimade(
|
|
116
|
+
formula: str,
|
|
117
|
+
*,
|
|
118
|
+
limit: int = 10,
|
|
119
|
+
output_directory: Optional[Path] = None,
|
|
120
|
+
registry_url: Optional[str] = None,
|
|
121
|
+
providers: Optional[List[Dict[str, str]]] = None,
|
|
122
|
+
) -> List[Dict]:
|
|
123
|
+
"""Retrieve structures from OPTIMADE providers."""
|
|
124
|
+
client = OptimadeSearchClient(
|
|
125
|
+
output_directory=_sanitize_output_directory(output_directory),
|
|
126
|
+
registry_url=registry_url,
|
|
127
|
+
providers=providers,
|
|
128
|
+
)
|
|
129
|
+
return client.get_structures(formula, limit=limit)
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def list_optimade_providers(
|
|
133
|
+
registry_url: Optional[str] = None,
|
|
134
|
+
cache_path: Optional[Path] = None,
|
|
135
|
+
) -> List[Dict]:
|
|
136
|
+
"""List OPTIMADE providers from the registry."""
|
|
137
|
+
if registry_url is None:
|
|
138
|
+
try:
|
|
139
|
+
import config # type: ignore
|
|
140
|
+
|
|
141
|
+
registry_url = getattr(config, "OPTIMADE_REGISTRY_URL", None)
|
|
142
|
+
except ImportError:
|
|
143
|
+
registry_url = None
|
|
144
|
+
if not registry_url:
|
|
145
|
+
raise ValueError("OPTIMADE registry URL is required")
|
|
146
|
+
|
|
147
|
+
return fetch_registry_links(
|
|
148
|
+
registry_url,
|
|
149
|
+
cache_path=cache_path,
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def harvest_optimade(
|
|
154
|
+
output_directory: Path,
|
|
155
|
+
*,
|
|
156
|
+
registry_url: Optional[str] = None,
|
|
157
|
+
include_providers: Optional[List[str]] = None,
|
|
158
|
+
exclude_providers: Optional[List[str]] = None,
|
|
159
|
+
page_limit: int = 1000,
|
|
160
|
+
shard_size: int = 5000,
|
|
161
|
+
resume: bool = True,
|
|
162
|
+
request_timeout: int = 30,
|
|
163
|
+
rate_limit_per_host: float = 1.0,
|
|
164
|
+
) -> None:
|
|
165
|
+
"""Harvest all OPTIMADE providers into CIF + metadata shards."""
|
|
166
|
+
if registry_url is None:
|
|
167
|
+
try:
|
|
168
|
+
import config # type: ignore
|
|
169
|
+
|
|
170
|
+
registry_url = getattr(config, "OPTIMADE_REGISTRY_URL", None)
|
|
171
|
+
except ImportError:
|
|
172
|
+
registry_url = None
|
|
173
|
+
if not registry_url:
|
|
174
|
+
raise ValueError("OPTIMADE registry URL is required")
|
|
175
|
+
|
|
176
|
+
providers = fetch_registry_links(
|
|
177
|
+
registry_url,
|
|
178
|
+
include=include_providers,
|
|
179
|
+
exclude=exclude_providers,
|
|
180
|
+
request_timeout=request_timeout,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
harvester = OptimadeHarvester(
|
|
184
|
+
providers,
|
|
185
|
+
Path(output_directory),
|
|
186
|
+
page_limit=page_limit,
|
|
187
|
+
shard_size=shard_size,
|
|
188
|
+
resume=resume,
|
|
189
|
+
request_timeout=request_timeout,
|
|
190
|
+
rate_limit_per_host=rate_limit_per_host,
|
|
191
|
+
)
|
|
192
|
+
harvester.harvest()
|
|
193
|
+
|
|
194
|
+
|
|
112
195
|
def fetch_all_databases(
|
|
113
196
|
formula: str,
|
|
114
197
|
*,
|
|
@@ -59,6 +59,7 @@ from .property_mapping import (
|
|
|
59
59
|
STANDARD_PROPERTIES,
|
|
60
60
|
DATABASE_PROPERTY_MAPPINGS,
|
|
61
61
|
)
|
|
62
|
+
from .optimade.registry import fetch_registry_links
|
|
62
63
|
|
|
63
64
|
|
|
64
65
|
AFLOW_REST_URL = os.getenv("AFLOW_BASE_URL", "http://aflowlib.duke.edu/search/API/")
|
|
@@ -1043,6 +1044,213 @@ class MaterialsCloudClient(MaterialsDatabaseClient):
|
|
|
1043
1044
|
return str(cif_path)
|
|
1044
1045
|
|
|
1045
1046
|
|
|
1047
|
+
class OptimadeSearchClient(MaterialsDatabaseClient):
|
|
1048
|
+
"""Generic OPTIMADE client for formula-based searches across providers."""
|
|
1049
|
+
|
|
1050
|
+
def __init__(
|
|
1051
|
+
self,
|
|
1052
|
+
output_directory: Optional[Path] = None,
|
|
1053
|
+
*,
|
|
1054
|
+
registry_url: Optional[str] = None,
|
|
1055
|
+
providers: Optional[List[Dict[str, str]]] = None,
|
|
1056
|
+
request_timeout: int = 30,
|
|
1057
|
+
max_providers: Optional[int] = None,
|
|
1058
|
+
):
|
|
1059
|
+
super().__init__("optimade", output_directory=output_directory)
|
|
1060
|
+
self.request_timeout = request_timeout
|
|
1061
|
+
self.max_providers = max_providers
|
|
1062
|
+
self._providers: Optional[List[Dict[str, str]]] = None
|
|
1063
|
+
|
|
1064
|
+
if registry_url is None:
|
|
1065
|
+
try:
|
|
1066
|
+
import config # type: ignore
|
|
1067
|
+
|
|
1068
|
+
registry_url = getattr(config, "OPTIMADE_REGISTRY_URL", None)
|
|
1069
|
+
except ImportError:
|
|
1070
|
+
registry_url = None
|
|
1071
|
+
|
|
1072
|
+
self.registry_url = registry_url
|
|
1073
|
+
|
|
1074
|
+
if providers is not None:
|
|
1075
|
+
cleaned: List[Dict[str, str]] = []
|
|
1076
|
+
for provider in providers:
|
|
1077
|
+
if not isinstance(provider, dict):
|
|
1078
|
+
continue
|
|
1079
|
+
base_url = provider.get("base_url")
|
|
1080
|
+
if not base_url:
|
|
1081
|
+
continue
|
|
1082
|
+
provider_id = provider.get("id") or base_url
|
|
1083
|
+
provider_name = provider.get("name") or provider_id
|
|
1084
|
+
cleaned.append(
|
|
1085
|
+
{
|
|
1086
|
+
"id": provider_id,
|
|
1087
|
+
"name": provider_name,
|
|
1088
|
+
"base_url": base_url,
|
|
1089
|
+
}
|
|
1090
|
+
)
|
|
1091
|
+
self._providers = cleaned
|
|
1092
|
+
|
|
1093
|
+
def _normalize_formula(self, formula: str) -> str:
|
|
1094
|
+
try:
|
|
1095
|
+
return Composition(formula).reduced_formula
|
|
1096
|
+
except Exception:
|
|
1097
|
+
return formula
|
|
1098
|
+
|
|
1099
|
+
def _get_providers(self) -> List[Dict[str, str]]:
|
|
1100
|
+
if self._providers is not None:
|
|
1101
|
+
return self._providers
|
|
1102
|
+
|
|
1103
|
+
if not self.registry_url:
|
|
1104
|
+
self._providers = []
|
|
1105
|
+
return self._providers
|
|
1106
|
+
|
|
1107
|
+
try:
|
|
1108
|
+
self._providers = fetch_registry_links(
|
|
1109
|
+
self.registry_url,
|
|
1110
|
+
request_timeout=self.request_timeout,
|
|
1111
|
+
max_providers=self.max_providers,
|
|
1112
|
+
)
|
|
1113
|
+
except Exception as exc:
|
|
1114
|
+
print(f" OPTIMADE registry fetch failed: {exc}")
|
|
1115
|
+
self._providers = []
|
|
1116
|
+
|
|
1117
|
+
return self._providers
|
|
1118
|
+
|
|
1119
|
+
def _create_pymatgen_structure(self, attributes: Dict[str, Any]) -> Optional[PymatgenStructure]:
|
|
1120
|
+
required_keys = {"lattice_vectors", "cartesian_site_positions", "species_at_sites"}
|
|
1121
|
+
if not required_keys.issubset(attributes):
|
|
1122
|
+
return None
|
|
1123
|
+
|
|
1124
|
+
lattice_vectors = attributes["lattice_vectors"]
|
|
1125
|
+
cart_positions = attributes["cartesian_site_positions"]
|
|
1126
|
+
site_species = attributes["species_at_sites"]
|
|
1127
|
+
|
|
1128
|
+
try:
|
|
1129
|
+
from pymatgen.core.lattice import Lattice
|
|
1130
|
+
|
|
1131
|
+
lattice = Lattice(lattice_vectors)
|
|
1132
|
+
return PymatgenStructure(lattice, site_species, cart_positions, coords_are_cartesian=True)
|
|
1133
|
+
except Exception:
|
|
1134
|
+
return None
|
|
1135
|
+
|
|
1136
|
+
def _convert_optimade_structure(self, entry: Dict[str, Any]) -> Optional[PymatgenStructure]:
|
|
1137
|
+
if OptimadeStructure is None:
|
|
1138
|
+
return None
|
|
1139
|
+
|
|
1140
|
+
attributes = entry.get("attributes", {}) if isinstance(entry.get("attributes"), dict) else {}
|
|
1141
|
+
entry_for_conversion = entry
|
|
1142
|
+
if "structure_features" not in attributes:
|
|
1143
|
+
entry_for_conversion = {
|
|
1144
|
+
**entry,
|
|
1145
|
+
"attributes": {
|
|
1146
|
+
**attributes,
|
|
1147
|
+
"structure_features": [],
|
|
1148
|
+
},
|
|
1149
|
+
}
|
|
1150
|
+
try:
|
|
1151
|
+
structure = OptimadeStructure(entry_for_conversion).convert("pymatgen")
|
|
1152
|
+
if isinstance(structure, PymatgenStructure):
|
|
1153
|
+
return structure
|
|
1154
|
+
except OptimadeConversionError as exc:
|
|
1155
|
+
print(f" OPTIMADE conversion error for {entry.get('id')}: {exc}")
|
|
1156
|
+
except Exception as exc:
|
|
1157
|
+
print(f" OPTIMADE conversion failed for {entry.get('id')}: {exc}")
|
|
1158
|
+
return None
|
|
1159
|
+
|
|
1160
|
+
def get_structures(self, formula: str, limit: int = 10) -> List[Dict]:
|
|
1161
|
+
"""Search OPTIMADE providers for a given formula."""
|
|
1162
|
+
providers = self._get_providers()
|
|
1163
|
+
if not providers or limit <= 0:
|
|
1164
|
+
return []
|
|
1165
|
+
|
|
1166
|
+
normalized_formula = self._normalize_formula(formula)
|
|
1167
|
+
filter_str = f'chemical_formula_reduced="{normalized_formula}"'
|
|
1168
|
+
available_props = get_available_properties("optimade")
|
|
1169
|
+
results: List[Dict] = []
|
|
1170
|
+
|
|
1171
|
+
for provider in providers:
|
|
1172
|
+
|
|
1173
|
+
base_url = provider.get("base_url")
|
|
1174
|
+
if not base_url:
|
|
1175
|
+
continue
|
|
1176
|
+
|
|
1177
|
+
provider_id = provider.get("id") or base_url
|
|
1178
|
+
provider_name = provider.get("name") or provider_id
|
|
1179
|
+
|
|
1180
|
+
params = {
|
|
1181
|
+
"filter": filter_str,
|
|
1182
|
+
"page_limit": str(limit),
|
|
1183
|
+
}
|
|
1184
|
+
|
|
1185
|
+
try:
|
|
1186
|
+
response = requests.get(
|
|
1187
|
+
f"{base_url.rstrip('/')}/v1/structures",
|
|
1188
|
+
params=params,
|
|
1189
|
+
timeout=self.request_timeout,
|
|
1190
|
+
)
|
|
1191
|
+
response.raise_for_status()
|
|
1192
|
+
payload = response.json()
|
|
1193
|
+
except Exception as exc:
|
|
1194
|
+
print(f" OPTIMADE query failed for {provider_id}: {exc}")
|
|
1195
|
+
continue
|
|
1196
|
+
|
|
1197
|
+
entries = payload.get("data", []) if isinstance(payload, dict) else []
|
|
1198
|
+
if not isinstance(entries, list):
|
|
1199
|
+
continue
|
|
1200
|
+
|
|
1201
|
+
for entry in entries[:limit]:
|
|
1202
|
+
if not isinstance(entry, dict):
|
|
1203
|
+
continue
|
|
1204
|
+
|
|
1205
|
+
attributes = entry.get("attributes", {}) if isinstance(entry.get("attributes"), dict) else {}
|
|
1206
|
+
full_data = {**entry, **attributes}
|
|
1207
|
+
|
|
1208
|
+
structure_data: Dict[str, Any] = {
|
|
1209
|
+
"database": "OPTIMADE",
|
|
1210
|
+
"source_database": provider_id,
|
|
1211
|
+
"provider_id": provider_id,
|
|
1212
|
+
"provider_name": provider_name,
|
|
1213
|
+
"provider_base_url": base_url,
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
for prop_name in available_props:
|
|
1217
|
+
value = get_property_value(full_data, "optimade", prop_name)
|
|
1218
|
+
if value is not None:
|
|
1219
|
+
structure_data[STANDARD_PROPERTIES[prop_name]] = value
|
|
1220
|
+
|
|
1221
|
+
structure = self._create_pymatgen_structure(attributes)
|
|
1222
|
+
if structure is None:
|
|
1223
|
+
structure = self._convert_optimade_structure(entry)
|
|
1224
|
+
if structure is not None:
|
|
1225
|
+
structure_data["structure"] = structure
|
|
1226
|
+
|
|
1227
|
+
results.append(structure_data)
|
|
1228
|
+
|
|
1229
|
+
return results
|
|
1230
|
+
|
|
1231
|
+
def save_cif(self, structure_data: Dict, filename: str) -> str:
|
|
1232
|
+
"""Save OPTIMADE structure as CIF file."""
|
|
1233
|
+
cif_path = self.output_dir / f"{filename}.cif"
|
|
1234
|
+
|
|
1235
|
+
if "structure" in structure_data:
|
|
1236
|
+
structure = structure_data["structure"]
|
|
1237
|
+
cif_writer = CifWriter(structure)
|
|
1238
|
+
cif_writer.write_file(str(cif_path))
|
|
1239
|
+
else:
|
|
1240
|
+
with open(cif_path, "w") as handle:
|
|
1241
|
+
handle.write("# OPTIMADE structure\n")
|
|
1242
|
+
handle.write(f"# Formula: {structure_data.get('formula', 'unknown')}\n")
|
|
1243
|
+
handle.write(f"# Material ID: {structure_data.get('material_id', 'unknown')}\n")
|
|
1244
|
+
handle.write(f"# Provider: {structure_data.get('provider_id', 'unknown')}\n")
|
|
1245
|
+
|
|
1246
|
+
metadata = {k: v for k, v in structure_data.items() if k != "structure"}
|
|
1247
|
+
metadata_path = self.output_dir / f"{filename}_metadata.json"
|
|
1248
|
+
with open(metadata_path, "w") as handle:
|
|
1249
|
+
json.dump(metadata, handle, indent=2, default=str)
|
|
1250
|
+
|
|
1251
|
+
return str(cif_path)
|
|
1252
|
+
|
|
1253
|
+
|
|
1046
1254
|
class MPDSClient(MaterialsDatabaseClient):
|
|
1047
1255
|
"""MPDS (Materials Platform for Data Science) database client."""
|
|
1048
1256
|
|
|
@@ -1503,6 +1711,15 @@ class MaterialsDatabaseRetriever:
|
|
|
1503
1711
|
print("✓ Materials Cloud client initialized")
|
|
1504
1712
|
except Exception as e:
|
|
1505
1713
|
print(f"✗ Materials Cloud client failed: {e}")
|
|
1714
|
+
|
|
1715
|
+
# OPTIMADE (generic)
|
|
1716
|
+
try:
|
|
1717
|
+
self.clients['optimade'] = OptimadeSearchClient(
|
|
1718
|
+
output_directory=self.output_directory,
|
|
1719
|
+
)
|
|
1720
|
+
print("✓ OPTIMADE client initialized")
|
|
1721
|
+
except Exception as e:
|
|
1722
|
+
print(f"✗ OPTIMADE client failed: {e}")
|
|
1506
1723
|
|
|
1507
1724
|
# OQMD
|
|
1508
1725
|
try:
|
|
@@ -1571,5 +1788,3 @@ class MaterialsDatabaseRetriever:
|
|
|
1571
1788
|
print(f" Successful databases: {successful_dbs}/{len(self.clients)}")
|
|
1572
1789
|
|
|
1573
1790
|
return test_results
|
|
1574
|
-
|
|
1575
|
-
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
mat_ret GUI Package
|
|
3
|
+
|
|
4
|
+
A PyQt6-based graphical interface for searching, retrieving,
|
|
5
|
+
and visualizing materials data from multiple databases.
|
|
6
|
+
|
|
7
|
+
Usage:
|
|
8
|
+
# From command line (after installing mat_ret):
|
|
9
|
+
mat-ret-gui
|
|
10
|
+
|
|
11
|
+
# Or programmatically:
|
|
12
|
+
from mat_ret.gui import main
|
|
13
|
+
main()
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
__version__ = "1.0.0"
|
|
17
|
+
|
|
18
|
+
from .main import main
|
|
19
|
+
|
|
20
|
+
__all__ = ["main"]
|