From 0cc43f999a18c8a1d720443a3b04c7b2cf139f4c Mon Sep 17 00:00:00 2001 From: Daniel Weindl Date: Mon, 18 Dec 2023 18:52:41 +0100 Subject: [PATCH] code-gen: Simplify `switch` (#2240) Improves model code generation by collapsing cases with identical statements. I.e. ``` switch(a) case b: case c: statements; break; ``` instead of ``` switch(a) case b: statements; break; case c: statements; break; ``` For my current model of interest, containing many events, this significantly reduces the generated code: E.g.: ``` 16K my_model/deltasx.cpp 6,6M my_model_old/deltasx.cpp ``` Overall, for this model, I got from 204201 LOC down to 7936 LOC (i.e. -96%). * .. * Apply suggestions from code review Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com> * GHA: test python3.12 (#2179) Run nightly tests also on python3.12 * Deterministic order of event assignments (#2242) Ensure event assignments targets are processed in deterministic order. Otherwise the ordering of state variables may change between subsequent model imports, which we'd like to avoid. Closes #2241. * Fix AMICI hiding all warnings (#2243) Previously, importing amici would result in all warnings of the program being hidden, due to `logging.captureWarnings(True)`: ```sh $ python -c "import warnings; warnings.warn('bla');" :1: UserWarning: bla $ python -c "import amici; import warnings; warnings.warn('bla');" $ ``` This can't be the desired default. Changes: * Default to not capturing warnings * If warnings are to be captured, at least handle them by amici loggers Closes ICB-DCM/pyPESTO#1252 --------- Co-authored-by: Dilan Pathirana <59329744+dilpath@users.noreply.github.com> --- python/sdist/amici/cxxcodeprinter.py | 39 +++++++++++++++++++--------- 1 file changed, 27 insertions(+), 12 deletions(-) diff --git a/python/sdist/amici/cxxcodeprinter.py b/python/sdist/amici/cxxcodeprinter.py index e6e377b331..a6ff17e835 100644 --- a/python/sdist/amici/cxxcodeprinter.py +++ b/python/sdist/amici/cxxcodeprinter.py @@ -303,7 +303,9 @@ def get_switch_statement( indentation_step: Optional[str] = " " * 4, ): """ - Generate code for switch statement + Generate code for a C++ switch statement. + + Generate code for a C++ switch statement with a ``break`` after each case. :param condition: Condition for switch @@ -321,26 +323,39 @@ def get_switch_statement( :return: Code for switch expression as list of strings """ - lines = [] - if not cases: - return lines + return [] indent0 = indentation_level * indentation_step indent1 = (indentation_level + 1) * indentation_step indent2 = (indentation_level + 2) * indentation_step + + # try to find redundant statements and collapse those cases + # map statements to case expressions + cases_map: dict[tuple[str, ...], list[str]] = {} for expression, statements in cases.items(): if statements: - lines.extend( + statement_code = tuple( [ - f"{indent1}case {expression}:", *(f"{indent2}{statement}" for statement in statements), f"{indent2}break;", ] ) - - if lines: - lines.insert(0, f"{indent0}switch({condition}) {{") - lines.append(indent0 + "}") - - return lines + case_code = f"{indent1}case {expression}:" + + cases_map[statement_code] = cases_map.get(statement_code, []) + [ + case_code + ] + + if not cases_map: + return [] + + return [ + f"{indent0}switch({condition}) {{", + *( + code + for codes in cases_map.items() + for code in itertools.chain.from_iterable(reversed(codes)) + ), + indent0 + "}", + ]