|
3 | 3 |
|
4 | 4 | from typing import Any |
5 | 5 |
|
| 6 | +from emmet.core.summary import SummaryDoc |
6 | 7 | from pydantic import BaseModel, Field, model_validator |
| 8 | +from typing_extensions import Self |
7 | 9 |
|
8 | 10 | from mp_api.client.core.utils import validate_ids |
9 | 11 |
|
@@ -264,6 +266,120 @@ class MaterialMetadata(BaseModel): |
264 | 266 | ), |
265 | 267 | ) |
266 | 268 |
|
| 269 | + cell_vectors: list[list[float]] | None = Field( |
| 270 | + None, |
| 271 | + description=( |
| 272 | + "The 3x3 array of (lattice) cell vectors, all values in Å." |
| 273 | + "The first row is the a vector, the second the b vector, " |
| 274 | + "and the third the c vector." |
| 275 | + ), |
| 276 | + ) |
| 277 | + |
| 278 | + atoms: list[str] | None = Field( |
| 279 | + None, |
| 280 | + description=( |
| 281 | + "A list of atom symbols on each site. Should have length `nsites`." |
| 282 | + ), |
| 283 | + ) |
| 284 | + |
| 285 | + cartesian_coordinates: list[list[float]] | None = Field( |
| 286 | + None, |
| 287 | + description=( |
| 288 | + "A `nsites` x 3 array of floats, all values in Å, " |
| 289 | + "representing the Cartesian coordinates of the atoms." |
| 290 | + "The order is the same as in `atoms`." |
| 291 | + ), |
| 292 | + ) |
| 293 | + magnetic_moments: list[float] | None = Field( |
| 294 | + None, |
| 295 | + description=( |
| 296 | + "A `nsites` array of floats, all values in μB, representing " |
| 297 | + "the on-site magnetic moments found by integrating the " |
| 298 | + "electronic spin density in a sphere surrounding each site " |
| 299 | + "in the structure." |
| 300 | + ), |
| 301 | + ) |
| 302 | + |
| 303 | + @staticmethod |
| 304 | + def _summary_fields() -> list[str]: |
| 305 | + """Get a list of the fields needed in a SummaryDoc to populate this document.""" |
| 306 | + return [ |
| 307 | + *(set(MaterialMetadata.model_fields) & set(SummaryDoc.model_fields)), |
| 308 | + # The following fields get renamed and flattened in `MaterialMetadata` |
| 309 | + "structure", |
| 310 | + "bulk_modulus", |
| 311 | + "shear_modulus", |
| 312 | + "database_IDs", |
| 313 | + "symmetry", |
| 314 | + ] |
| 315 | + |
| 316 | + @classmethod |
| 317 | + def from_summary_data(cls, summary_data: dict[str, Any], **kwargs) -> Self: |
| 318 | + """Create a MaterialMetadata document from materials summary data. |
| 319 | +
|
| 320 | + Args: |
| 321 | + summary_data : dict of str to Any |
| 322 | + The dict representation of an `emmet.core.summary.SummaryDoc` |
| 323 | + document (i.e., its `model_dump_json`) |
| 324 | + **kwargs : to pass to `MaterialMetadata` |
| 325 | + """ |
| 326 | + metadata = { |
| 327 | + **kwargs, |
| 328 | + **{ |
| 329 | + k: summary_data[k] |
| 330 | + for k in MaterialMetadata.model_fields |
| 331 | + if summary_data.get(k) is not None |
| 332 | + }, |
| 333 | + } |
| 334 | + for k in {"bulk", "shear"}: |
| 335 | + if summary_data.get(f"{k}_modulus"): |
| 336 | + metadata.update( |
| 337 | + { |
| 338 | + f"{k}_modulus_{v}": summary_data[f"{k}_modulus"].get(v) |
| 339 | + for v in ("voigt", "reuss", "hill") |
| 340 | + } |
| 341 | + ) |
| 342 | + |
| 343 | + # Augment with experimental database id information |
| 344 | + if summary_data.get("database_IDs"): |
| 345 | + metadata.update( |
| 346 | + { |
| 347 | + f"linked_{database}_ids": ", ".join(matched_ids) |
| 348 | + for database, matched_ids in summary_data["database_IDs"].items() |
| 349 | + } |
| 350 | + ) |
| 351 | + |
| 352 | + if (symm_meta := summary_data.get("symmetry")) is not None: |
| 353 | + metadata.update( |
| 354 | + { |
| 355 | + k: symm_meta.get(v) |
| 356 | + for k, v in { |
| 357 | + "space_group_number": "number", |
| 358 | + "space_group_symbol": "symbol", |
| 359 | + "crystal_system": "crystal_system", |
| 360 | + "point_group": "point_group", |
| 361 | + }.items() |
| 362 | + } |
| 363 | + ) |
| 364 | + |
| 365 | + # flatten structure data |
| 366 | + if struct_dict := summary_data.get("structure"): |
| 367 | + magnetic_moments = [ |
| 368 | + site["properties"].get("magmom") for site in struct_dict["sites"] |
| 369 | + ] |
| 370 | + metadata.update( |
| 371 | + cell_vectors=struct_dict["lattice"]["matrix"], |
| 372 | + atoms=[site["species"][0]["element"] for site in struct_dict["sites"]], |
| 373 | + cartesian_coordinates=[site["xyz"] for site in struct_dict["sites"]], |
| 374 | + magnetic_moments=( |
| 375 | + None |
| 376 | + if any(magmom is None for magmom in magnetic_moments) |
| 377 | + else magnetic_moments |
| 378 | + ), |
| 379 | + ) |
| 380 | + |
| 381 | + return cls(**metadata) |
| 382 | + |
267 | 383 |
|
268 | 384 | class FetchResult(BaseModel): |
269 | 385 | """Schematize result of the `fetch` MCP tool. |
|
0 commit comments