Repository navigation
Conversation
Add initial README file with author information.
- labels.py: M/X/T/n/stacking/termination-site vocabularies and rules - structure.py: arrow-friendly structure + descriptors derived from it (thickness, vacuum, bond lengths, measured O/P coordination sequence) - properties.py, calculation.py: draft spreadsheet-backed properties and project-level DFT settings - mxene.py: MXeneEntry, cross-validating labels against the structure - pipelines/build_contributions.py: CONTCAR tree + spreadsheet -> entries - tests and sample Hf2CF2 structure; README with spreadsheet spec Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m
…data - Site 1 = octahedral, site 2 = prismatic outer-metal coordination; MXeneEntry now rejects structures that disagree with their site label - Move sample CONTCAR to MXENE_DATA/Hf/m2x/h-1/ (its real location) and test the pipeline directly on the repo test data - Document site meanings in labels.py and README Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m
- check_dataset() validates all CONTCARs and collects failures instead of stopping at the first one - Command-line entry point prints valid/failed counts, a composition summary, and each failing file with its error Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m
- Termination site rule: for n=1, site 1 = octahedral and site 2 = prismatic outer metal layers; for n>=2, site 1 = same coordination as the inner metal layers of the stacking (O for t/h1b, P for h1a/h2) and site 2 = opposite - CONTCARs in sub-folders of an entry folder (supercells, phonons, d2, ...) are skipped by check_dataset and build_entries instead of failing - Failure messages are one line instead of full pydantic tracebacks - Summary table of measured outer-metal coordination for every (n, stacking, site) label, including failed structures - Optional --report CSV with one row per CONTCAR Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m
- StructureDescriptors.reducedFormula (also the MPContribs identifier) is now written in M, X, T order without grouping, e.g. Hf3C2F2 instead of pymatgen's Hf3(CF)2 - Mixed-species layer errors report the z positions and suggest the structure may be strongly distorted Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m
- New pipelines/dataset_overview.py and --overview option on the check command: one row per M-X system, one column per feasible structure (n / termination / stacking label), with a good / problem / missing symbol in every cell, hover comments on problem cells, per-row and per-column totals, and Problems, Missing and All files sheets - Terminations outside the study (pristine and O for Hf, Re) are shaded - Check records now store the structure each file claims (cellId), so failed files are placed on the grid - All files claiming the same structure are now flagged as duplicates, instead of accepting whichever was found first - infer_chemistry() factored out of MXeneEntry.from_structure Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m
Documentation - README.md (project): labelling rules including the n-dependent termination-site rule, checks, record contents, workflow, extending the dataset, and a PR guide for human and AI contributors - pipelines/README.md: command-line usage, output files, error catalogue with remedies, properties spreadsheet, step-by-step MPContribs upload, upload errors, API summary, tolerances - schemas/README.md: field reference generated from the models Pipeline - --template FILE.csv writes one row per valid structure with read-only structure columns and blank property columns (units in headers); re-running keeps entered values and moves orphaned rows aside - load_properties accepts template files, checks header units and rejects half-filled elastic constants - build_entries(skip_invalid=True) to upload the valid part of a dataset - MPCONTRIBS_COLUMNS / MPCONTRIBS_COLUMN_DESCRIPTIONS for init_columns, project_other() for server-safe project metadata - Contribution identifier is now mxeneId (formula identifiers would be deduplicated by MPContribs' default unique_identifiers=True); formula goes in the formula field; consistent column types - write_parquet / read_parquet for the full records Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m
Documentation - Project README defines the schema independently of any dataset: labelling rules, eight validation phases, accepted and rejected data, record contents, conformance with MPContribs and MPContribs-lux requirements, test coverage by phase, extension points and contribution guidelines - pipelines/README.md and schemas/README.md rewritten in the same terms; field reference regenerated from the models - Field descriptions and docstrings no longer refer to a specific dataset; the stacking nomenclature cites its source Behaviour - Overview rows are created for every transition metal in the data (by atomic number); all combinations are in scope unless excluded with --out-of-scope M:T (replaces the hard-coded study scope) - Template rows are ordered by atomic number - Unsupported elements are reported explicitly by infer_chemistry Tests - Grid-state logic tested without openpyxl; scope exclusion parsing - Accepted supercells; rejected mixed terminations, other elements, MBenes, double-metal sheets, n = 4, one-sided terminations, vacancies - Column units parsed with the MPContribs client's unit registry Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m
bfoley12
left a comment
There was a problem hiding this comment.
Great start!
Mostly styling changes and being stricter about limiting fields.
Major change we need to talk about - happy to set up a call if it is not clear:
- classes in schemas/structure.py seem to be subsets of
pymatgen.Structure. It is much simpler to store this info as apymatgen.Structure.- internally, we do not allow Contributions.data (what you describe in mxene.py) to contain
list-typed fields (as lists are better represented asTables). The structure classes have several lists, which would get rejected at submission. - Instead, we should submit the data during
submit_contributionsas a structure with their respective contributions, then users would derive views as the see fit (structure.py would be your own internal representation, not stored here) - If these are absoultely necessary representations and include non-
pymatgen.Structureinformation, we can come up with alternate strategies
- internally, we do not allow Contributions.data (what you describe in mxene.py) to contain
I did not review pipelines here. My primary concern is the data representation, so subsequent reviews will address pipelines. If the primary use case of classes in structures.py is to be used in the pipelines, then we should move those into something like pipelines/schemas - not totally settled on the name yet.
| @property | ||
| def core_sequence(self) -> list[str]: | ||
| """Measured coordination of the layers between the outer metal layers.""" | ||
| seq = self.descriptors.coordinationSequence | ||
| return seq[1:-1] if self.labels.termination else seq | ||
|
|
||
| @property | ||
| def termination_coordination(self) -> tuple[str, str] | None: | ||
| """Measured coordination of the bottom and top outer metal layers. | ||
|
|
||
| This is set by the termination site: `O` if the termination sits | ||
| staggered with respect to the X layer beneath the outer metal, `P` if | ||
| it sits directly above an X atom. None for pristine MXenes. | ||
| """ | ||
| if not self.labels.termination: | ||
| return None | ||
| seq = self.descriptors.coordinationSequence | ||
| return seq[0], seq[-1] |
There was a problem hiding this comment.
To be clear: these are not be fields that are submitted as data?
| _FOLDER_LABEL = re.compile(r"^(?P<stacking>h1a|h1b|h2|h|t)(?:-(?P<site>[12]))?$") | ||
|
|
||
| _MODEL_CONFIG = ConfigDict(extra="forbid", allow_inf_nan=False) | ||
|
|
There was a problem hiding this comment.
Move these to top of file with other module-level constants
|
|
||
| from pydantic import BaseModel, ConfigDict, Field | ||
|
|
||
| _MODEL_CONFIG = ConfigDict(extra="forbid", allow_inf_nan=False) |
There was a problem hiding this comment.
remove _MODEL_CONFIG and inline it within the class
|
|
||
| model_config = _MODEL_CONFIG | ||
|
|
||
| code: str | None = Field(None, description="Simulation code, e.g. `VASP`.") |
There was a problem hiding this comment.
Do you know all possible codes, or is this high cardinality? If it is a small collection of codes, add a Literal[...]
| functional: str | None = Field( | ||
| None, | ||
| description="Exchange-correlation functional, e.g. `PBE`.", | ||
| ) | ||
| vdwCorrection: str | None = Field( | ||
| None, | ||
| description="Dispersion correction, e.g. `DFT-D3`, or null if none.", | ||
| ) | ||
| pseudopotentials: list[str] | None = Field( | ||
| None, | ||
| description="Pseudopotential (e.g. PAW POTCAR) labels used for each element.", | ||
| ) |
There was a problem hiding this comment.
Similar to code, are these easy to enumerate? If not, can you cap the length to something reasonable (ie. longest known value + 50%)? I like to limit the amount of arbitrary strings present.
| from pydantic import BaseModel, ConfigDict, Field, model_validator | ||
| from pymatgen.core import Composition, Structure | ||
|
|
||
| _MODEL_CONFIG = ConfigDict(extra="forbid", allow_inf_nan=False) |
There was a problem hiding this comment.
remove in favor of inlining
| metals = [el.symbol for el in composition if el.is_transition_metal] | ||
| nonmetals = [el.symbol for el in composition if el.symbol in {"C", "N"}] | ||
| terms = [el.symbol for el in composition if el.symbol in {"F", "O"}] | ||
| others = sorted( | ||
| el.symbol | ||
| for el in composition | ||
| if not el.is_transition_metal and el.symbol not in {"C", "N", "F", "O"} | ||
| ) |
There was a problem hiding this comment.
refactor to single iteration instead of 4
| return "".join(parts) | ||
|
|
||
|
|
||
| class MXeneStructure(BaseModel): |
There was a problem hiding this comment.
How is this different from a pymatgen.Structure? It looks like you use only specific fields from pymatgen.Structure (def from_structure).
If this is truly not a subset of a pymatgen structure, representation gets tricky since we do not allow lists for fields in a contribution.
If it is a subset of a pymatgen structure, we can remove this class and just upload structures directly. I can explain more as we refine.
| reducedFormula: str = Field( | ||
| description="Reduced chemical formula of the slab in M, X, T order, " | ||
| "e.g. `Hf2CF2` or `Ti3C2O2`.", | ||
| ) |
There was a problem hiding this comment.
Same comments as other string fields: try to restrict with literals, fall back to length
|
|
||
| from pydantic import BaseModel, ConfigDict, Field | ||
|
|
||
| _MODEL_CONFIG = ConfigDict(extra="forbid", allow_inf_nan=False) |
We can totally swap to using pymatgen Structure objects instead of a custom class. Also, I am happy to move the computations over to the pipelines folder, leaving just models and validators in schema, with the recommended syntax changes. One question for you, are sequences as strings, like "O-P-O" and "F-Hf-C-Hf-F" acceptable as data fields, or should these be set up as tables? |
… pipelines Schema (mpcontribs-lux/.../two_d_mxenes/schemas) - MXeneEntry is exactly the contribution `data`: no list fields; the relaxed structure is submitted as a pymatgen Structure in `structures` - Remove MXeneStructure; structure.py is replaced by descriptors.py, which holds only StructureDescriptors (scalars and validated strings) - Layer and coordination sequences are strings (e.g. `F-Hf-C-Hf-F`, `O-P-O`) - Restrict every string: metal is a Literal of the 40 transition metals; mxeneId, formula and sequences have patterns and maximum lengths; the space-group symbol must be one spglib produces and match its number - Bounds on all numeric fields; units declared in json_schema_extra - MXeneEntry also checks the layer sequence and mxeneId against the labels - ElasticProperties checks derived moduli and stability for consistency - model_config declared in each class; module constants at the top of labels.py; Google-style docstrings throughout - core_sequence / termination_coordination properties removed; the implied sequences are MXeneLabel.layer_sequence / coordination_sequence Pipelines - structure_analysis.py: chemistry inference (single pass over the elements), layer grouping, coordination, descriptors, entry_from_structure - upload.py: CalculationSettings (moved from schemas; code Literal["VASP"], emmet RunType functional, Literal dispersion correction / POTCAR set / elastic method, patterned POTCAR symbols and version, k-point mesh as three integers, strain amplitude and point count); MPContribs columns and descriptions generated from the schema; to_contribution(entry, structure); project_other; Parquet with optional structure column - build_contributions.py: MXeneRecord (entry, structure, path), build_records; numeric spreadsheet cells validated explicitly and only empty cells treated as missing Docs and tests updated; 143 project tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m
Yes, those are totally ok. In fact, we store information similarly internally. The issue is more around lists of numbers or objects and abuse that can come from it. |
…enance Review comments - derived_elastic_properties returns a TypedDict (DerivedElastic) - ElasticProperties passes cast(float, self.c12) to the derivation - C11 = 0 is rejected by a validator (moduli undefined); the derivation also raises on c11 = 0. Negative C11 stays valid (unstable sheet) - terminationSite and spaceGroupNumber declare no unit and are stored as text columns (categories); n and nSites stay dimensionless numbers Wider data contract (checks no longer specific to single-metal MXenes) - Labels: metals, nonmetals and terminations are element lists (strings ordered by atomic number, joined by `-`), e.g. Ti-V-Nb-Mo, C-N, O-F; nMetals; optional configuration index (`-c<k>` label suffix) for several arrangements of one composition and stacking (e.g. SQS) - Vocabulary: T = F, Cl, Br, I, O, S, Se, Te; n = 1-4 - Descriptors: layers may mix elements of one role (`Ti/V`); new metalArrangement (single, out-of-plane ordered, in-plane mixed); formula and sequence patterns sized for up to 10 metals and M5X4T2 - MXeneEntry cross-checks generalized: labelled elements and M_(n+1)X_n(T_2) proportions from the reduced formula; role of every layer; metal arrangement; stacking and termination site as before - mxeneId is <reducedFormula>-<label>; reduced formulas list metals, X and terminations, each by atomic number (single-metal IDs unchanged) Elastic data - Optional C22 for non-hexagonal cells (with C66); Young's modulus and Poisson's ratio only for C22 = C11; Born criteria of an orthotropic sheet; elastic data of a non-hexagonal cell must include C22 Provenance - New Provenance model: method DFT (default) or MLIP with the potential's name; read from the properties spreadsheet (load_provenance) - Relative stacking energies compare one reduced formula and one method Settings, overview, typing - codeVersion accepts the OUTCAR build tag; POTCAR symbols accept multi-part suffixes; PotcarSet is pymatgen's list (subset test) - Overview rows per metal/X system, including alloy and carbonitride systems; columns extend to the n and terminations found in the data - The project passes mypy with the pydantic plugin Tests and docs - Tests for ordered, solid-solution and high-entropy sheets, carbonitrides, mixed and Janus terminations, configurations, C22, provenance and the overview; READMEs and field reference updated Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m
Hi Brandon. Sorry to do this, but I introduced some changes that are going to need to be re-reviewed. In the nature of making sure my checks weren't too tight, I talked to Dr. Chongze Hu, and he said he would like this schema to be used both for the current dataset (single transition metal MXenes) and future datasets (high entropy alloy MXenes). Thus, some of my columns have been adjusted to account for future material systems. That being said, I have implemented your recommended changes regarding type casting, annotating, etc. so it should be MP compliant. Thanks! |
Add
two_d_mxeneslux project: schema for 2D MXene structures and properties@bfoley12, this PR adds the MPContribs-lux schema for the
two_d_mxenesproject and is ready for review.Summary
The project describes two-dimensional MXenes Mn+1XnTx: one record per relaxed structure, identified by its transition metal M, non-metal X (C, N), thickness n (1–3), termination T (none, F, O), layer stacking and termination site. Each record contains the structure, descriptors computed from it, and computed energetic and elastic properties. The schema is designed for non-bulk (slab) materials and is intended to support a future MPContribs app for them.
All changes are under
mpcontribs-lux/(16 new files; no existing files modified):projects/two_d_mxenes/schemas/MXeneEntry,MXeneLabel,MXeneStructure,StructureDescriptors,MXeneProperties,Energetics,ElasticProperties,CalculationSettingsprojects/two_d_mxenes/pipelines/projects/two_d_mxenes/README.md,pipelines/README.md,schemas/README.mdtests/projects/two_d_mxenes/test_two_d_mxenes.pytest_data/by_user/two_d_mxenes/Validation phases
Each structure passes through eight phases. A failure in any phase produces no record, and every rule is tested for both acceptance and rejection.
CONTCARfiles are collected; files in label folders are entries; files nested inside an entry folder are auxiliary calculations and are skipped; files anywhere else are errorst,h,h1a,h1b,h2) allowed for n, with a-1/-2termination-site suffix exactly when the MXene is terminatedmxeneId; when several claim the same ID, all are rejectedAccepted and rejected data
Conformance with MPContribs requirements
projects/two_d_mxenes/schemas, with a description and unit on every field; all models pass the Arrow compatibility test.two_d_mxenes(3–31 alphanumeric/underscore characters).datacolumns (limit 50). camelCase names without underscores, nested at most 2 levels. Values are strings with pint units;Nonemarks text columns and""dimensionless ones. No NaN, unset keys or lists.identifierand aformulaper contribution, and the projectothermetadata keys pass the API's key validation.lux project scaffold: a project README,pip-extra-requirements.txt, tests undertests/projects/two_d_mxenes, and data undertest_data/by_user/two_d_mxenes.pre-commit(ruff, black, pyupgrade and file checks) passes on all files.Design decisions for review
mxeneId(e.g.Ti3C2O2-h1a-2), and the formula goes informula. 2D slabs have nomp-IDs, and several structures share a formula. With the defaultunique_identifiers=True, a formula identifier would retain only one structure per formula. The MPContribs-lux upload example states that identifiers may be arbitrary.MXeneStructurestores lattice, species and fractional coordinates as plain arrays. It does not subclass emmet'sStructureMetadata, whose volume and density depend on the vacuum of a slab model. We can align with emmet types if preferred.computed_fields, so they are present in Parquet.labels.expected_termination_coordination.pipelines/, followingalab/pipelines.openpyxl(inpip-extra-requirements.txt) for the Excel overview. It is not in the lux CI requirements, so the workbook test is skipped in CI; the grid logic is tested independently. It can be added to the extras requirements if preferred.othermetadata. The API rejects keys containing., soproject_other()nests column descriptions (geometry→a). The upload notebook inmpcontribs-lux/examplespasses dotted keys toupdate_project, which this validation appears to reject.extra="forbid"andallow_inf_nan=False.Testing
Clean environments built from
requirements/requirements-ubuntu-latest_py3.1{1,2}_extras.txt(emmet-core 0.86.2, pymatgen 2025.10.7), run with the CI commandpytest -n auto -x:tests/projects/two_d_mxenes+tests/test_models_for_arrow_compatibility.pyThe two skips are the workbook test (
openpyxl) and the unit-registry test (pint), neither of which is in the lux CI requirements. With both installed, all 95 project tests pass.test_autogen.pyandtests/projects/esoteric_ephemerarequire Git LFS files that were unavailable in the test environment. They are not affected by this PR.Developed with the assistance of Claude, as suggested in the MPContribs onboarding guidance.
🤖 Generated with Claude Code
https://claude.ai/code/session_01Gc97mYBH8XSmaL2oDocZ9m