File indexing completed on 2026-09-18 08:21:41
0001 import argparse
0002 import contextlib
0003 import sys
0004
0005 import sympy as sym
0006 from sympy import MatrixSymbol
0007
0008 from codegen.sympy_common import (
0009 NamedExpr,
0010 name_expr,
0011 find_by_name,
0012 cxx_printer,
0013 my_expression_print,
0014 )
0015
0016 step_path_derivatives = (
0017 MatrixSymbol("step_path_derivatives", 8, 1).as_explicit().as_mutable()
0018 )
0019 step_path_derivatives[7, 0] = 0
0020
0021 surface_path_derivatives = (
0022 MatrixSymbol("surface_path_derivatives", 1, 8).as_explicit().as_mutable()
0023 )
0024 surface_path_derivatives[0, 3] = 0
0025 surface_path_derivatives[0, 7] = 0
0026
0027
0028
0029
0030 J_bf = MatrixSymbol("M", 8, 6).as_explicit().as_mutable()
0031 tmp = sym.zeros(8, 6)
0032 tmp[0:3, 0:2] = J_bf[0:3, 0:2]
0033 tmp[0:3, 2:5] = J_bf[0:3, 2:5]
0034 tmp[3, 4] = J_bf[3, 4]
0035 tmp[4:7, 2:5] = J_bf[4:7, 2:5]
0036 tmp[7, 4] = J_bf[7, 4]
0037 tmp[3, 5] = 1
0038 J_bf = tmp
0039
0040 J_fb = MatrixSymbol("J_fb", 6, 8).as_explicit().as_mutable()
0041 tmp = sym.zeros(6, 8)
0042 tmp[0:2, 0:3] = J_fb[0:2, 0:3]
0043 tmp[2:4, 4:7] = J_fb[2:4, 4:7]
0044 tmp[5, 3] = 1
0045 tmp[4, 7] = 1
0046 J_fb = tmp
0047
0048
0049 def full_transport_jacobian_generic() -> list[NamedExpr]:
0050 J_full = name_expr(
0051 "J_full",
0052 J_fb * (sym.eye(8) + step_path_derivatives * surface_path_derivatives) * J_bf,
0053 )
0054
0055 return [J_full]
0056
0057
0058 def full_transport_jacobian_curvilinear(direction: MatrixSymbol) -> list[NamedExpr]:
0059 surface_path_derivatives = (
0060 MatrixSymbol("surface_path_derivatives", 1, 8).as_explicit().as_mutable()
0061 )
0062 surface_path_derivatives[0, 0:3] = -direction.as_explicit().transpose()
0063 surface_path_derivatives[0, 3:8] = sym.zeros(1, 5)
0064
0065 J_full = name_expr(
0066 "J_full",
0067 J_fb * (sym.eye(8) + step_path_derivatives * surface_path_derivatives) * J_bf,
0068 )
0069
0070 return [J_full]
0071
0072
0073 def my_full_transport_jacobian_generic_function_print(name_exprs, run_cse=True):
0074 printer = cxx_printer
0075 outputs = [find_by_name(name_exprs, name)[0] for name in ["J_full"]]
0076
0077 lines = []
0078
0079 head = (
0080 "template <typename T> void boundToBoundTransportJacobianImpl("
0081 "std::span<const T, 48> J_fb, std::span<const T, 48> M,"
0082 " std::span<const T, 8> step_path_derivatives,"
0083 " std::span<const T, 8> surface_path_derivatives,"
0084 " std::span<T, 36> J_full) {"
0085 )
0086 lines.append(head)
0087
0088 code = my_expression_print(
0089 printer,
0090 name_exprs,
0091 outputs,
0092 run_cse=run_cse,
0093 )
0094 lines.extend([f" {l}" for l in code.split("\n")])
0095
0096 lines.append("}")
0097
0098 return "\n".join(lines)
0099
0100
0101 def my_full_transport_jacobian_curvilinear_function_print(name_exprs, run_cse=True):
0102 printer = cxx_printer
0103 outputs = [find_by_name(name_exprs, name)[0] for name in ["J_full"]]
0104
0105 lines = []
0106
0107 head = (
0108 "template <typename T> void boundToCurvilinearTransportJacobianImpl("
0109 "std::span<const T, 48> J_fb, std::span<const T, 48> M,"
0110 " std::span<const T, 8> step_path_derivatives,"
0111 " std::span<const T, 3> dir, std::span<T, 36> J_full) {"
0112 )
0113 lines.append(head)
0114
0115 code = my_expression_print(
0116 printer,
0117 name_exprs,
0118 outputs,
0119 run_cse=run_cse,
0120 )
0121 lines.extend([f" {l}" for l in code.split("\n")])
0122
0123 lines.append("}")
0124
0125 return "\n".join(lines)
0126
0127
0128 def check_curvilinear_is_generic_specialised() -> None:
0129 """Assert the curvilinear jacobian is the generic one at a curvilinear surface.
0130
0131 They are printed as two functions, so nothing else keeps them in step. A
0132 curvilinear surface has path derivatives -direction over the position part
0133 and zero elsewhere.
0134 """
0135 direction = MatrixSymbol("dir", 3, 1)
0136 generic = full_transport_jacobian_generic()[0].expr
0137 curvilinear = full_transport_jacobian_curvilinear(direction)[0].expr
0138
0139 spd = MatrixSymbol("surface_path_derivatives", 1, 8).as_explicit()
0140 at_curvilinear = {spd[0, i]: -direction.as_explicit()[i, 0] for i in range(3)}
0141 at_curvilinear.update({spd[0, i]: 0 for i in range(3, 8)})
0142
0143 diff = sym.expand(generic.subs(at_curvilinear) - curvilinear)
0144 if any(e != 0 for e in diff):
0145 bad = [
0146 (i, j)
0147 for i in range(diff.rows)
0148 for j in range(diff.cols)
0149 if diff[i, j] != 0
0150 ]
0151 raise AssertionError(
0152 f"curvilinear jacobian is not the generic one specialised, at {bad}"
0153 )
0154
0155
0156 def check_covariance_transport_sparsity() -> None:
0157 """Assert the shape the covariance transport masks this jacobian down to.
0158
0159 Live entries outside it would be dropped there silently, so check here.
0160
0161 Raises AssertionError if the jacobian reaches outside that shape.
0162 """
0163 J = sym.expand(full_transport_jacobian_generic()[0].expr)
0164 qop, time = 4, 5
0165
0166
0167 leaks = [(qop, j) for j in range(6) if j != qop and J[qop, j] != 0]
0168 leaks += [(i, time) for i in range(6) if J[i, time] != (1 if i == time else 0)]
0169 if leaks:
0170 raise AssertionError(
0171 "bound-to-bound jacobian is not of the shape the covariance "
0172 f"transport assumes; live entries outside it: {leaks}"
0173 )
0174
0175
0176 HEADER = """// This file is part of the ACTS project.
0177 //
0178 // Copyright (C) 2016 CERN for the benefit of the ACTS project
0179 //
0180 // This Source Code Form is subject to the terms of the Mozilla Public
0181 // License, v. 2.0. If a copy of the MPL was not distributed with this
0182 // file, You can obtain one at https://mozilla.org/MPL/2.0/.
0183
0184 // Note: This file is generated by generate_sympy_jac.py
0185 // Do not modify it manually.
0186
0187 #pragma once
0188
0189 #include <cmath>
0190 #include <span>
0191 """
0192
0193
0194 def main(argv: list[str]) -> None:
0195 """Generate the transport jacobians.
0196
0197 @param argv is the command line, argv[0] being the program name
0198 """
0199 parser = argparse.ArgumentParser(description=__doc__)
0200 parser.add_argument(
0201 "output",
0202 nargs="?",
0203 help="file to write the generated jacobians to; stdout if omitted",
0204 )
0205 parser.add_argument(
0206 "--no-check",
0207 action="store_true",
0208 help="skip the symbolic assertions",
0209 )
0210 args = parser.parse_args(argv[1:])
0211
0212 if not args.no_check:
0213
0214
0215 check_curvilinear_is_generic_specialised()
0216 check_covariance_transport_sparsity()
0217
0218 with (
0219 open(args.output, "w") if args.output else contextlib.nullcontext(sys.stdout)
0220 ) as out:
0221 out.write(HEADER)
0222 out.write(
0223 my_full_transport_jacobian_generic_function_print(
0224 full_transport_jacobian_generic(), run_cse=True
0225 )
0226 )
0227 out.write("\n")
0228 out.write(
0229 my_full_transport_jacobian_curvilinear_function_print(
0230 full_transport_jacobian_curvilinear(MatrixSymbol("dir", 3, 1)),
0231 run_cse=True,
0232 )
0233 )
0234 out.write("\n")
0235
0236
0237 if __name__ == "__main__":
0238 main(sys.argv)