neuron-explorer
Qwen3.5‑4B 전용 조향형 뉴런 탐색기, 모든 MLP 뉴런을 밀어 결과를 보관·탐색
재료를 위한 그래프 딥러닝 라이브러리
Graph deep learning library for materials
개발 도구Python · C++GitHub @materialyzeai직접 설치운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
메이커가 쓴 소개는 위의 한 줄이 전부입니다. 더 자세한 내용은 저장소 README에서 읽을 수 있습니다. 저장소 README ↗
README 전체를 불러오는 중입니다.
README 전문 보기 ↗MatGL (Materials Graph Library) is a graph deep learning library for materials science. Mathematical graphs are a natural representation for a collection of atoms. Graph deep learning models have been shown to consistently deliver exceptional performance as surrogate models for the prediction of materials properties. The goal is for MatGL to serve as an extensible platform to develop and share materials graph deep learning models.
This first version of MatGL is a collaboration between the Materialyze.AI and Intel Labs.
MatGL is part of the MatML ecosystem, which includes the MatGL (Materials Graph Library) and maml (MAterials Machine Learning) packages, the MatPES (Materials Potential Energy Surface) dataset, and the MatCalc (Materials Calculator).
Major milestones are summarized below. Please refer to the changelog for details.
M3GNet and QET. Corrected message-passing convention in
TensorNet, M3GNet, and QET. New pre-trained weights re-released on Hugging Face (materialyze org), which
is now the canonical source for all matgl models.DGL support removed. The DGL backend has been removed. matgl now targets PyTorch Geometric (PyG) exclusively. Models that previously had only DGL implementations have either been ported to PyG or removed.
A bug in the message-passing convention of TensorNet, M3GNet, and QET has been corrected:
edge messages are now aggregated onto the source (center) node so each atom correctly collects information from
its neighbors. Pre-trained weights generated under the old convention are no longer numerically valid. New weights
— including TensorNet-PES-MatPES-PBE-2025.2 and the PyG M3GNet / QET potentials — have been retrained
against the corrected convention and uploaded to the materialyze Hugging
Face org, which is now the canonical (and only) source for matgl pre-trained models. The legacy GitHub
pretrained_models/ download fallback (RemoteFile, PRETRAINED_MODELS_BASE_URL) has been removed in this
release.
Here, we summarize the currently implemented architectures in MatGL. It should be stressed that this is by no means an exhaustive list, and we expect new architectures to be added by the core MatGL team as well as other contributors in the future.
For detailed performance benchmarks, please refer to the publications in the References section.
MatGL can be installed via pip:
pip install matgl
Two optional acceleration backends are available — pick one based on your hardware:
pip install matgl[ops] # NVIDIA Warp kernels for TensorNet/QET (NVIDIA GPU, train + infer)
pip install matgl[jax] # JAX/XLA inference for TensorNet/QET (CPU / CUDA / Apple Silicon)
MatGL ships a native LAMMPS pair_style matgl that evaluates a TorchScript-compiled MatGL TensorNet or
M3GNet PES (energies, forces, and virials) on every timestep via LibTorch, with both a CPU/serial pair style
and a Kokkos GPU/host variant (pair_style matgl/kk). Export a LAMMPS-loadable model from a pre-trained TensorNet
or M3GNet with mgl create-lammps-model. Build and usage instructions, the input-deck syntax, and current
limitations are in lammps/README.md.
Docker images have now been built for matgl, together with LAMMPS support. They are available at the Materials Virtual Lab Docker Repository. If you wish to use MatGL with LAMMPS, this is probably the easiest option.
Pre-trained M3GNet universal potential and MEGNet models for the Materials Project formation energy and multi-fidelity band gap are now available.
A CLI tool now provides the capability to perform quick relaxations or predictions using pre-trained models, as well as other simple administrative tasks (e.g., clearing the cache). Some simple examples:
To perform a relaxation,
mgl relax --infile Li2O.cif --outfile Li2O_relax.cif
To use one of the pre-trained property models,
mgl predict --model M3GNet-Eform-MP-2019.4.1 --infile Li2O.cif
To clear the cache,
mgl clear
For a full range of options, use mgl -h.
Pre-trained MatGL models can be loaded from (and published to) the Hugging Face Hub. Any repo that contains the standard matgl
serialization artifacts (model.pt, state.pt, and model.json) can be loaded directly using matgl.load_model by
passing the repo id in "owner/name" form. Pre-trained models released by the Materialyze lab can be found under the
Materialyze Hugging Face Organization.
import matgl
# Load directly from a Hugging Face Hub repo id.
model = matgl.load_model("materialyze/TensorNet-PES-MatPES-r2SCAN-2025.2")
# For materialyze org, you can also just use the bare model names directly.
model = matgl.load_model("TensorNet-PES-MatPES-r2SCAN-2025.2")
To publish a trained model to the Hugging Face Hub, use push_to_hub (requires huggingface-cli login or a token):
model.push_to_hub("your-username/your-matgl-model", private=False)
To list available matgl models, you can use the HfApi:
from huggingface_hub import HfApi
hf = HfApi()
print(list(hf.list_models(filter="matgl")))
The following is an example of a prediction of the formation energy for CsCl.
from pymatgen.core import Lattice, Structure
import matgl
model = matgl.load_model("M3GNet-Eform-MP-2019.4.1")
# This is the structure obtained from the Materials Project.
struct = Structure.from_spacegroup("Pm-3m", Lattice.cubic(4.1437), ["Cs", "Cl"], [[0, 0, 0], [0.5, 0.5, 0.5]])
eform = model.predict_structure(struct)
print(f"The predicted formation energy for CsCl is {float(eform.numpy()):.3f} eV/atom.")
By default, matgl runs TensorNet / QET in eager PyTorch on whichever device the model lives on. Two optional
backends accelerate the inner loop of MD and relaxation: NVIDIA Warp (training + inference, NVIDIA GPU only)
and JAX (inference only, portable across CPU / CUDA / Apple Silicon). They are mutually exclusive — pick one
based on the hardware and workflow.
| Backend | Install | Hardware | Training | Inference | Models | Typical speedup vs eager PyTorch |
|---|---|---|---|---|---|---|
| PyTorch (default) | pip install matgl | CPU / CUDA / MPS | yes | yes | all | 1x baseline |
| Warp | pip install matgl[ops] | NVIDIA GPU (CUDA) | yes | yes | TensorNet / QET | ~2-3x speed + memory at train+infer |
| JAX | pip install matgl[jax] | CPU / CUDA / Metal | no | yes | TensorNet / QET | ~2-3.5x at inference |
How to choose:
[ops] extra and use Warp. It is the only backend
that accelerates the training loop, and it cuts memory as well as wall time. MGLPotentialTrainer and the
models pick it up automatically; pass use_warp=False only if you need to fall back.[jax] extra). It is the
only accelerated path on Metal and is typically the fastest path on CPU. See the JAX subsection
for the applejax Metal plugin and Apple-Silicon benchmarks.Warp acceleration is provided by the nvalchemi-toolkit-ops
package, contributed by the NVIDIA Alchemi team. It replaces the PyG message-passing layers in TensorNet (and
therefore in QET, which composes a TensorNet) with hand-written NVIDIA Warp CUDA kernels for the embedding,
interaction, and tensor decomposition / composition steps, yielding ~2-3x speed and memory reduction in both
training and inference relative to the plain PyG path.
Install the extra:
pip install matgl[ops]
Once installed, every newly constructed TensorNet / QET autodetects Warp and uses it:
from matgl.models import TensorNet
# Warp is used automatically when nvalchemi-toolkit-ops is importable.
model = TensorNet(element_types=("H", "C", "N", "O"))
# Force the plain PyG implementation for debugging / parity tests.
model_pyg = TensorNet(element_types=("H", "C", "N", "O"), use_warp=False)
A loaded pre-trained Potential works the same way — matgl.load_model("TensorNet-PES-MatPES-r2SCAN-2025.2") will
use Warp kernels under the hood whenever the extra is present, with no code change in PESCalculator /
MolecularDynamics / Relaxer. Warp requires an NVIDIA GPU at run time; on CPU-only or non-CUDA devices, leave
the extra uninstalled (or pass use_warp=False) and use the JAX path below instead.
The optional matgl.ext.jax subpackage reimplements the inference path (energy + forces + stress) of the PyG-backend
TensorNet and QET models in JAX. A pre-trained PyTorch potential is converted to a JAX
parameter tree and JIT-compiled by XLA into a single fused program, giving a portable (CPU / CUDA / Apple-Silicon)
~2-3.5x speedup over eager PyTorch for the MD / relaxation inner loop — without the NVIDIA-Warp dependency. It
requires the jax extra (pip install matgl[jax]).
JAXPESCalculator is a drop-in twin of matgl.ext.ase.PESCalculator and plugs into the usual MolecularDynamics /
Relaxer workflows:
import matgl
from matgl.ext.jax import JAXPESCalculator
potential = matgl.load_model("TensorNet-PES-MatPES-r2SCAN-2025.2")
atoms.calc = JAXPESCalculator(potential, stress_unit="eV/A3") # any ASE Atoms
Energies, forces and stresses match the PyTorch reference to float64 precision. The backend is inference-only and
PyG-only; training still goes through the standard PyTorch path. See dev/jax_benchmark.py for the benchmark harness.
On Apple Silicon the JAX backend is the recommended inference path. With the
community applejax PJRT plugin
(pip install applejax), the same JAXPESCalculator runs on the Metal GPU
via MLX. Benchmarks on an M5 Pro for TensorNet (energy + forces + stress
per step) show:
Speedups relative to PyTorch CPU are shown in brackets.
| Atoms | PyTorch CPU | PyTorch MPS | JAX CPU | JAX Metal |
|---|---|---|---|---|
| 2 | 4.9 ms | 9.7 ms (0.50x) | 1.9 ms (2.62x) | 8.5 ms (0.57x) |
| 64 | 22.5 ms | 19.9 ms (1.13x) | 6.6 ms (3.43x) | 12.6 ms (1.79x) |
| 216 | 48.3 ms | 24.8 ms (1.95x) | 18.0 ms (2.69x) | 17.9 ms (2.70x) |
| 512 | 96.5 ms | 52.4 ms (1.84x) | 35.9 ms (2.69x) | 31.2 ms (3.09x) |
| 1000 | 189.6 ms | 100.6 ms (1.88x) | 72.9 ms (2.60x) | 57.7 ms (3.29x) |
| 5832 | 1057.6 ms | 591.9 ms (1.79x) | 708.5 ms (1.49x) | 437.7 ms (2.42x) |
JAX-Metal wins from ~500 atoms upward (1.3-1.7x over PyTorch MPS); JAX-CPU is
the fastest path for small cells. A 1 ps NVE MD on LiFePO4 with
TensorNet-PES-MatPES-r2SCAN-2025.2 shows energy drift of 0.0007 meV/atom/ps
on JAX-Metal vs 0.0006 meV/atom/ps on PyTorch-CPU (statistically identical).
Caveats: applejax is float32-only (the existing float64 parity tests are
relaxed automatically) and pins jaxlib==0.9.x, so install it in a separate
venv from the main matgl dev environment. QET is not yet supported on
JAX-Metal -- the Gaussian-smeared Coulomb potential uses jax.scipy.special.erf,
which applejax currently lowers to an unsupported stablehlo.composite op
(applejax issue).
Benchmarks on a single H200 and a single A40 for TensorNet-PES-MatPES-PBE-2025.2 (energy + forces + stress per step) show:
Speedups relative to PyTorch eager on the same GPU are shown in brackets. Bold marks the fastest non-eager backend at each size.
| Atoms | Eager (H200) | Warp (H200) | JAX (H200) | Eager (A40) | Warp (A40) | JAX (A40) |
|---|---|---|---|---|---|---|
| 2 | 14.5 ms | 18.9 ms (0.77x) | 3.8 ms (3.80x) | 26.2 ms | 32.6 ms (0.81x) | 12.3 ms (2.14x) |
| 64 | 14.8 ms | 19.2 ms (0.77x) | 4.3 ms (3.41x) | 26.9 ms | 32.8 ms (0.82x) | 6.2 ms (4.31x) |
| 216 | 16.3 ms | 20.0 ms (0.81x) | 6.8 ms (2.38x) | 30.3 ms | 33.1 ms (0.92x) | 10.2 ms (2.96x) |
| 512 | 19.4 ms | 21.5 ms (0.90x) | 8.9 ms (2.18x) | 52.6 ms | 39.9 ms (1.32x) | 17.0 ms (3.10x) |
| 1000 | 30.4 ms | 24.3 ms (1.25x) | 14.6 ms (2.08x) | 96.9 ms | 37.5 ms (2.59x) | 33.4 ms (2.90x) |
| 1728 | 48.1 ms | 28.8 ms (1.67x) | 28.5 ms (1.69x) | 163.9 ms | 49.1 ms (3.34x) | 71.8 ms (2.28x) |
| 2744 | 72.6 ms |
Larger systems and slower GPUs benefit more from Warp.
In the PES training, the unit of energies, forces and stresses (optional) in the training, validation and test sets is extremely important to be consistent with the unit used in MatGL.
Note: For stresses, we use the convention that compressive stress gives negative values. Stresses obtained from VASP calculations (default unit is kBar) should be multiplied by -0.1 to work directly with the model.
Potential and MGLPotentialTrainer apply E = data_std * E_model + data_mean + E_element_refs.
data_mean is a constant added once per structure, independent of atom count. Keep it at 0.0 for an
extensive potential and supply atomic energy offsets through element_refs. data_std scales total
energies and their derivatives, including forces and stresses.
For structure-property training, ModelLightningModule applies its data_mean and data_std in the loss.
Calling lit.model.save(...) saves the raw model. To retain that normalization in an inference model, save
the existing target-transform wrapper:
from matgl.data.transformer import Normalizer
from matgl.models import TransformedTargetModel
exported = TransformedTargetModel(lit.model, Normalizer(lit.data_mean, lit.data_std))
exported.save("property-model")
MGLPotentialTrainermatgl.utils.training.MGLPotentialTrainer is a high-level wrapper around PotentialLightningModule and pl.Trainer with sensible MatPES-tuned defaults (Huber loss, stress weight 0.1, Adam + CosineAnnealingLR). Dataset construction is delegated to a sibling MGLDatasetLoader factory; the trainer itself only consumes pre-built MGLDatasets.
The CLI accepts MatPES-shaped JSON/JSONL and ASE Extended XYZ files. To train TensorNet from scratch and evaluate the saved potential:
mgl train -i training.extxyz -m TensorNet -o TensorNet-local --epochs 100
mgl evaluate -i test.extxyz -m TensorNet-local
For Extended XYZ, the default fields are energy, forces, stress, charges, and magmoms; override them with --energy-key, --forces-key, and the corresponding key options. Energies and forces are expected in eV and eV/Å. Stress defaults to ASE's eV/ų convention and is converted to matgl's GPa convention. Periodic frames use Structure2Graph, while fully nonperiodic frames use Molecule2Graph; one file cannot mix the two. Use --model-kwargs '{"units": 128, "nblocks": 3}' for architecture options, or pass a saved potential instead of TensorNet to fine-tune it.
The geometry commands also accept multi-frame .xyz and .extxyz files. mgl predict reports one result per frame; mgl relax -s _relaxed writes all relaxed frames back to Extended XYZ; and mgl md starts one simulation per frame, using _0, _1, and so on in trajectory/log names. Nonperiodic molecular relaxation requires --no-relax-cell.
from matgl.models import TensorNet
from matgl import MGLDatasetLoader, MGLPotentialTrainer
# 1. Download the r2SCAN MatPES dataset + per-element isolated-atom offsets
# from materialyze/matpes on Hugging Face. One loader holds the shared HF
# auth / cache config; both calls go through it.
loader = MGLDatasetLoader()
ds = loader.matpes_dataset(version="R2SCAN-2025.2")
refs = loader.matpes_element_refs(version="R2SCAN-2025.2", element_types=ds.element_types)
# 2. Build the model on the same element_types as the dataset.
model = TensorNet(element_types=ds.element_types, is_intensive=False, cutoff=5.0)
# 3. Configure once, fit when asked.
trainer = MGLPotentialTrainer(
model,
energy_weight=1.0,
force_weight=1.0,
stress_weight=0.1,
lr=1e-3,
batch_size=32,
max_epochs=200,
accelerator="gpu", # "auto" / "cpu" / "gpu" / "cuda" / "mps" / "tpu"
devices=1,
)
potential = trainer.fit(dataset=ds, atomrefs=refs, save_path="./MatPES-TensorNet")
# trainer.potential / .lit_module / .trainer / .loaders / .dataset / .atomrefs
# are populated for inspection.
MGLDatasetLoader() defaults to the materialyze/matpes HF dataset repo; override repo_id / revision / token / cache_dir in the constructor to point at a fork or a private mirror.
MGLPotentialTrainer instantiates pl.Trainer inside fit() and forwards everything in trainer_kwargs verbatim, so any Lightning logger / callbacks setup works unchanged. The PotentialLightningModule logs Total_Loss, Energy_MAE, Force_MAE, Stress_MAE (plus Magmom_MAE / Charge_MAE when those heads are active) and the matching *_RMSE keys, each prefixed with train_ / val_ / test_ — those are the names a checkpoint / early-stopping / logger sees. For per-epoch dumps of every prediction / label / error in stable sample order, matgl ships matgl.utils.callbacks.PredictionLogger.
matgl.load_model(...) returns a Potential whose inner graph model is potential.model. Pass that inner model into MGLPotentialTrainer to keep the pretrained weights as the initialisation; pair it with a low learning rate, fewer epochs, and (often) zero or reduced stress weight if the fine-tuning dataset doesn't carry stresses.
from matgl import load_model, MGLDatasetLoader, MGLPotentialTrainer
# 1. Load the foundation potential and extract the inner graph model.
pretrained = load_model("TensorNet-PES-MatPES-r2SCAN-2025.2")
model = pretrained.model # the bare TensorNet — pretrained weights intact
# 2. Build / load the fine-tuning dataset. Use MGLDatasetLoader for MatPES, or
# construct an MGLDataset yourself from your own structures + labels.
loader = MGLDatasetLoader()
ds = loader.matpes_dataset(version="R2SCAN-2025.2", element_types=model.element_types)
# 3. Reuse the MatPES atomic references so the loss starts in the right energy
# range. Reorder them to the model's element_types.
refs = loader.matpes_element_refs(version="R2SCAN-2025.2", element_types=model.element_types)
# 4. Fine-tune with a low LR and short schedule. inference_mode is set to False
# automatically by MGLPotentialTrainer (autograd-driven force / stress).
trainer = MGLPotentialTrainer(
model,
energy_weight=1.0,
force_weight=10.0, # bump force weight; energies are already in scale
stress_weight=0.0, # set to 0 if your fine-tune set has no stress
lr=1e-4, # one to two orders of magnitude lower than from-scratch
decay_steps=200,
max_epochs=50,
accelerator="gpu",
)
finetuned = trainer.fit(dataset=ds, atomrefs=refs, save_path="./TensorNet-finetuned")
The same pattern works for any pretrained Potential from the materialyze HF organisation — extract pretrained.model, hand it to MGLPotentialTrainer, and fit. For datasets without stress labels (e.g. cluster / dimer extxyz), set stress_weight=0 in the trainer constructor so the stress term is dropped from the loss.
We wrote tutorials on how to use MatGL. These were generated from [Jupyter notebooks] jupyternb, which can be directly run on Google Colab.
matgl, especially for developers wishing to
train and contribute matgl models.A manuscript for MatGL has been published in npj Computational Materials. please cite the following:
MatGL
Ko, T. W.; Deng, B.; Nassar, M.; Barroso-Luque, L.; Liu, R.; Qi, J.; Thakur, A. C.; Mishra, A. R.; Liu, E.; Ceder, G.; Miret, S.; Ong, S. P. Materials Graph Library (MatGL), an Open-Source Graph Deep Learning Library for Materials Science and Chemistry. npj Comput Mater 11, 253 (2025). DOI: https://doi.org/10.1038/s41524-025-01742-y.
If you are using any of the pretrained models, please cite the relevant works below:
MEGNet
Chen, C.; Ye, W.; Zuo, Y.; Zheng, C.; Ong, S. P. Graph Networks as a Universal Machine Learning Framework for Molecules and Crystals. Chem. Mater. 2019, 31 (9), 3564–3572. DOI: 10.1021/acs.chemmater.9b01294.
Multi-fidelity MEGNet
Chen, C.; Zuo, Y.; Ye, W.; Li, X.; Ong, S. P. Learning Properties of Ordered and Disordered Materials from Multi-Fidelity Data. Nature Computational Science, 2021, 1, 46–53. DOI: 10.1038/s43588-020-00002-x.
M3GNet
Chen, C., Ong, S.P. A universal graph deep learning interatomic potential for the periodic table. Nature Computational Science, 2023, 2, 718–728. DOI: 10.1038/s43588-022-00349-3.
CHGNet
Deng, B., Zhong, P., Jun, K. et al. CHGNet: as a pretrained universal neural network potential for charge-informed atomistic modelling. Nat Mach Intell 5, 1031–1041 (2023). DOI:10.1038/s42256-023-00716-3
TensorNet
Simeon, G. De Fabritiis, G. Tensornet: Cartesian tensor representations for efficient learning of molecular potentials. Adv. Neural Info. Process. Syst. 36, (2024). DOI: 10.48550/arXiv.2306.06482
SO3Net
Schütt, K. T., Hessmann, S. S. P., Gebauer, N. W. A., Lederer, J., Gastegger, M. SchNetPack 2.0: A neural network toolbox for atomistic machine learning. J. Chem. Phys. 158, 144801 (2023). DOI: 10.1063/5.0138367
QET
Ko, T. W., Liu, R., Mishra, A. R., Yu, Z., Qi, J., Ong, S. P. A Fast, Accurate, and Reactive Equivariant Foundation Potential. arXiv preprint arXiv:2511.07249 (2025). DOI: 10.48550/arXiv.2511.07249
The M3GNet-MP-2021.2.8-PES differs from the original TensorFlow (TF) implementation!
Answer: M3GNet-MP-2021.2.8-PES is a refitted model with some data improvements and minor architectural changes.
Porting over the weights from the TF version to DGL/PyTorch is non-trivial. We have performed reasonable benchmarking
to ensure that the new implementation reproduces the broad error characteristics of the original TF implementation
(see examples). However, it is not expected to reproduce the TF version exactly. This refitted model
serves as a baseline for future model improvements. We do not believe there is value in expending the resources
to reproduce the TF version exactly.
I am getting errors with matgl.load_model()!
Answer: The most likely reason is that you have a cached older version of the model. We often refactor models to
ensure the best implementation. This can usually be solved by updating your matgl to the latest version
and clearing your cache using the following command mgl clear. On the next run, the latest model will be
downloaded. With effect from v0.5.2, we have implemented a model versioning scheme that will detect code vs model
version conflicts and alert the user of such problems.
What pre-trained models should I be using?
Answer: There is no one definitive answer. In general, the newer the architecture and dataset, the more likely the model performs better. However, it should also be noted that a model operating on a more diverse dataset may compromise on performance on a specific system. The best way is to look at the READMEs included with each model and do some tests on the systems you are interested in.
How do I contribute to matgl?
Answer: For code contributions, please fork and submit pull requests. You should read the
developer guide to understand the general design guidelines. We welcome pre-trained model
contributions as well, which should also be submitted via PRs. Please follow the folder structure of the
pretrained models. In particular, we expect all models to come with a README.md and notebook
documenting its use and its key performance metrics. Also, we expect contributions to be on new properties
or systems or to significantly outperform the existing models. We will develop an alternative means for model
sharing in the future.
This work was primarily supported by the Materials Project, funded by the U.S. Department of Energy, Office of Science, Office of Basic Energy Sciences, Materials Sciences and Engineering Division under contract no. DE-AC02-05-CH11231: Materials Project program KC23MP. This work used the Expanse supercomputing cluster at the Extreme Science and Engineering Discovery Environment (XSEDE), which is supported by National Science Foundation grant number ACI-1548562.
We also acknowledge the NVIDIA Alchemi Team, specifically Roman Zubatyuk (@zubatyuk) and Alireza Moradzadeh (@moradza), for their contributions to warp-acceleration for TensorNet, which yielded ~2-3x speed and memory usage improvements.
| 62.3 ms (1.16x) |
| 258.4 ms |
| 67.4 ms (3.84x) |
| 150.6 ms (1.72x) |
| 4096 | 105.9 ms | 43.4 ms (2.44x) | 44.8 ms (2.36x) | 385.7 ms | 100.3 ms (3.84x) | 156.9 ms (2.46x) |
None of your models do what I need. Where can I get help?
Answer: Please contact Prof Ong with a brief description of your needs. For simple problems, we are glad to advise and point you in the right direction. For more complicated problems, we are always open to academic collaborations or projects. We also offer consulting services for companies with unique needs, including but not limited to custom data generation, model development and materials design.