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.
Files changed (30) hide show
  1. {mat_ret-0.1.0 → mat_ret-0.3.0}/PKG-INFO +49 -5
  2. {mat_ret-0.1.0 → mat_ret-0.3.0}/README.md +46 -4
  3. {mat_ret-0.1.0 → mat_ret-0.3.0}/pyproject.toml +7 -2
  4. {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret/__init__.py +6 -0
  5. {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret/api.py +83 -0
  6. {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret/databases.py +217 -2
  7. mat_ret-0.3.0/src/mat_ret/gui/__init__.py +20 -0
  8. mat_ret-0.3.0/src/mat_ret/gui/__main__.py +11 -0
  9. mat_ret-0.3.0/src/mat_ret/gui/main.py +110 -0
  10. mat_ret-0.3.0/src/mat_ret/gui/main_window.py +549 -0
  11. mat_ret-0.3.0/src/mat_ret/gui/utils.py +417 -0
  12. mat_ret-0.3.0/src/mat_ret/gui/widgets/__init__.py +11 -0
  13. mat_ret-0.3.0/src/mat_ret/gui/widgets/database_selector.py +506 -0
  14. mat_ret-0.3.0/src/mat_ret/gui/widgets/results_view.py +611 -0
  15. mat_ret-0.3.0/src/mat_ret/gui/widgets/structure_viewer.py +1041 -0
  16. mat_ret-0.3.0/src/mat_ret/gui/workers.py +234 -0
  17. mat_ret-0.3.0/src/mat_ret/optimade/__init__.py +9 -0
  18. mat_ret-0.3.0/src/mat_ret/optimade/cli.py +114 -0
  19. mat_ret-0.3.0/src/mat_ret/optimade/harvester.py +422 -0
  20. mat_ret-0.3.0/src/mat_ret/optimade/registry.py +189 -0
  21. {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret/property_mapping.py +10 -0
  22. {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret.egg-info/PKG-INFO +49 -5
  23. mat_ret-0.3.0/src/mat_ret.egg-info/SOURCES.txt +27 -0
  24. mat_ret-0.3.0/src/mat_ret.egg-info/entry_points.txt +2 -0
  25. {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret.egg-info/requires.txt +2 -0
  26. mat_ret-0.1.0/src/mat_ret.egg-info/SOURCES.txt +0 -12
  27. {mat_ret-0.1.0 → mat_ret-0.3.0}/LICENSE +0 -0
  28. {mat_ret-0.1.0 → mat_ret-0.3.0}/setup.cfg +0 -0
  29. {mat_ret-0.1.0 → mat_ret-0.3.0}/src/mat_ret.egg-info/dependency_links.txt +0 -0
  30. {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.1.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
+ ![mat_ret GUI Screenshot](doc/Screenshot.png?raw=true)
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 retirive information from all the above mentioned database
649
- ├── example_single_fetch.py # Demo script intended to retirive information from anyone of the above mentioned database
650
- ├── README # Project overview
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 present for future development purpose.
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
+ ![mat_ret GUI Screenshot](doc/Screenshot.png?raw=true)
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 retirive information from all the above mentioned database
106
- ├── example_single_fetch.py # Demo script intended to retirive information from anyone of the above mentioned database
107
- ├── README # Project overview
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 present for future development purpose.
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.1.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"]
@@ -0,0 +1,11 @@
1
+ """
2
+ Allow running mat_ret.gui as a module.
3
+
4
+ Usage:
5
+ python -m mat_ret.gui
6
+ """
7
+
8
+ from .main import main
9
+
10
+ if __name__ == "__main__":
11
+ main()