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.
Files changed (193) hide show
  1. package/.github/skills/ai-chemistry-scientist/SKILL.md +97 -0
  2. package/.github/skills/ai-chemistry-scientist/manifest.json +156 -0
  3. package/.github/skills/ai-data-scientist/SKILL.md +330 -0
  4. package/.github/skills/ai-genomics-scientist/SKILL.md +98 -0
  5. package/.github/skills/ai-genomics-scientist/manifest.json +93 -0
  6. package/.github/skills/ai-materials-scientist/SKILL.md +51 -0
  7. package/.github/skills/ai-materials-scientist/manifest.json +58 -0
  8. package/.github/skills/ai-scientist/SKILL.md +69 -0
  9. package/.github/skills/ai-scientist/manifest.json +61 -0
  10. package/.github/skills/ai-structural-biology-scientist/SKILL.md +67 -0
  11. package/.github/skills/ai-structural-biology-scientist/manifest.json +72 -0
  12. package/.github/skills/japanese-prose/NOTICE.md +17 -0
  13. package/.github/skills/japanese-prose/SKILL.md +111 -0
  14. package/.github/skills/japanese-prose/references/review-workflow.md +50 -0
  15. package/.github/skills/japanese-prose/references/scoring.md +24 -0
  16. package/.github/skills/japanese-prose/references/writing-guidelines.md +60 -0
  17. package/.github/skills/japanese-prose/scripts/core.py +192 -0
  18. package/.github/skills/japanese-prose/scripts/fixtures/natural.md +5 -0
  19. package/.github/skills/japanese-prose/scripts/fixtures/unnatural.md +5 -0
  20. package/.github/skills/japanese-prose/scripts/lint.py +378 -0
  21. package/.github/skills/japanese-prose/scripts/outline.py +68 -0
  22. package/.github/skills/japanese-prose/scripts/terms.py +112 -0
  23. package/.github/skills/japanese-prose/scripts/test_engine.py +117 -0
  24. package/.github/skills/presentation-planner/SKILL.md +257 -0
  25. package/.github/skills/presentation-planner/assets/design-templates/data-report.yaml +97 -0
  26. package/.github/skills/presentation-planner/assets/design-templates/executive-proposal.yaml +92 -0
  27. package/.github/skills/presentation-planner/assets/design-templates/technical-briefing.yaml +96 -0
  28. package/.github/skills/presentation-planner/assets/scenario-templates/data-report.md +47 -0
  29. package/.github/skills/presentation-planner/assets/scenario-templates/executive-decision.md +43 -0
  30. package/.github/skills/presentation-planner/assets/scenario-templates/technical-briefing.md +45 -0
  31. package/.github/skills/presentation-planner/references/customizing-design-templates.md +160 -0
  32. package/.github/skills/presentation-planner/references/design-spec-schema.md +72 -0
  33. package/.github/skills/presentation-planner/references/handoff-contract.md +49 -0
  34. package/.github/skills/presentation-planner/references/responsibility-boundary.md +32 -0
  35. package/.github/skills/presentation-planner/references/scenario-templates.md +55 -0
  36. package/.github/skills/tech-writer/SKILL.md +434 -0
  37. package/.github/skills/tech-writer/assets/templates/blueprint.md +187 -0
  38. package/.github/skills/tech-writer/assets/templates/design-doc.md +29 -0
  39. package/.github/skills/tech-writer/assets/templates/migration-plan.md +173 -0
  40. package/.github/skills/tech-writer/assets/templates/operations-runbook.md +202 -0
  41. package/.github/skills/tech-writer/assets/templates/pr-description.md +23 -0
  42. package/.github/skills/tech-writer/assets/templates/qiita.md +44 -0
  43. package/.github/skills/tech-writer/assets/templates/readme.md +38 -0
  44. package/.github/skills/tech-writer/assets/templates/requirements-definition.md +170 -0
  45. package/.github/skills/tech-writer/assets/templates/rfi.md +113 -0
  46. package/.github/skills/tech-writer/assets/templates/rfp.md +180 -0
  47. package/.github/skills/tech-writer/assets/templates/security-design.md +167 -0
  48. package/.github/skills/tech-writer/assets/templates/system-design.md +220 -0
  49. package/.github/skills/tech-writer/assets/templates/technical-proposal.md +112 -0
  50. package/.github/skills/tech-writer/assets/templates/test-plan.md +153 -0
  51. package/.github/skills/tech-writer/assets/templates/user-manual.md +22 -0
  52. package/.github/skills/tech-writer/assets/templates/white-paper.md +192 -0
  53. package/.github/skills/tech-writer/references/doctypes/api-docs.md +33 -0
  54. package/.github/skills/tech-writer/references/doctypes/blueprint.md +81 -0
  55. package/.github/skills/tech-writer/references/doctypes/code-comments.md +39 -0
  56. package/.github/skills/tech-writer/references/doctypes/design-doc.md +42 -0
  57. package/.github/skills/tech-writer/references/doctypes/migration-plan.md +63 -0
  58. package/.github/skills/tech-writer/references/doctypes/operations-runbook.md +63 -0
  59. package/.github/skills/tech-writer/references/doctypes/pr-commit.md +82 -0
  60. package/.github/skills/tech-writer/references/doctypes/qiita.md +75 -0
  61. package/.github/skills/tech-writer/references/doctypes/readme.md +43 -0
  62. package/.github/skills/tech-writer/references/doctypes/release-notes.md +30 -0
  63. package/.github/skills/tech-writer/references/doctypes/requirements-definition.md +61 -0
  64. package/.github/skills/tech-writer/references/doctypes/rfi.md +43 -0
  65. package/.github/skills/tech-writer/references/doctypes/rfp.md +46 -0
  66. package/.github/skills/tech-writer/references/doctypes/security-design.md +71 -0
  67. package/.github/skills/tech-writer/references/doctypes/system-design.md +74 -0
  68. package/.github/skills/tech-writer/references/doctypes/technical-proposal.md +49 -0
  69. package/.github/skills/tech-writer/references/doctypes/test-plan.md +67 -0
  70. package/.github/skills/tech-writer/references/doctypes/user-manual.md +58 -0
  71. package/.github/skills/tech-writer/references/doctypes/white-paper.md +84 -0
  72. package/.github/skills/tech-writer/references/doctypes/zenn.md +66 -0
  73. package/.github/skills/tech-writer/references/japanese-prose-optimization.md +110 -0
  74. package/.github/skills/tech-writer/references/style-constitution.md +104 -0
  75. package/.github/skills/tech-writer/scripts/lint.py +412 -0
  76. package/LICENSE +21 -0
  77. package/README.md +92 -0
  78. package/bin/ai-data-scientist.js +123 -0
  79. package/package.json +41 -0
  80. package/pyproject.toml +45 -0
  81. package/src/ai_chemistry_scientist/__init__.py +0 -0
  82. package/src/ai_chemistry_scientist/admet_prediction.py +71 -0
  83. package/src/ai_chemistry_scientist/bioactivity_classification.py +73 -0
  84. package/src/ai_chemistry_scientist/data/sample_molecules.csv +21 -0
  85. package/src/ai_chemistry_scientist/dispatch.py +369 -0
  86. package/src/ai_chemistry_scientist/docking_score.py +97 -0
  87. package/src/ai_chemistry_scientist/drug_likeness_rules.py +84 -0
  88. package/src/ai_chemistry_scientist/evidence.py +41 -0
  89. package/src/ai_chemistry_scientist/molecular_descriptors.py +97 -0
  90. package/src/ai_chemistry_scientist/molecular_formula_mass.py +40 -0
  91. package/src/ai_chemistry_scientist/molecular_similarity.py +78 -0
  92. package/src/ai_chemistry_scientist/qsar_modeling.py +105 -0
  93. package/src/ai_chemistry_scientist/salt_standardization.py +81 -0
  94. package/src/ai_chemistry_scientist/structural_alerts.py +76 -0
  95. package/src/ai_chemistry_scientist/structure_format_conversion.py +84 -0
  96. package/src/ai_chemistry_scientist/validation.py +70 -0
  97. package/src/ai_data_scientist/__init__.py +0 -0
  98. package/src/ai_data_scientist/analysis_assumptions.py +121 -0
  99. package/src/ai_data_scientist/anomaly_detection.py +39 -0
  100. package/src/ai_data_scientist/automl.py +109 -0
  101. package/src/ai_data_scientist/cleaning.py +56 -0
  102. package/src/ai_data_scientist/cli.py +90 -0
  103. package/src/ai_data_scientist/clustering.py +54 -0
  104. package/src/ai_data_scientist/dashboard.py +33 -0
  105. package/src/ai_data_scientist/data_definition.py +100 -0
  106. package/src/ai_data_scientist/data_quality.py +164 -0
  107. package/src/ai_data_scientist/dataset_validation.py +135 -0
  108. package/src/ai_data_scientist/dependency_pins.py +60 -0
  109. package/src/ai_data_scientist/eda.py +82 -0
  110. package/src/ai_data_scientist/experiment_evaluation.py +635 -0
  111. package/src/ai_data_scientist/explainability.py +340 -0
  112. package/src/ai_data_scientist/feature_engineering.py +163 -0
  113. package/src/ai_data_scientist/gate_config.py +32 -0
  114. package/src/ai_data_scientist/ingestion.py +127 -0
  115. package/src/ai_data_scientist/insight_engine.py +180 -0
  116. package/src/ai_data_scientist/japanese_nlp.py +43 -0
  117. package/src/ai_data_scientist/jupyter_launcher.py +137 -0
  118. package/src/ai_data_scientist/jupyter_mcp_client.py +94 -0
  119. package/src/ai_data_scientist/language_router.py +28 -0
  120. package/src/ai_data_scientist/lifecycle.py +221 -0
  121. package/src/ai_data_scientist/mcp_gateway.py +113 -0
  122. package/src/ai_data_scientist/mcp_runtime.py +194 -0
  123. package/src/ai_data_scientist/mcp_transport.py +53 -0
  124. package/src/ai_data_scientist/ml_modeling.py +451 -0
  125. package/src/ai_data_scientist/model_tuning.py +104 -0
  126. package/src/ai_data_scientist/notebook_audit.py +574 -0
  127. package/src/ai_data_scientist/project_manager.py +243 -0
  128. package/src/ai_data_scientist/report_export.py +73 -0
  129. package/src/ai_data_scientist/sensitivity.py +445 -0
  130. package/src/ai_data_scientist/signal_analysis.py +201 -0
  131. package/src/ai_data_scientist/skill_packaging.py +40 -0
  132. package/src/ai_data_scientist/stats_analysis.py +88 -0
  133. package/src/ai_data_scientist/text_nlp.py +44 -0
  134. package/src/ai_data_scientist/timeseries.py +68 -0
  135. package/src/ai_data_scientist/visualization.py +708 -0
  136. package/src/ai_genomics_scientist/__init__.py +1 -0
  137. package/src/ai_genomics_scientist/differential_expression.py +147 -0
  138. package/src/ai_genomics_scientist/dispatch.py +267 -0
  139. package/src/ai_genomics_scientist/evidence.py +45 -0
  140. package/src/ai_genomics_scientist/gene_set_enrichment.py +76 -0
  141. package/src/ai_genomics_scientist/sequence_alignment.py +97 -0
  142. package/src/ai_genomics_scientist/sequence_features.py +111 -0
  143. package/src/ai_genomics_scientist/splice_site_scoring.py +66 -0
  144. package/src/ai_genomics_scientist/validation.py +83 -0
  145. package/src/ai_genomics_scientist/variant_effect.py +147 -0
  146. package/src/ai_genomics_scientist/variant_pathogenicity.py +125 -0
  147. package/src/ai_materials_scientist/__init__.py +0 -0
  148. package/src/ai_materials_scientist/calphad.py +117 -0
  149. package/src/ai_materials_scientist/classical_monte_carlo.py +165 -0
  150. package/src/ai_materials_scientist/crystal_plasticity.py +184 -0
  151. package/src/ai_materials_scientist/dispatch.py +100 -0
  152. package/src/ai_materials_scientist/evidence.py +84 -0
  153. package/src/ai_materials_scientist/fem.py +279 -0
  154. package/src/ai_materials_scientist/kinetic_monte_carlo.py +145 -0
  155. package/src/ai_materials_scientist/molecular_dynamics.py +240 -0
  156. package/src/ai_materials_scientist/phase_field.py +167 -0
  157. package/src/ai_materials_scientist/validation.py +70 -0
  158. package/src/ai_scientist/__init__.py +1 -0
  159. package/src/ai_scientist/completion_gate.py +15 -0
  160. package/src/ai_scientist/data_analysis.py +46 -0
  161. package/src/ai_scientist/evidence_registry.py +99 -0
  162. package/src/ai_scientist/experimental_design.py +20 -0
  163. package/src/ai_scientist/language.py +14 -0
  164. package/src/ai_scientist/latex_renderer.py +41 -0
  165. package/src/ai_scientist/literature_review.py +37 -0
  166. package/src/ai_scientist/manifest.py +87 -0
  167. package/src/ai_scientist/manuscript.py +94 -0
  168. package/src/ai_scientist/mcp_config.py +76 -0
  169. package/src/ai_scientist/mcp_external.py +42 -0
  170. package/src/ai_scientist/mcp_failures.py +23 -0
  171. package/src/ai_scientist/mcp_gateway.py +38 -0
  172. package/src/ai_scientist/mcp_managed.py +180 -0
  173. package/src/ai_scientist/npm_packaging.py +49 -0
  174. package/src/ai_scientist/orchestrator.py +133 -0
  175. package/src/ai_scientist/peer_review.py +60 -0
  176. package/src/ai_scientist/phase_gate.py +74 -0
  177. package/src/ai_scientist/phase_state.py +230 -0
  178. package/src/ai_scientist/presentation.py +56 -0
  179. package/src/ai_scientist/project_config.py +31 -0
  180. package/src/ai_scientist/project_handle.py +74 -0
  181. package/src/ai_scientist/reproducibility.py +20 -0
  182. package/src/ai_scientist/research_planning.py +20 -0
  183. package/src/ai_scientist/skill_invocation.py +21 -0
  184. package/src/ai_scientist/tdd_gate.py +99 -0
  185. package/src/ai_structural_biology_scientist/__init__.py +0 -0
  186. package/src/ai_structural_biology_scientist/contact_map.py +87 -0
  187. package/src/ai_structural_biology_scientist/dispatch.py +269 -0
  188. package/src/ai_structural_biology_scientist/evidence.py +43 -0
  189. package/src/ai_structural_biology_scientist/hydrophobicity.py +101 -0
  190. package/src/ai_structural_biology_scientist/protein_docking_score.py +104 -0
  191. package/src/ai_structural_biology_scientist/secondary_structure.py +95 -0
  192. package/src/ai_structural_biology_scientist/structural_similarity.py +74 -0
  193. 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
+ )