Custom Plugin Development
Build your own domain plugin to extend MatCraft with new material classes.
Custom Plugin Development
If none of the 16 built-in domains fits your material class, you can create a custom domain plugin. Plugins are standard Python packages that implement the DomainPlugin interface and register via entry points.
Plugin Interface
Every domain plugin must implement the DomainPlugin abstract base class:
from materia.plugins import DomainPlugin
from materia.types import ParameterSpec, ObjectiveSpec, EvaluationResult
class MyDomain(DomainPlugin):
"""Custom domain for shape-memory alloy optimization."""
@property
def name(self) -> str:
return "shape-memory-alloy"
@property
def description(self) -> str:
return "NiTi shape memory alloy composition and processing"
def default_parameters(self) -> list[ParameterSpec]:
return [
ParameterSpec(
name="ni_content",
type="continuous",
bounds=(49.0, 51.5),
unit="at%",
description="Nickel content in atomic percent",
),
ParameterSpec(
name="aging_temp",
type="integer",
bounds=(300, 600),
unit="C",
description="Aging treatment temperature",
),
ParameterSpec(
name="aging_time",
type="continuous",
bounds=(0.5, 48.0),
unit="hours",
description="Aging treatment duration",
),
]
def default_objectives(self) -> list[ObjectiveSpec]:
return [
ObjectiveSpec(
name="transformation_temp",
direction="maximize",
unit="C",
),
ObjectiveSpec(
name="recoverable_strain",
direction="maximize",
unit="%",
),
]
def evaluate(self, params: dict[str, float | int | str]) -> EvaluationResult:
ni = params["ni_content"]
aging_t = params["aging_temp"]
aging_h = params["aging_time"]
# Your physics model or ML prediction here
transformation_temp = self._calc_transformation_temp(ni, aging_t, aging_h)
strain = self._calc_recoverable_strain(ni, aging_t, aging_h)
return EvaluationResult(
objectives={
"transformation_temp": transformation_temp,
"recoverable_strain": strain,
},
metadata={
"phase": "B2" if ni < 50.5 else "B19'",
},
)
def validate(self, params: dict) -> list[str]:
errors = []
if params.get("ni_content", 50) < 49:
errors.append("Ni content below minimum for shape memory effect")
return errorsProject Structure
A plugin package has this structure:
my-materia-plugin/
pyproject.toml
src/
my_plugin/
__init__.py
domain.py # DomainPlugin implementation
physics.py # Evaluation models
components.yaml # Default component data (optional)
tests/
test_domain.pyRegistration via Entry Points
Register your domain in pyproject.toml:
[project]
name = "materia-shape-memory"
version = "0.1.0"
dependencies = ["materia>=1.0"]
[project.entry-points."materia.domains"]
shape-memory-alloy = "my_plugin.domain:MyDomain"After installation (pip install -e .), your domain will be automatically discovered:
materia init --list-domains
# Output includes: shape-memory-alloy NiTi shape memory alloy composition and processingThe evaluate() Method
This is the core of your plugin. It receives a dictionary of parameter values and must return an EvaluationResult with objective values:
def evaluate(self, params: dict) -> EvaluationResult:
# params = {"ni_content": 50.2, "aging_temp": 450, "aging_time": 12.0}
# Option 1: Analytical model
result = self.physics_model(params)
# Option 2: Pre-trained ML model
features = self.encode_features(params)
result = self.ml_model.predict(features)
# Option 3: External simulation call
result = self.run_simulation(params)
# Option 4: Experimental lookup (for closed-loop experiments)
result = self.query_experiment_database(params)
return EvaluationResult(objectives=result)Validation Hook
The validate() method lets you add domain-specific validation rules:
def validate(self, params: dict) -> list[str]:
errors = []
if params.get("aging_temp", 0) > 500 and params.get("aging_time", 0) > 24:
errors.append("Aging above 500C for >24h causes grain growth issues")
return errorsTemplates
Optionally provide templates that users can access via materia init —template:
def templates(self) -> dict[str, str]:
return {
"actuator": "templates/actuator.yaml",
"biomedical": "templates/biomedical.yaml",
}Testing Your Plugin
Write tests to verify your evaluation function:
import pytest
from my_plugin.domain import MyDomain
def test_evaluation():
domain = MyDomain()
result = domain.evaluate({
"ni_content": 50.5,
"aging_temp": 450,
"aging_time": 12.0,
})
assert "transformation_temp" in result.objectives
assert "recoverable_strain" in result.objectives
assert result.objectives["transformation_temp"] > 0
def test_validation():
domain = MyDomain()
errors = domain.validate({"ni_content": 48.0})
assert len(errors) > 0Publishing
Publish your plugin to PyPI so others can use it:
pip install build twine
python -m build
twine upload dist/*Users install and use it:
pip install materia-shape-memory
materia init my-sma --domain shape-memory-alloyBest Practices
- Keep the evaluation function fast: Aim for under 1 second per evaluation. If your physics model is slow, consider pre-computing a lookup table or training a fast surrogate offline.
- Return NaN for failed evaluations: If a parameter combination is physically invalid, return NaN objectives rather than raising an exception.
- Include metadata: Return auxiliary information (phase, microstructure, intermediate calculations) in the
metadatafield for post-hoc analysis. - Document parameter ranges: Provide clear descriptions and physically motivated bounds for all default parameters.
- Version your evaluation model: If the physics model changes between plugin versions, users need to know which version produced their results.