SimiLie
Loading...
Searching...
No Matches
generate_cpp_constitutive_law.py
1#!/usr/bin/env python3
2# SPDX-FileCopyrightText: 2026 Baptiste Legouix
3# SPDX-License-Identifier: AGPL-3.0-or-later
4
5from __future__ import annotations
6
7from dataclasses import dataclass
8from pathlib import Path
9
10from sympy import diff, solve, symbols
11from sympy.printing.codeprinter import cxxcode
12
13
14@dataclass(frozen=True)
16 namespace: str
17 class_name: str
18 parameters: list[str]
19 variables: list[str]
20 output_variable: str
21 constitutive_law: object
22
23
24def _replace_symbols(expression: str, replacements: dict[str, str]) -> str:
25 for source, target in replacements.items():
26 expression = expression.replace(source, target)
27 return expression
28
29
31 expression, symbol, rendered_symbol: str, replacements: dict[str, str]
32) -> str:
33 return _replace_symbols(
34 cxxcode(expression.subs(symbol, symbols(rendered_symbol))), replacements
35 )
36
37
39 output_path: Path,
40 namespace: str,
41 class_name: str,
42 parameters: list[tuple[str, str, bool]],
43 variables: list[str],
44 output_variable: str,
45 constitutive_law,
46) -> None:
47 output_path.parent.mkdir(parents=True, exist_ok=True)
48
49 parameter_replacements = {
50 constructor_name: member_name for member_name, constructor_name, _ in parameters
51 }
52 constitutive_law_symbols = {
53 str(symbol): symbol
54 for symbol in constitutive_law.free_symbols
55 if str(symbol) not in parameter_replacements
56 }
57 if not variables:
58 raise ValueError("Constitutive law must define at least one variable")
59 if any(name not in constitutive_law_symbols for name in variables):
60 raise ValueError("Constitutive law variables must match symbolic variables")
61
62 state_variable_name = variables[-1]
63 state_variable_symbol = constitutive_law_symbols[state_variable_name]
64 constitutive_law_replacements = dict(parameter_replacements)
65 output_symbol = symbols(output_variable)
66 inverse_solution = solve(
67 output_symbol - constitutive_law, state_variable_symbol, dict=True
68 )
69 if not inverse_solution:
70 raise ValueError("Unable to invert constitutive law")
71 inverse_expression = inverse_solution[0][state_variable_symbol]
72 forward_value_expression = diff(constitutive_law, state_variable_symbol)
73 inverse_value_expression = diff(inverse_expression, output_symbol)
74 inverse_replacements = dict(parameter_replacements)
75
76 parameter_members = "\n".join(
77 f" {'const ' if is_const else ''}double {member_name};"
78 for member_name, _, is_const in parameters
79 )
80 constructor_signature = ",\n ".join(
81 f"double {constructor_name}_" for _, constructor_name, _ in parameters
82 )
83 constructor_initializers = ", ".join(
84 f"{member_name}({constructor_name}_)"
85 for member_name, constructor_name, _ in parameters
86 )
87 forward_arguments = ", ".join(f"double {name}" for name in variables)
88 inverse_output_name = state_variable_name
89 inverse_arguments = ", ".join(
90 [
91 *[f"double {name}" for name in variables if name != inverse_output_name],
92 f"double {output_variable}",
93 ]
94 )
95
96 output_path.write_text(
97 f"""\
98// SPDX-FileCopyrightText: 2026 Baptiste Legouix
99// SPDX-License-Identifier: AGPL-3.0-or-later
100
101#pragma once
102
103#include <Kokkos_Core.hpp>
104
105namespace {namespace} {{
106
107class {class_name}
108{{
109{parameter_members}
110
111public:
112 constexpr explicit {class_name}(
113 {constructor_signature})
114 : {constructor_initializers}
115 {{
116 }}
117
118 KOKKOS_FUNCTION constexpr double value({forward_arguments}) const
119 {{
120 return {_render_expression(forward_value_expression, state_variable_symbol, inverse_output_name, constitutive_law_replacements)};
121 }}
122
123 KOKKOS_FUNCTION constexpr double jacobian({forward_arguments}) const
124 {{
125 return value({", ".join(variables)});
126 }}
127
128 KOKKOS_FUNCTION constexpr double operator()({forward_arguments}) const
129 {{
130 return {_render_expression(constitutive_law, state_variable_symbol, inverse_output_name, constitutive_law_replacements)};
131 }}
132
133 KOKKOS_FUNCTION constexpr double inverse_value({inverse_arguments}) const
134 {{
135 return {_render_expression(inverse_value_expression, output_symbol, output_variable, inverse_replacements)};
136 }}
137
138 KOKKOS_FUNCTION constexpr double inverse({inverse_arguments}) const
139 {{
140 return {_render_expression(inverse_expression, output_symbol, output_variable, inverse_replacements)};
141 }}
142}};
143
144}} // namespace {namespace}
145"""
146 )
147
148
150 functor_class, output_path: Path, *args, **kwargs
151) -> None:
152 definition = functor_class.__call__(*args, **kwargs)
153 parameter_tuples = [(f"m_{name}", name, True) for name in definition.parameters]
155 output_path=output_path,
156 namespace=definition.namespace,
157 class_name=definition.class_name,
158 parameters=parameter_tuples,
159 variables=definition.variables,
160 output_variable=definition.output_variable,
161 constitutive_law=definition.constitutive_law,
162 )
str _replace_symbols(str expression, dict[str, str] replacements)
None write_cpp_constitutive_law_header(Path output_path, str namespace, str class_name, list[tuple[str, str, bool]] parameters, list[str] variables, str output_variable, constitutive_law)
str _render_expression(expression, symbol, str rendered_symbol, dict[str, str] replacements)