jupytermind 0.3.0
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.
- package/.github/skills/ai-chemistry-scientist/SKILL.md +97 -0
- package/.github/skills/ai-chemistry-scientist/manifest.json +156 -0
- package/.github/skills/ai-data-scientist/SKILL.md +330 -0
- package/.github/skills/ai-genomics-scientist/SKILL.md +98 -0
- package/.github/skills/ai-genomics-scientist/manifest.json +93 -0
- package/.github/skills/ai-materials-scientist/SKILL.md +51 -0
- package/.github/skills/ai-materials-scientist/manifest.json +58 -0
- package/.github/skills/ai-scientist/SKILL.md +69 -0
- package/.github/skills/ai-scientist/manifest.json +61 -0
- package/.github/skills/ai-structural-biology-scientist/SKILL.md +67 -0
- package/.github/skills/ai-structural-biology-scientist/manifest.json +72 -0
- package/.github/skills/japanese-prose/NOTICE.md +17 -0
- package/.github/skills/japanese-prose/SKILL.md +111 -0
- package/.github/skills/japanese-prose/references/review-workflow.md +50 -0
- package/.github/skills/japanese-prose/references/scoring.md +24 -0
- package/.github/skills/japanese-prose/references/writing-guidelines.md +60 -0
- package/.github/skills/japanese-prose/scripts/core.py +192 -0
- package/.github/skills/japanese-prose/scripts/fixtures/natural.md +5 -0
- package/.github/skills/japanese-prose/scripts/fixtures/unnatural.md +5 -0
- package/.github/skills/japanese-prose/scripts/lint.py +378 -0
- package/.github/skills/japanese-prose/scripts/outline.py +68 -0
- package/.github/skills/japanese-prose/scripts/terms.py +112 -0
- package/.github/skills/japanese-prose/scripts/test_engine.py +117 -0
- package/.github/skills/presentation-planner/SKILL.md +257 -0
- package/.github/skills/presentation-planner/assets/design-templates/data-report.yaml +97 -0
- package/.github/skills/presentation-planner/assets/design-templates/executive-proposal.yaml +92 -0
- package/.github/skills/presentation-planner/assets/design-templates/technical-briefing.yaml +96 -0
- package/.github/skills/presentation-planner/assets/scenario-templates/data-report.md +47 -0
- package/.github/skills/presentation-planner/assets/scenario-templates/executive-decision.md +43 -0
- package/.github/skills/presentation-planner/assets/scenario-templates/technical-briefing.md +45 -0
- package/.github/skills/presentation-planner/references/customizing-design-templates.md +160 -0
- package/.github/skills/presentation-planner/references/design-spec-schema.md +72 -0
- package/.github/skills/presentation-planner/references/handoff-contract.md +49 -0
- package/.github/skills/presentation-planner/references/responsibility-boundary.md +32 -0
- package/.github/skills/presentation-planner/references/scenario-templates.md +55 -0
- package/.github/skills/tech-writer/SKILL.md +434 -0
- package/.github/skills/tech-writer/assets/templates/blueprint.md +187 -0
- package/.github/skills/tech-writer/assets/templates/design-doc.md +29 -0
- package/.github/skills/tech-writer/assets/templates/migration-plan.md +173 -0
- package/.github/skills/tech-writer/assets/templates/operations-runbook.md +202 -0
- package/.github/skills/tech-writer/assets/templates/pr-description.md +23 -0
- package/.github/skills/tech-writer/assets/templates/qiita.md +44 -0
- package/.github/skills/tech-writer/assets/templates/readme.md +38 -0
- package/.github/skills/tech-writer/assets/templates/requirements-definition.md +170 -0
- package/.github/skills/tech-writer/assets/templates/rfi.md +113 -0
- package/.github/skills/tech-writer/assets/templates/rfp.md +180 -0
- package/.github/skills/tech-writer/assets/templates/security-design.md +167 -0
- package/.github/skills/tech-writer/assets/templates/system-design.md +220 -0
- package/.github/skills/tech-writer/assets/templates/technical-proposal.md +112 -0
- package/.github/skills/tech-writer/assets/templates/test-plan.md +153 -0
- package/.github/skills/tech-writer/assets/templates/user-manual.md +22 -0
- package/.github/skills/tech-writer/assets/templates/white-paper.md +192 -0
- package/.github/skills/tech-writer/references/doctypes/api-docs.md +33 -0
- package/.github/skills/tech-writer/references/doctypes/blueprint.md +81 -0
- package/.github/skills/tech-writer/references/doctypes/code-comments.md +39 -0
- package/.github/skills/tech-writer/references/doctypes/design-doc.md +42 -0
- package/.github/skills/tech-writer/references/doctypes/migration-plan.md +63 -0
- package/.github/skills/tech-writer/references/doctypes/operations-runbook.md +63 -0
- package/.github/skills/tech-writer/references/doctypes/pr-commit.md +82 -0
- package/.github/skills/tech-writer/references/doctypes/qiita.md +75 -0
- package/.github/skills/tech-writer/references/doctypes/readme.md +43 -0
- package/.github/skills/tech-writer/references/doctypes/release-notes.md +30 -0
- package/.github/skills/tech-writer/references/doctypes/requirements-definition.md +61 -0
- package/.github/skills/tech-writer/references/doctypes/rfi.md +43 -0
- package/.github/skills/tech-writer/references/doctypes/rfp.md +46 -0
- package/.github/skills/tech-writer/references/doctypes/security-design.md +71 -0
- package/.github/skills/tech-writer/references/doctypes/system-design.md +74 -0
- package/.github/skills/tech-writer/references/doctypes/technical-proposal.md +49 -0
- package/.github/skills/tech-writer/references/doctypes/test-plan.md +67 -0
- package/.github/skills/tech-writer/references/doctypes/user-manual.md +58 -0
- package/.github/skills/tech-writer/references/doctypes/white-paper.md +84 -0
- package/.github/skills/tech-writer/references/doctypes/zenn.md +66 -0
- package/.github/skills/tech-writer/references/japanese-prose-optimization.md +110 -0
- package/.github/skills/tech-writer/references/style-constitution.md +104 -0
- package/.github/skills/tech-writer/scripts/lint.py +412 -0
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/bin/ai-data-scientist.js +123 -0
- package/package.json +41 -0
- package/pyproject.toml +45 -0
- package/src/ai_chemistry_scientist/__init__.py +0 -0
- package/src/ai_chemistry_scientist/admet_prediction.py +71 -0
- package/src/ai_chemistry_scientist/bioactivity_classification.py +73 -0
- package/src/ai_chemistry_scientist/data/sample_molecules.csv +21 -0
- package/src/ai_chemistry_scientist/dispatch.py +369 -0
- package/src/ai_chemistry_scientist/docking_score.py +97 -0
- package/src/ai_chemistry_scientist/drug_likeness_rules.py +84 -0
- package/src/ai_chemistry_scientist/evidence.py +41 -0
- package/src/ai_chemistry_scientist/molecular_descriptors.py +97 -0
- package/src/ai_chemistry_scientist/molecular_formula_mass.py +40 -0
- package/src/ai_chemistry_scientist/molecular_similarity.py +78 -0
- package/src/ai_chemistry_scientist/qsar_modeling.py +105 -0
- package/src/ai_chemistry_scientist/salt_standardization.py +81 -0
- package/src/ai_chemistry_scientist/structural_alerts.py +76 -0
- package/src/ai_chemistry_scientist/structure_format_conversion.py +84 -0
- package/src/ai_chemistry_scientist/validation.py +70 -0
- package/src/ai_data_scientist/__init__.py +0 -0
- package/src/ai_data_scientist/analysis_assumptions.py +121 -0
- package/src/ai_data_scientist/anomaly_detection.py +39 -0
- package/src/ai_data_scientist/automl.py +109 -0
- package/src/ai_data_scientist/cleaning.py +56 -0
- package/src/ai_data_scientist/cli.py +90 -0
- package/src/ai_data_scientist/clustering.py +54 -0
- package/src/ai_data_scientist/dashboard.py +33 -0
- package/src/ai_data_scientist/data_definition.py +100 -0
- package/src/ai_data_scientist/data_quality.py +164 -0
- package/src/ai_data_scientist/dataset_validation.py +135 -0
- package/src/ai_data_scientist/dependency_pins.py +60 -0
- package/src/ai_data_scientist/eda.py +82 -0
- package/src/ai_data_scientist/experiment_evaluation.py +635 -0
- package/src/ai_data_scientist/explainability.py +340 -0
- package/src/ai_data_scientist/feature_engineering.py +163 -0
- package/src/ai_data_scientist/gate_config.py +32 -0
- package/src/ai_data_scientist/ingestion.py +127 -0
- package/src/ai_data_scientist/insight_engine.py +180 -0
- package/src/ai_data_scientist/japanese_nlp.py +43 -0
- package/src/ai_data_scientist/jupyter_launcher.py +137 -0
- package/src/ai_data_scientist/jupyter_mcp_client.py +94 -0
- package/src/ai_data_scientist/language_router.py +28 -0
- package/src/ai_data_scientist/lifecycle.py +221 -0
- package/src/ai_data_scientist/mcp_gateway.py +113 -0
- package/src/ai_data_scientist/mcp_runtime.py +194 -0
- package/src/ai_data_scientist/mcp_transport.py +53 -0
- package/src/ai_data_scientist/ml_modeling.py +451 -0
- package/src/ai_data_scientist/model_tuning.py +104 -0
- package/src/ai_data_scientist/notebook_audit.py +574 -0
- package/src/ai_data_scientist/project_manager.py +243 -0
- package/src/ai_data_scientist/report_export.py +73 -0
- package/src/ai_data_scientist/sensitivity.py +445 -0
- package/src/ai_data_scientist/signal_analysis.py +201 -0
- package/src/ai_data_scientist/skill_packaging.py +40 -0
- package/src/ai_data_scientist/stats_analysis.py +88 -0
- package/src/ai_data_scientist/text_nlp.py +44 -0
- package/src/ai_data_scientist/timeseries.py +68 -0
- package/src/ai_data_scientist/visualization.py +708 -0
- package/src/ai_genomics_scientist/__init__.py +1 -0
- package/src/ai_genomics_scientist/differential_expression.py +147 -0
- package/src/ai_genomics_scientist/dispatch.py +267 -0
- package/src/ai_genomics_scientist/evidence.py +45 -0
- package/src/ai_genomics_scientist/gene_set_enrichment.py +76 -0
- package/src/ai_genomics_scientist/sequence_alignment.py +97 -0
- package/src/ai_genomics_scientist/sequence_features.py +111 -0
- package/src/ai_genomics_scientist/splice_site_scoring.py +66 -0
- package/src/ai_genomics_scientist/validation.py +83 -0
- package/src/ai_genomics_scientist/variant_effect.py +147 -0
- package/src/ai_genomics_scientist/variant_pathogenicity.py +125 -0
- package/src/ai_materials_scientist/__init__.py +0 -0
- package/src/ai_materials_scientist/calphad.py +117 -0
- package/src/ai_materials_scientist/classical_monte_carlo.py +165 -0
- package/src/ai_materials_scientist/crystal_plasticity.py +184 -0
- package/src/ai_materials_scientist/dispatch.py +100 -0
- package/src/ai_materials_scientist/evidence.py +84 -0
- package/src/ai_materials_scientist/fem.py +279 -0
- package/src/ai_materials_scientist/kinetic_monte_carlo.py +145 -0
- package/src/ai_materials_scientist/molecular_dynamics.py +240 -0
- package/src/ai_materials_scientist/phase_field.py +167 -0
- package/src/ai_materials_scientist/validation.py +70 -0
- package/src/ai_scientist/__init__.py +1 -0
- package/src/ai_scientist/completion_gate.py +15 -0
- package/src/ai_scientist/data_analysis.py +46 -0
- package/src/ai_scientist/evidence_registry.py +99 -0
- package/src/ai_scientist/experimental_design.py +20 -0
- package/src/ai_scientist/language.py +14 -0
- package/src/ai_scientist/latex_renderer.py +41 -0
- package/src/ai_scientist/literature_review.py +37 -0
- package/src/ai_scientist/manifest.py +87 -0
- package/src/ai_scientist/manuscript.py +94 -0
- package/src/ai_scientist/mcp_config.py +76 -0
- package/src/ai_scientist/mcp_external.py +42 -0
- package/src/ai_scientist/mcp_failures.py +23 -0
- package/src/ai_scientist/mcp_gateway.py +38 -0
- package/src/ai_scientist/mcp_managed.py +180 -0
- package/src/ai_scientist/npm_packaging.py +49 -0
- package/src/ai_scientist/orchestrator.py +133 -0
- package/src/ai_scientist/peer_review.py +60 -0
- package/src/ai_scientist/phase_gate.py +74 -0
- package/src/ai_scientist/phase_state.py +230 -0
- package/src/ai_scientist/presentation.py +56 -0
- package/src/ai_scientist/project_config.py +31 -0
- package/src/ai_scientist/project_handle.py +74 -0
- package/src/ai_scientist/reproducibility.py +20 -0
- package/src/ai_scientist/research_planning.py +20 -0
- package/src/ai_scientist/skill_invocation.py +21 -0
- package/src/ai_scientist/tdd_gate.py +99 -0
- package/src/ai_structural_biology_scientist/__init__.py +0 -0
- package/src/ai_structural_biology_scientist/contact_map.py +87 -0
- package/src/ai_structural_biology_scientist/dispatch.py +269 -0
- package/src/ai_structural_biology_scientist/evidence.py +43 -0
- package/src/ai_structural_biology_scientist/hydrophobicity.py +101 -0
- package/src/ai_structural_biology_scientist/protein_docking_score.py +104 -0
- package/src/ai_structural_biology_scientist/secondary_structure.py +95 -0
- package/src/ai_structural_biology_scientist/structural_similarity.py +74 -0
- package/src/ai_structural_biology_scientist/validation.py +100 -0
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
"""Simplified finite-element field solver module (DES-AIMS-060 / REQ-AIMS-060)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from ai_materials_scientist.evidence import record_run
|
|
8
|
+
from ai_materials_scientist.validation import register_validator, validate_parameters
|
|
9
|
+
|
|
10
|
+
_MODULE_NAME = "finite-element"
|
|
11
|
+
_ALLOWED_MESH_TYPES = ("line", "quad2d")
|
|
12
|
+
_ALLOWED_ELEMENT_ORDER = "linear"
|
|
13
|
+
_RECIPROCAL_CONDITION_TOL = 1e-10
|
|
14
|
+
_GAUSS_POINT = 1.0 / np.sqrt(3.0)
|
|
15
|
+
_GAUSS_POINTS = [
|
|
16
|
+
(-_GAUSS_POINT, -_GAUSS_POINT),
|
|
17
|
+
(_GAUSS_POINT, -_GAUSS_POINT),
|
|
18
|
+
(_GAUSS_POINT, _GAUSS_POINT),
|
|
19
|
+
(-_GAUSS_POINT, _GAUSS_POINT),
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _quad_shape_derivs(xi: float, eta: float) -> tuple[np.ndarray, np.ndarray]:
|
|
24
|
+
dN_dxi = 0.25 * np.array([-(1 - eta), (1 - eta), (1 + eta), -(1 + eta)])
|
|
25
|
+
dN_deta = 0.25 * np.array([-(1 - xi), -(1 + xi), (1 + xi), (1 - xi)])
|
|
26
|
+
return dN_dxi, dN_deta
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _plane_strain_constitutive(E: float, nu: float) -> np.ndarray:
|
|
30
|
+
factor = E / ((1 + nu) * (1 - 2 * nu))
|
|
31
|
+
return factor * np.array(
|
|
32
|
+
[
|
|
33
|
+
[1 - nu, nu, 0],
|
|
34
|
+
[nu, 1 - nu, 0],
|
|
35
|
+
[0, 0, (1 - 2 * nu) / 2],
|
|
36
|
+
]
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _assemble_line_heat(mesh: dict, conductivity: float) -> tuple[np.ndarray, np.ndarray]:
|
|
41
|
+
node_coords = np.asarray(mesh["node_coords"], dtype=np.float64)
|
|
42
|
+
n = len(node_coords)
|
|
43
|
+
K = np.zeros((n, n))
|
|
44
|
+
f = np.zeros(n)
|
|
45
|
+
for i, j in mesh["elements"]:
|
|
46
|
+
length = abs(node_coords[j] - node_coords[i])
|
|
47
|
+
k_local = (conductivity / length) * np.array([[1, -1], [-1, 1]])
|
|
48
|
+
for a, node_a in enumerate((i, j)):
|
|
49
|
+
for b, node_b in enumerate((i, j)):
|
|
50
|
+
K[node_a, node_b] += k_local[a, b]
|
|
51
|
+
return K, f
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _assemble_quad_heat(mesh: dict, conductivity: float) -> tuple[np.ndarray, np.ndarray]:
|
|
55
|
+
node_coords = np.asarray(mesh["node_coords"], dtype=np.float64)
|
|
56
|
+
n = len(node_coords)
|
|
57
|
+
K = np.zeros((n, n))
|
|
58
|
+
f = np.zeros(n)
|
|
59
|
+
for element in mesh["elements"]:
|
|
60
|
+
coords = node_coords[list(element)]
|
|
61
|
+
k_local = np.zeros((4, 4))
|
|
62
|
+
for xi, eta in _GAUSS_POINTS:
|
|
63
|
+
dN_dxi, dN_deta = _quad_shape_derivs(xi, eta)
|
|
64
|
+
jacobian = np.array([dN_dxi, dN_deta]) @ coords
|
|
65
|
+
det_j = np.linalg.det(jacobian)
|
|
66
|
+
jacobian_inv = np.linalg.inv(jacobian)
|
|
67
|
+
grads = jacobian_inv @ np.array([dN_dxi, dN_deta])
|
|
68
|
+
k_local += conductivity * (grads.T @ grads) * det_j
|
|
69
|
+
for a, node_a in enumerate(element):
|
|
70
|
+
for b, node_b in enumerate(element):
|
|
71
|
+
K[node_a, node_b] += k_local[a, b]
|
|
72
|
+
return K, f
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _assemble_quad_elasticity(
|
|
76
|
+
mesh: dict, youngs_modulus: float, poisson_ratio: float
|
|
77
|
+
) -> tuple[np.ndarray, np.ndarray]:
|
|
78
|
+
node_coords = np.asarray(mesh["node_coords"], dtype=np.float64)
|
|
79
|
+
n_nodes = len(node_coords)
|
|
80
|
+
n_dofs = n_nodes * 2
|
|
81
|
+
K = np.zeros((n_dofs, n_dofs))
|
|
82
|
+
f = np.zeros(n_dofs)
|
|
83
|
+
D = _plane_strain_constitutive(youngs_modulus, poisson_ratio)
|
|
84
|
+
for element in mesh["elements"]:
|
|
85
|
+
coords = node_coords[list(element)]
|
|
86
|
+
k_local = np.zeros((8, 8))
|
|
87
|
+
for xi, eta in _GAUSS_POINTS:
|
|
88
|
+
dN_dxi, dN_deta = _quad_shape_derivs(xi, eta)
|
|
89
|
+
jacobian = np.array([dN_dxi, dN_deta]) @ coords
|
|
90
|
+
det_j = np.linalg.det(jacobian)
|
|
91
|
+
jacobian_inv = np.linalg.inv(jacobian)
|
|
92
|
+
grads = jacobian_inv @ np.array([dN_dxi, dN_deta])
|
|
93
|
+
dN_dx, dN_dy = grads[0], grads[1]
|
|
94
|
+
B = np.zeros((3, 8))
|
|
95
|
+
for a in range(4):
|
|
96
|
+
B[0, 2 * a] = dN_dx[a]
|
|
97
|
+
B[1, 2 * a + 1] = dN_dy[a]
|
|
98
|
+
B[2, 2 * a] = dN_dy[a]
|
|
99
|
+
B[2, 2 * a + 1] = dN_dx[a]
|
|
100
|
+
k_local += B.T @ D @ B * det_j
|
|
101
|
+
dofs = [2 * node + c for node in element for c in (0, 1)]
|
|
102
|
+
for a, dof_a in enumerate(dofs):
|
|
103
|
+
for b, dof_b in enumerate(dofs):
|
|
104
|
+
K[dof_a, dof_b] += k_local[a, b]
|
|
105
|
+
return K, f
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _ndof_per_node(physics: str) -> int:
|
|
109
|
+
return 2 if physics == "plane_strain_elasticity" else 1
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _fem_validator(params: dict) -> dict:
|
|
113
|
+
"""DES-AIMS-002 registered validator for module_name='finite-element'."""
|
|
114
|
+
mesh = params["mesh"]
|
|
115
|
+
material_properties = params["material_properties"]
|
|
116
|
+
physics = params["physics"]
|
|
117
|
+
|
|
118
|
+
if physics not in ("heat_conduction", "plane_strain_elasticity"):
|
|
119
|
+
return {
|
|
120
|
+
"ok": False,
|
|
121
|
+
"parameter": "physics",
|
|
122
|
+
"constraint": "physics in ('heat_conduction', 'plane_strain_elasticity')",
|
|
123
|
+
}
|
|
124
|
+
if mesh["mesh_type"] not in _ALLOWED_MESH_TYPES:
|
|
125
|
+
return {
|
|
126
|
+
"ok": False,
|
|
127
|
+
"parameter": "mesh",
|
|
128
|
+
"constraint": f"mesh_type in {_ALLOWED_MESH_TYPES} (unstructured mesh rejected)",
|
|
129
|
+
}
|
|
130
|
+
if mesh["element_order"] != _ALLOWED_ELEMENT_ORDER:
|
|
131
|
+
return {
|
|
132
|
+
"ok": False,
|
|
133
|
+
"parameter": "element_order",
|
|
134
|
+
"constraint": "element_order == 'linear' (higher-order element rejected)",
|
|
135
|
+
}
|
|
136
|
+
if material_properties.get("nonlinear", False):
|
|
137
|
+
return {
|
|
138
|
+
"ok": False,
|
|
139
|
+
"parameter": "material_properties",
|
|
140
|
+
"constraint": "material law must be linear",
|
|
141
|
+
}
|
|
142
|
+
if physics == "plane_strain_elasticity" and mesh["mesh_type"] == "line":
|
|
143
|
+
return {
|
|
144
|
+
"ok": False,
|
|
145
|
+
"parameter": "physics",
|
|
146
|
+
"constraint": "plane_strain_elasticity is not supported on a 1D line mesh",
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if physics == "heat_conduction":
|
|
150
|
+
conductivity = material_properties["conductivity"]
|
|
151
|
+
if conductivity <= 0:
|
|
152
|
+
return {
|
|
153
|
+
"ok": False,
|
|
154
|
+
"parameter": "conductivity",
|
|
155
|
+
"constraint": "conductivity > 0",
|
|
156
|
+
}
|
|
157
|
+
elif physics == "plane_strain_elasticity":
|
|
158
|
+
youngs_modulus = material_properties["youngs_modulus"]
|
|
159
|
+
poisson_ratio = material_properties["poisson_ratio"]
|
|
160
|
+
if youngs_modulus <= 0:
|
|
161
|
+
return {
|
|
162
|
+
"ok": False,
|
|
163
|
+
"parameter": "youngs_modulus",
|
|
164
|
+
"constraint": "youngs_modulus > 0",
|
|
165
|
+
}
|
|
166
|
+
if not (-1 < poisson_ratio < 0.5):
|
|
167
|
+
return {
|
|
168
|
+
"ok": False,
|
|
169
|
+
"parameter": "poisson_ratio",
|
|
170
|
+
"constraint": "-1 < poisson_ratio < 0.5",
|
|
171
|
+
}
|
|
172
|
+
return {"ok": True}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
register_validator(_MODULE_NAME, _fem_validator)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def _validate(mesh, material_properties, boundary_conditions, physics) -> None:
|
|
179
|
+
result = validate_parameters(
|
|
180
|
+
_MODULE_NAME,
|
|
181
|
+
{
|
|
182
|
+
"mesh": mesh,
|
|
183
|
+
"material_properties": material_properties,
|
|
184
|
+
"boundary_conditions": boundary_conditions,
|
|
185
|
+
"physics": physics,
|
|
186
|
+
},
|
|
187
|
+
)
|
|
188
|
+
if not result["ok"]:
|
|
189
|
+
raise ValueError(f"{result['parameter']}: {result['constraint']}")
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# @id CODE-AIMS-060
|
|
193
|
+
# @implements REQ-AIMS-060 REQ-AIMS-003
|
|
194
|
+
# @design DES-AIMS-060
|
|
195
|
+
def assemble_and_check(
|
|
196
|
+
mesh: dict, material_properties: dict, boundary_conditions: dict, physics: str
|
|
197
|
+
) -> dict:
|
|
198
|
+
"""Assemble K, partition DOFs, and verify constrained_stiffness is well-posed.
|
|
199
|
+
|
|
200
|
+
Rejects an empty free-DOF set or a singular/underconstrained
|
|
201
|
+
constrained_stiffness (REQ-AIMS-003) before any solve is attempted.
|
|
202
|
+
"""
|
|
203
|
+
_validate(mesh, material_properties, boundary_conditions, physics)
|
|
204
|
+
|
|
205
|
+
if physics == "heat_conduction":
|
|
206
|
+
if mesh["mesh_type"] == "line":
|
|
207
|
+
K, f = _assemble_line_heat(mesh, material_properties["conductivity"])
|
|
208
|
+
else:
|
|
209
|
+
K, f = _assemble_quad_heat(mesh, material_properties["conductivity"])
|
|
210
|
+
else: # plane_strain_elasticity
|
|
211
|
+
K, f = _assemble_quad_elasticity(
|
|
212
|
+
mesh, material_properties["youngs_modulus"], material_properties["poisson_ratio"]
|
|
213
|
+
)
|
|
214
|
+
|
|
215
|
+
n_dofs = K.shape[0]
|
|
216
|
+
prescribed = boundary_conditions.get("prescribed", {})
|
|
217
|
+
loads = boundary_conditions.get("loads", {})
|
|
218
|
+
for dof, value in loads.items():
|
|
219
|
+
f[dof] += value
|
|
220
|
+
|
|
221
|
+
prescribed_dofs = sorted(prescribed.keys())
|
|
222
|
+
free_dofs = [d for d in range(n_dofs) if d not in prescribed]
|
|
223
|
+
|
|
224
|
+
if len(free_dofs) == 0:
|
|
225
|
+
return {"ok": False, "violated_condition": "free_dofs set is empty"}
|
|
226
|
+
|
|
227
|
+
u_prescribed = np.array([prescribed[d] for d in prescribed_dofs])
|
|
228
|
+
f_adjusted = f[free_dofs] - K[np.ix_(free_dofs, prescribed_dofs)] @ u_prescribed
|
|
229
|
+
constrained_stiffness = K[np.ix_(free_dofs, free_dofs)]
|
|
230
|
+
|
|
231
|
+
reciprocal_cond = 1.0 / np.linalg.cond(constrained_stiffness, 2)
|
|
232
|
+
if reciprocal_cond < _RECIPROCAL_CONDITION_TOL:
|
|
233
|
+
return {
|
|
234
|
+
"ok": False,
|
|
235
|
+
"violated_condition": "constrained_stiffness is singular/underconstrained",
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
"ok": True,
|
|
240
|
+
"constrained_stiffness": constrained_stiffness,
|
|
241
|
+
"free_dofs": free_dofs,
|
|
242
|
+
"prescribed_dofs": prescribed_dofs,
|
|
243
|
+
"u_prescribed": u_prescribed,
|
|
244
|
+
"rhs_adjusted": f_adjusted,
|
|
245
|
+
"n_dofs": n_dofs,
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# @id CODE-AIMS-905
|
|
250
|
+
# @implements REQ-AIMS-060
|
|
251
|
+
# @design DES-AIMS-060
|
|
252
|
+
def run_fem(mesh: dict, material_properties: dict, boundary_conditions: dict, physics: str) -> dict:
|
|
253
|
+
"""Assemble and solve the FEM system for the nodal field (REQ-AIMS-060)."""
|
|
254
|
+
assembly = assemble_and_check(mesh, material_properties, boundary_conditions, physics)
|
|
255
|
+
if not assembly["ok"]:
|
|
256
|
+
raise ValueError(f"free_dofs: {assembly['violated_condition']}")
|
|
257
|
+
|
|
258
|
+
u = np.zeros(assembly["n_dofs"])
|
|
259
|
+
u[assembly["prescribed_dofs"]] = assembly["u_prescribed"]
|
|
260
|
+
u[assembly["free_dofs"]] = np.linalg.solve(
|
|
261
|
+
assembly["constrained_stiffness"], assembly["rhs_adjusted"]
|
|
262
|
+
)
|
|
263
|
+
|
|
264
|
+
return {"nodal_values": u, "mesh": mesh}
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
# @id CODE-AIMS-906
|
|
268
|
+
# @implements REQ-AIMS-060 REQ-AIMS-004 REQ-AIMS-005
|
|
269
|
+
# @design DES-AIMS-060
|
|
270
|
+
def run_fem_with_evidence(**kwargs) -> dict:
|
|
271
|
+
"""Run FEM and wrap the result as a reproducible RunRecord (temperatures in Kelvin)."""
|
|
272
|
+
result = run_fem(**kwargs)
|
|
273
|
+
return record_run(
|
|
274
|
+
module_name=_MODULE_NAME,
|
|
275
|
+
unit_system="si-kelvin-meter" if kwargs["physics"] == "heat_conduction" else "si",
|
|
276
|
+
params={k: v for k, v in kwargs.items() if k != "mesh"},
|
|
277
|
+
arrays={"nodal_values": np.asarray(result["nodal_values"], dtype=np.float64)},
|
|
278
|
+
seed=None,
|
|
279
|
+
)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"""Kinetic Monte Carlo event-driven evolution module (DES-AIMS-040 / REQ-AIMS-040)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from ai_materials_scientist.evidence import record_run
|
|
8
|
+
from ai_materials_scientist.validation import (
|
|
9
|
+
check_finite_array,
|
|
10
|
+
register_validator,
|
|
11
|
+
validate_parameters,
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
_MODULE_NAME = "kinetic-monte-carlo"
|
|
15
|
+
_NUM_REALIZATIONS = 200
|
|
16
|
+
_HOP_DIRECTIONS = np.array([[1, 0], [-1, 0], [0, 1], [0, -1]])
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def _kmc_validator(params: dict) -> dict:
|
|
20
|
+
"""DES-AIMS-002 registered validator for module_name='kinetic-monte-carlo'."""
|
|
21
|
+
gamma = params["gamma"]
|
|
22
|
+
a = params["a"]
|
|
23
|
+
total_time = params["total_time"]
|
|
24
|
+
output_times = params["output_times"]
|
|
25
|
+
|
|
26
|
+
if gamma <= 0:
|
|
27
|
+
return {"ok": False, "parameter": "gamma", "constraint": "gamma > 0"}
|
|
28
|
+
if a <= 0:
|
|
29
|
+
return {"ok": False, "parameter": "a", "constraint": "a > 0"}
|
|
30
|
+
if total_time <= 0:
|
|
31
|
+
return {"ok": False, "parameter": "total_time", "constraint": "total_time > 0"}
|
|
32
|
+
if len(output_times) == 0:
|
|
33
|
+
return {
|
|
34
|
+
"ok": False,
|
|
35
|
+
"parameter": "output_times",
|
|
36
|
+
"constraint": "output_times must be non-empty",
|
|
37
|
+
}
|
|
38
|
+
finite_check = check_finite_array("output_times", np.asarray(output_times, dtype=np.float64))
|
|
39
|
+
if not finite_check["ok"]:
|
|
40
|
+
return finite_check
|
|
41
|
+
for output_time in output_times:
|
|
42
|
+
if not (0 < output_time <= total_time):
|
|
43
|
+
return {
|
|
44
|
+
"ok": False,
|
|
45
|
+
"parameter": "output_time",
|
|
46
|
+
"constraint": "0 < output_time <= total_time",
|
|
47
|
+
}
|
|
48
|
+
return {"ok": True}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
register_validator(_MODULE_NAME, _kmc_validator)
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _validate(gamma, a, total_time, output_times) -> None:
|
|
55
|
+
result = validate_parameters(
|
|
56
|
+
_MODULE_NAME,
|
|
57
|
+
{"gamma": gamma, "a": a, "total_time": total_time, "output_times": output_times},
|
|
58
|
+
)
|
|
59
|
+
if not result["ok"]:
|
|
60
|
+
raise ValueError(f"{result['parameter']}: {result['constraint']}")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# @id CODE-AIMS-040
|
|
64
|
+
# @implements REQ-AIMS-040
|
|
65
|
+
# @design DES-AIMS-040
|
|
66
|
+
def run_kmc_single_realization(
|
|
67
|
+
gamma: float, a: float, total_time: float, seed: int
|
|
68
|
+
) -> list[tuple[float, np.ndarray]]:
|
|
69
|
+
"""Run one rejection-free (BKL) realization; never advances past total_time.
|
|
70
|
+
|
|
71
|
+
Returns the list of (event_time, unwrapped_position) pairs, starting
|
|
72
|
+
with the step-zero event (0.0, origin).
|
|
73
|
+
"""
|
|
74
|
+
rng = np.random.default_rng(seed)
|
|
75
|
+
total_rate = 4.0 * gamma
|
|
76
|
+
t = 0.0
|
|
77
|
+
pos = np.zeros(2)
|
|
78
|
+
events: list[tuple[float, np.ndarray]] = [(t, pos.copy())]
|
|
79
|
+
|
|
80
|
+
while True:
|
|
81
|
+
dt = rng.exponential(1.0 / total_rate)
|
|
82
|
+
if t + dt > total_time:
|
|
83
|
+
break
|
|
84
|
+
t = t + dt
|
|
85
|
+
direction = rng.integers(0, 4)
|
|
86
|
+
pos = pos + _HOP_DIRECTIONS[direction] * a
|
|
87
|
+
events.append((t, pos.copy()))
|
|
88
|
+
|
|
89
|
+
return events
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _displacement_at(events: list[tuple[float, np.ndarray]], query_time: float) -> np.ndarray:
|
|
93
|
+
"""Displacement after the last event at or before query_time."""
|
|
94
|
+
last_position = events[0][1]
|
|
95
|
+
for event_time, position in events:
|
|
96
|
+
if event_time > query_time:
|
|
97
|
+
break
|
|
98
|
+
last_position = position
|
|
99
|
+
return last_position
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# @id CODE-AIMS-907
|
|
103
|
+
# @implements REQ-AIMS-040 REQ-AIMS-003
|
|
104
|
+
# @design DES-AIMS-040
|
|
105
|
+
def run_kinetic_monte_carlo(
|
|
106
|
+
gamma: float, a: float, total_time: float, output_times: list[float], base_seed: int
|
|
107
|
+
) -> dict:
|
|
108
|
+
"""Run 200 independent BKL realizations and report displacements (REQ-AIMS-040).
|
|
109
|
+
|
|
110
|
+
Rejects any out-of-domain parameter (REQ-AIMS-003) before any hop event
|
|
111
|
+
is drawn.
|
|
112
|
+
"""
|
|
113
|
+
_validate(gamma, a, total_time, output_times)
|
|
114
|
+
|
|
115
|
+
realization_seeds = [base_seed + i for i in range(_NUM_REALIZATIONS)]
|
|
116
|
+
displacements = np.zeros((len(output_times), _NUM_REALIZATIONS, 2))
|
|
117
|
+
|
|
118
|
+
for realization_index, seed in enumerate(realization_seeds):
|
|
119
|
+
events = run_kmc_single_realization(gamma, a, total_time, seed)
|
|
120
|
+
for time_index, query_time in enumerate(output_times):
|
|
121
|
+
displacements[time_index, realization_index] = _displacement_at(events, query_time)
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
"displacements": displacements,
|
|
125
|
+
"times": list(output_times),
|
|
126
|
+
"realization_seeds": realization_seeds,
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# @id CODE-AIMS-908
|
|
131
|
+
# @implements REQ-AIMS-040 REQ-AIMS-004 REQ-AIMS-005
|
|
132
|
+
# @design DES-AIMS-040
|
|
133
|
+
def run_kinetic_monte_carlo_with_evidence(**kwargs) -> dict:
|
|
134
|
+
"""Run kinetic Monte Carlo and wrap the result as a reproducible RunRecord."""
|
|
135
|
+
result = run_kinetic_monte_carlo(**kwargs)
|
|
136
|
+
return record_run(
|
|
137
|
+
module_name=_MODULE_NAME,
|
|
138
|
+
unit_system="lattice-hop-reduced",
|
|
139
|
+
params={k: v for k, v in kwargs.items() if k != "output_times"},
|
|
140
|
+
arrays={
|
|
141
|
+
"displacements": result["displacements"],
|
|
142
|
+
"times": np.array(result["times"], dtype=np.float64),
|
|
143
|
+
},
|
|
144
|
+
seed=kwargs["base_seed"],
|
|
145
|
+
)
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
"""Molecular dynamics trajectory integration module (DES-AIMS-020 / REQ-AIMS-020)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import numpy as np
|
|
6
|
+
|
|
7
|
+
from ai_materials_scientist.evidence import record_run
|
|
8
|
+
from ai_materials_scientist.validation import (
|
|
9
|
+
check_finite_array,
|
|
10
|
+
check_positive_output_interval,
|
|
11
|
+
check_positive_step_count,
|
|
12
|
+
register_validator,
|
|
13
|
+
validate_parameters,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
_MODULE_NAME = "molecular-dynamics"
|
|
17
|
+
_MIN_SEPARATION_SIGMA = 0.8
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# @id CODE-AIMS-020
|
|
21
|
+
# @implements REQ-AIMS-020
|
|
22
|
+
# @design DES-AIMS-020
|
|
23
|
+
def compute_dt_bound(epsilon: float, sigma: float, mass: float) -> float:
|
|
24
|
+
"""Conservative explicit stability bound dt_bound = 0.005*sigma*sqrt(mass/epsilon)."""
|
|
25
|
+
return 0.005 * sigma * (mass / epsilon) ** 0.5
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _minimum_image_displacements(positions: np.ndarray, box_length: float) -> np.ndarray:
|
|
29
|
+
"""Pairwise displacement[i, j] = positions[i] - positions[j] under min-image."""
|
|
30
|
+
diff = positions[:, None, :] - positions[None, :, :]
|
|
31
|
+
return diff - box_length * np.round(diff / box_length)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _pair_distances(positions: np.ndarray, box_length: float) -> np.ndarray:
|
|
35
|
+
disp = _minimum_image_displacements(positions, box_length)
|
|
36
|
+
dist = np.sqrt(np.sum(disp**2, axis=-1))
|
|
37
|
+
np.fill_diagonal(dist, np.inf)
|
|
38
|
+
return dist
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _lj_potential(r: np.ndarray, epsilon: float, sigma: float) -> np.ndarray:
|
|
42
|
+
sr6 = (sigma / r) ** 6
|
|
43
|
+
return 4 * epsilon * (sr6**2 - sr6)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _lj_radial_force_magnitude(r: np.ndarray, epsilon: float, sigma: float) -> np.ndarray:
|
|
47
|
+
"""F(r) = -dU/dr; positive is repulsive."""
|
|
48
|
+
sr6 = (sigma / r) ** 6
|
|
49
|
+
return 24 * epsilon / r * (2 * sr6**2 - sr6)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _forces_and_potential(
|
|
53
|
+
positions: np.ndarray, box_length: float, cutoff: float, epsilon: float, sigma: float
|
|
54
|
+
) -> tuple[np.ndarray, float]:
|
|
55
|
+
"""Shifted-force Lennard-Jones forces (per particle) and total pair potential."""
|
|
56
|
+
n = positions.shape[0]
|
|
57
|
+
disp = _minimum_image_displacements(positions, box_length)
|
|
58
|
+
dist = np.sqrt(np.sum(disp**2, axis=-1))
|
|
59
|
+
np.fill_diagonal(dist, np.inf)
|
|
60
|
+
|
|
61
|
+
within_cutoff = dist < cutoff
|
|
62
|
+
f_cutoff = _lj_radial_force_magnitude(np.array(cutoff), epsilon, sigma)
|
|
63
|
+
u_cutoff = _lj_potential(np.array(cutoff), epsilon, sigma)
|
|
64
|
+
|
|
65
|
+
forces = np.zeros_like(positions)
|
|
66
|
+
total_potential = 0.0
|
|
67
|
+
for i in range(n):
|
|
68
|
+
for j in range(i + 1, n):
|
|
69
|
+
if not within_cutoff[i, j]:
|
|
70
|
+
continue
|
|
71
|
+
r = dist[i, j]
|
|
72
|
+
f_shifted = _lj_radial_force_magnitude(r, epsilon, sigma) - f_cutoff
|
|
73
|
+
u_shifted = _lj_potential(r, epsilon, sigma) - u_cutoff - (r - cutoff) * (-f_cutoff)
|
|
74
|
+
unit_vec = disp[i, j] / r
|
|
75
|
+
force_on_i = f_shifted * unit_vec
|
|
76
|
+
forces[i] += force_on_i
|
|
77
|
+
forces[j] -= force_on_i
|
|
78
|
+
total_potential += u_shifted
|
|
79
|
+
return forces, total_potential
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _md_validator(params: dict) -> dict:
|
|
83
|
+
"""DES-AIMS-002 registered validator for module_name='molecular-dynamics'."""
|
|
84
|
+
lj_params = params["lj_params"]
|
|
85
|
+
mass = lj_params["mass"]
|
|
86
|
+
epsilon = lj_params["epsilon"]
|
|
87
|
+
sigma = lj_params["sigma"]
|
|
88
|
+
box_length = params["box_length"]
|
|
89
|
+
cutoff = params["cutoff"]
|
|
90
|
+
dt = params["dt"]
|
|
91
|
+
|
|
92
|
+
if mass <= 0:
|
|
93
|
+
return {"ok": False, "parameter": "mass", "constraint": "mass > 0"}
|
|
94
|
+
if epsilon <= 0:
|
|
95
|
+
return {"ok": False, "parameter": "epsilon", "constraint": "epsilon > 0"}
|
|
96
|
+
if sigma <= 0:
|
|
97
|
+
return {"ok": False, "parameter": "sigma", "constraint": "sigma > 0"}
|
|
98
|
+
if box_length <= 0:
|
|
99
|
+
return {"ok": False, "parameter": "box_length", "constraint": "box_length > 0"}
|
|
100
|
+
if dt <= 0:
|
|
101
|
+
return {"ok": False, "parameter": "dt", "constraint": "dt > 0"}
|
|
102
|
+
if cutoff <= 0:
|
|
103
|
+
return {"ok": False, "parameter": "cutoff", "constraint": "cutoff > 0"}
|
|
104
|
+
|
|
105
|
+
positions_check = check_finite_array("positions0", params["positions0"])
|
|
106
|
+
if not positions_check["ok"]:
|
|
107
|
+
return positions_check
|
|
108
|
+
velocities_check = check_finite_array("velocities0", params["velocities0"])
|
|
109
|
+
if not velocities_check["ok"]:
|
|
110
|
+
return velocities_check
|
|
111
|
+
steps_check = check_positive_step_count(params["steps"])
|
|
112
|
+
if not steps_check["ok"]:
|
|
113
|
+
return steps_check
|
|
114
|
+
interval_check = check_positive_output_interval(params["output_every"])
|
|
115
|
+
if not interval_check["ok"]:
|
|
116
|
+
return interval_check
|
|
117
|
+
|
|
118
|
+
if cutoff > box_length / 2:
|
|
119
|
+
return {
|
|
120
|
+
"ok": False,
|
|
121
|
+
"parameter": "cutoff",
|
|
122
|
+
"constraint": "cutoff <= box_length / 2",
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
distances = _pair_distances(params["positions0"], box_length)
|
|
126
|
+
min_separation = _MIN_SEPARATION_SIGMA * sigma
|
|
127
|
+
if np.any(distances < min_separation):
|
|
128
|
+
return {
|
|
129
|
+
"ok": False,
|
|
130
|
+
"parameter": "positions0",
|
|
131
|
+
"constraint": f"every pairwise separation >= {min_separation} (0.8*sigma)",
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
dt_bound = compute_dt_bound(epsilon, sigma, mass)
|
|
135
|
+
if dt > dt_bound:
|
|
136
|
+
return {
|
|
137
|
+
"ok": False,
|
|
138
|
+
"parameter": "dt",
|
|
139
|
+
"constraint": f"dt <= dt_bound ({dt_bound})",
|
|
140
|
+
}
|
|
141
|
+
return {"ok": True}
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
register_validator(_MODULE_NAME, _md_validator)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def _validate(positions0, velocities0, lj_params, box_length, cutoff, dt, steps, output_every):
|
|
148
|
+
result = validate_parameters(
|
|
149
|
+
_MODULE_NAME,
|
|
150
|
+
{
|
|
151
|
+
"positions0": positions0,
|
|
152
|
+
"velocities0": velocities0,
|
|
153
|
+
"lj_params": lj_params,
|
|
154
|
+
"box_length": box_length,
|
|
155
|
+
"cutoff": cutoff,
|
|
156
|
+
"dt": dt,
|
|
157
|
+
"steps": steps,
|
|
158
|
+
"output_every": output_every,
|
|
159
|
+
},
|
|
160
|
+
)
|
|
161
|
+
if not result["ok"]:
|
|
162
|
+
raise ValueError(f"{result['parameter']}: {result['constraint']}")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# @id CODE-AIMS-909
|
|
166
|
+
# @implements REQ-AIMS-020 REQ-AIMS-003
|
|
167
|
+
# @design DES-AIMS-020
|
|
168
|
+
def run_molecular_dynamics(
|
|
169
|
+
positions0: np.ndarray,
|
|
170
|
+
velocities0: np.ndarray,
|
|
171
|
+
lj_params: dict,
|
|
172
|
+
box_length: float,
|
|
173
|
+
cutoff: float,
|
|
174
|
+
dt: float,
|
|
175
|
+
steps: int,
|
|
176
|
+
output_every: int,
|
|
177
|
+
) -> dict:
|
|
178
|
+
"""Integrate Newton's equations of motion with velocity-Verlet (REQ-AIMS-020).
|
|
179
|
+
|
|
180
|
+
Rejects any out-of-domain parameter (REQ-AIMS-003) before performing any
|
|
181
|
+
integration step or mutating ``positions0``/``velocities0``.
|
|
182
|
+
"""
|
|
183
|
+
_validate(positions0, velocities0, lj_params, box_length, cutoff, dt, steps, output_every)
|
|
184
|
+
|
|
185
|
+
epsilon = lj_params["epsilon"]
|
|
186
|
+
sigma = lj_params["sigma"]
|
|
187
|
+
mass = lj_params["mass"]
|
|
188
|
+
|
|
189
|
+
positions = positions0.astype(np.float64).copy()
|
|
190
|
+
velocities = velocities0.astype(np.float64).copy()
|
|
191
|
+
|
|
192
|
+
def _kinetic(v: np.ndarray) -> float:
|
|
193
|
+
return float(0.5 * mass * np.sum(v**2))
|
|
194
|
+
|
|
195
|
+
forces, potential = _forces_and_potential(positions, box_length, cutoff, epsilon, sigma)
|
|
196
|
+
accel = forces / mass
|
|
197
|
+
|
|
198
|
+
snapshots = [{"positions": positions.copy(), "velocities": velocities.copy()}]
|
|
199
|
+
energies = [_kinetic(velocities) + potential]
|
|
200
|
+
times = [0.0]
|
|
201
|
+
|
|
202
|
+
for step in range(1, steps + 1):
|
|
203
|
+
velocities_half = velocities + 0.5 * dt * accel
|
|
204
|
+
positions = positions + dt * velocities_half
|
|
205
|
+
forces, potential = _forces_and_potential(positions, box_length, cutoff, epsilon, sigma)
|
|
206
|
+
accel = forces / mass
|
|
207
|
+
velocities = velocities_half + 0.5 * dt * accel
|
|
208
|
+
|
|
209
|
+
if step % output_every == 0:
|
|
210
|
+
snapshots.append({"positions": positions.copy(), "velocities": velocities.copy()})
|
|
211
|
+
energies.append(_kinetic(velocities) + potential)
|
|
212
|
+
times.append(step * dt)
|
|
213
|
+
|
|
214
|
+
dt_bound = compute_dt_bound(epsilon, sigma, mass)
|
|
215
|
+
return {
|
|
216
|
+
"snapshots": snapshots,
|
|
217
|
+
"energies": energies,
|
|
218
|
+
"times": times,
|
|
219
|
+
"dt_bound": dt_bound,
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
# @id CODE-AIMS-910
|
|
224
|
+
# @implements REQ-AIMS-020 REQ-AIMS-004 REQ-AIMS-005
|
|
225
|
+
# @design DES-AIMS-020
|
|
226
|
+
def run_molecular_dynamics_with_evidence(**kwargs) -> dict:
|
|
227
|
+
"""Run molecular dynamics and wrap the result as a reproducible RunRecord."""
|
|
228
|
+
result = run_molecular_dynamics(**kwargs)
|
|
229
|
+
return record_run(
|
|
230
|
+
module_name=_MODULE_NAME,
|
|
231
|
+
unit_system="lennard-jones-reduced",
|
|
232
|
+
params={k: v for k, v in kwargs.items() if k not in ("positions0", "velocities0")},
|
|
233
|
+
arrays={
|
|
234
|
+
"positions": np.stack([s["positions"] for s in result["snapshots"]]),
|
|
235
|
+
"velocities": np.stack([s["velocities"] for s in result["snapshots"]]),
|
|
236
|
+
"energies": np.array(result["energies"], dtype=np.float64),
|
|
237
|
+
"times": np.array(result["times"], dtype=np.float64),
|
|
238
|
+
},
|
|
239
|
+
seed=None,
|
|
240
|
+
)
|