diff --git a/.basedpyright/baseline.json b/.basedpyright/baseline.json new file mode 100644 index 000000000..0666c32fe --- /dev/null +++ b/.basedpyright/baseline.json @@ -0,0 +1,26358 @@ +{ + "files": { + "./examples/curve-pot.py": [ + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 4, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 4, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportEmptyAbstractUsage", + "range": { + "startColumn": 22, + "endColumn": 50, + "lineCount": 1 + } + } + ], + "./examples/expansion-toys.py": [ + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 4, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnusedVariable", + "range": { + "startColumn": 4, + "endColumn": 9, + "lineCount": 1 + } + } + ], + "./examples/fourier.py": [ + { + "code": "reportReturnType", + "range": { + "startColumn": 11, + "endColumn": 17, + "lineCount": 1 + } + } + ], + "./examples/sym-exp-complexity.py": [ + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 4, + "endColumn": 18, + "lineCount": 1 + } + } + ], + "./sumpy/__init__.py": [ + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 0, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 29, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 29, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 4, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 24, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 24, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 30, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 30, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 4, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 4, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 33, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 33, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUninitializedInstanceVariable", + "range": { + "startColumn": 13, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUninitializedInstanceVariable", + "range": { + "startColumn": 13, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 43, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 33, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 33, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 42, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 42, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 48, + "endColumn": 69, + "lineCount": 1 + } + } + ], + "./sumpy/array_context.py": [ + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnusedFunction", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 13, + "endColumn": 29, + "lineCount": 1 + } + } + ], + "./sumpy/codegen.py": [ + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 52, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 57, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 35, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 35, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 32, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 37, + "lineCount": 1 + } + } + ], + "./sumpy/cse.py": [ + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 29, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 58, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 38, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 38, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 68, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 38, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 38, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 46, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 32, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 32, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 48, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 17, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 36, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 36, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 44, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 44, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 33, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 33, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 41, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 41, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 22, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 22, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 41, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 41, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 37, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 44, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 67, + "lineCount": 3 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 27, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 30, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 36, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 81, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 72, + "endColumn": 80, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 70, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 46, + "endColumn": 72, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 45, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 82, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 52, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 52, + "endColumn": 81, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportGeneralTypeIssues", + "range": { + "startColumn": 24, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 26, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 40, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportGeneralTypeIssues", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportGeneralTypeIssues", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 26, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportGeneralTypeIssues", + "range": { + "startColumn": 48, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportGeneralTypeIssues", + "range": { + "startColumn": 48, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 37, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 39, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 13, + "endColumn": 38, + "lineCount": 1 + } + } + ], + "./sumpy/derivative_taker.py": [ + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 11, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 19, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 60, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 63, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 43, + "lineCount": 1 + } + } + ], + "./sumpy/distributed.py": [ + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 30, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 30, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 47, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 47, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 19, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 19, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 39, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 72, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 32, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 32, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 49, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 49, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 26, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 37, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 32, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 32, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 44, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 44, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 26, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 17, + "lineCount": 4 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 16, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 32, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 30, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 30, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 33, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 33, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 16, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 24, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 24, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 32, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 32, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 44, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 42, + "lineCount": 1 + } + } + ], + "./sumpy/e2e.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 26, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 20, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 66, + "lineCount": 3 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 39, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 46, + "lineCount": 3 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 39, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 16, + "endColumn": 62, + "lineCount": 19 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 20, + "lineCount": 28 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 42, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 42, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 37, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 37, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 38, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 45, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 49, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 18, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 45, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 34, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 50, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 45, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 56, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 49, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 46, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 46, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 45, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 45, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 27, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 64, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 46, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 46, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 45, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 45, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 27, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 64, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 48, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 16, + "endColumn": 58, + "lineCount": 19 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 20, + "lineCount": 26 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + } + ], + "./sumpy/e2p.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 26, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 43, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 43, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 31, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 20, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 37, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 72, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 57, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 41, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 36, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 57, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 31, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 36, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + } + ], + "./sumpy/expansion/__init__.py": [ + { + "code": "reportAny", + "range": { + "startColumn": 21, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 21, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 13, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 59, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 59, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 59, + "endColumn": 62, + "lineCount": 1 + } + } + ], + "./sumpy/expansion/diff_op.py": [ + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 33, + "endColumn": 82, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 17, + "endColumn": 84, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 31, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 11, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 9, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 17, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 11, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 29, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 34, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 52, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 53, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 53, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 72, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 61, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 12, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 12, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 44, + "lineCount": 1 + } + } + ], + "./sumpy/expansion/level_to_order.py": [ + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 13, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 29, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 39, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 49, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 59, + "endColumn": 67, + "lineCount": 1 + } + } + ], + "./sumpy/expansion/local.py": [ + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 49, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 49, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 33, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 46, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 44, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 54, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 62, + "lineCount": 1 + } + } + ], + "./sumpy/expansion/loopy.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 36, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + } + ], + "./sumpy/expansion/m2l.py": [ + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 16, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 16, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 16, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 16, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 16, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 16, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 16, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 53, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 16, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 16, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + } + ], + "./sumpy/expansion/multipole.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 62, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 56, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 75, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 64, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 59, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 44, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 54, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 23, + "endColumn": 26, + "lineCount": 1 + } + } + ], + "./sumpy/fmm.py": [ + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 58, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 58, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 69, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 69, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 42, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 42, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 53, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 53, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 43, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 43, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 54, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 54, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 86, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleVariableOverride", + "range": { + "startColumn": 4, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 16, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 16, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 16, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 17, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 63, + "lineCount": 2 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 63, + "lineCount": 2 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 13, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 36, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 36, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 37, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 26, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 52, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 57, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 36, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 48, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 57, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 16, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 16, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 56, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 56, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 33, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 26, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 26, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 61, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 57, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 38, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 38, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 38, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 26, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 48, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 48, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 60, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 60, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 4, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 4, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 4, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnusedVariable", + "range": { + "startColumn": 20, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 30, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 33, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 20, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 20, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 20, + "lineCount": 3 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 20, + "lineCount": 3 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 32, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 32, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 45, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 45, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 62, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 62, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 18, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 42, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 60, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 26, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 26, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 30, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 30, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 18, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 18, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 64, + "lineCount": 2 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 76, + "lineCount": 2 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 21, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 48, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 64, + "lineCount": 2 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 37, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 41, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 41, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 64, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportOptionalMemberAccess", + "range": { + "startColumn": 42, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 26, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 26, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 42, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 42, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 61, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 29, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 48, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 41, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 41, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 34, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 20, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 24, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 20, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 42, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 42, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 65, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 65, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 43, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 51, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 58, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 54, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 54, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 27, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 27, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 41, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 41, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 56, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 56, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 40, + "lineCount": 2 + } + } + ], + "./sumpy/kernel.py": [ + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 11, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 16, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 43, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 62, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 65, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 64, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 67, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 67, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 25, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 37, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 43, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 49, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 56, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 37, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 43, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 46, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 52, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 63, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 66, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 37, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 43, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 49, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 56, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 22, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 37, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 43, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 46, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 52, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 59, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 22, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 37, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 43, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 49, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 56, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 22, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 37, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 43, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownLambdaType", + "range": { + "startColumn": 46, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 52, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 66, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 69, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 22, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportIndexIssue", + "range": { + "startColumn": 20, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 20, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportIndexIssue", + "range": { + "startColumn": 20, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportIndexIssue", + "range": { + "startColumn": 20, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportIndexIssue", + "range": { + "startColumn": 20, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 20, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 16, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 41, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 30, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 53, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 53, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 58, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 36, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 4, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 4, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 40, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 4, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 33, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 15, + "lineCount": 1 + } + } + ], + "./sumpy/p2e.py": [ + { + "code": "reportImplicitAbstractClass", + "range": { + "startColumn": 6, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 48, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 48, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 59, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 59, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 31, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 17, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 48, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 57, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 57, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 68, + "endColumn": 81, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 31, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 27, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 57, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 57, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 31, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 41, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 57, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 57, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + } + ], + "./sumpy/p2p.py": [ + { + "code": "reportImplicitAbstractClass", + "range": { + "startColumn": 6, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 39, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 39, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 53, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 53, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 31, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 31, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 42, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 42, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 48, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 56, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 58, + "endColumn": 72, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 23, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 53, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 60, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 27, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 15, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 31, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 13 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 18, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 11 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 21 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 57, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 57, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 36, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 36, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 36, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 27, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 29, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + } + ], + "./sumpy/point_calculus.py": [ + { + "code": "reportAny", + "range": { + "startColumn": 17, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 29, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 29, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 46, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 56, + "lineCount": 4 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 60, + "lineCount": 4 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 21, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 31, + "lineCount": 5 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 23, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 15, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 39, + "lineCount": 3 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 23, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 31, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 40, + "lineCount": 1 + } + } + ], + "./sumpy/qbx.py": [ + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 27, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 27, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 17, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 22, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 22, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 33, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 33, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 39, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 39, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 47, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 47, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 59, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 52, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 24, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 24, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 29, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 29, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 41, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 41, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 49, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 49, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 63, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 63, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 70, + "lineCount": 3 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 62, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 68, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 31, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 31, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 26, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnusedVariable", + "range": { + "startColumn": 36, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 57, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 39, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 12, + "endColumn": 25, + "lineCount": 8 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 15 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 21, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 21, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 30, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 30, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 39, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 39, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 50, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 50, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 12, + "endColumn": 25, + "lineCount": 6 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 13 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 21, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 21, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 30, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 30, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 39, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 39, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 58, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 58, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 12, + "endColumn": 25, + "lineCount": 14 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 20 + } + }, + { + "code": "reportIncompatibleMethodOverride", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 56, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 56, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 21, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 21, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 30, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 30, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 39, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 39, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 24, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 24, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 38, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 38, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 19, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 19, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 27, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 27, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 28, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 28, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 28, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 37, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 67, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 33, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 46, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 60, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 55, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 41, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 29, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 42, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 56, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnusedClass", + "range": { + "startColumn": 6, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 39, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 39, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 27, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 27, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 52, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 45, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 48, + "lineCount": 1 + } + } + ], + "./sumpy/symbolic.py": [ + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 15, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnusedImport", + "range": { + "startColumn": 15, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 11, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 7, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 10, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnusedFunction", + "range": { + "startColumn": 4, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 16, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 39, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 16, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 16, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 33, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 41, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 23, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 5, + "endColumn": 10, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 35, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 46, + "lineCount": 1 + } + } + ], + "./sumpy/test/coeff_test_tools.py": [ + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 7, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 14, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 14, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 15, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 15, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 36, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 36, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 21, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 21, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 72, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 18, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + } + ], + "./sumpy/test/curve.py": [ + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 15, + "lineCount": 1 + } + } + ], + "./sumpy/test/geometries.py": [ + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 36, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_codegen.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 39, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportEmptyAbstractUsage", + "range": { + "startColumn": 11, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 75, + "endColumn": 76, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_cse.py": [ + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 0, + "endColumn": 1, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 3, + "endColumn": 4, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 6, + "endColumn": 7, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 9, + "endColumn": 10, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 13, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 0, + "endColumn": 2, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 6, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 10, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 20, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 24, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 32, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 36, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 45, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 50, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 56, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 9, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 9, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 9, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 9, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 41, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 35, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 33, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 11, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 31, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 42, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 31, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 36, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 31, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 34, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 31, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 34, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 11, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 24, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 44, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 32, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 42, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 44, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 24, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 37, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 46, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 32, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 29, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 47, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 37, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 35, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 29, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 47, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 36, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 53, + "lineCount": 2 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 6, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 6, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 21, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 21, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 21, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 25, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 29, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 62, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 29, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 31, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 41, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 46, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 48, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 58, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 29, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 35, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 41, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 35, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 49, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 39, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 32, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 46, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 44, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 35, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 51, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 26, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 29, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 14, + "lineCount": 25 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 9, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 18, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 39, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 17, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 24, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 36, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 41, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 17, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 45, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 9, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 18, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 24, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 45, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 15, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 12, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportIndexIssue", + "range": { + "startColumn": 13, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 15, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportIndexIssue", + "range": { + "startColumn": 20, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 26, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportIndexIssue", + "range": { + "startColumn": 27, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 29, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportIndexIssue", + "range": { + "startColumn": 34, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 14, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportIndexIssue", + "range": { + "startColumn": 15, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 17, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportIndexIssue", + "range": { + "startColumn": 22, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 9, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 32, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 6, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 6, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 18, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 36, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 6, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 6, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 18, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 36, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 30, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 6, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 17, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 47, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 36, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 48, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 11, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 21, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 15, + "endColumn": 53, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_distributed.py": [ + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 63, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 14, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 14, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 41, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 27, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 27, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 27, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 40, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 40, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 62, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 62, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 45, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 49, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 49, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 42, + "endColumn": 80, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 84, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 57, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 18, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 71, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 42, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 25, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 25, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 42, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 42, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 71, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 46, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 29, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 29, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 39, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 39, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 49, + "endColumn": 81, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 49, + "endColumn": 81, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 35, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_fmm.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 65, + "endColumn": 81, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 66, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 61, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 62, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportOptionalOperand", + "range": { + "startColumn": 24, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 21, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 68, + "endColumn": 84, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 16, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportRedeclaration", + "range": { + "startColumn": 16, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 35, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 43, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 43, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 43, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 62, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 62, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 35, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 43, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 43, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 43, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 62, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 62, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 62, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 31, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 67, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 67, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 48, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 48, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 8, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 27, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 27, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 27, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 35, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 48, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 48, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 48, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 54, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 54, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 54, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 70, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 12, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 46, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 48, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 63, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 42, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 42, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 64, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 67, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 34, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 60, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 49, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_heat_translations.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 68, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 46, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 60, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 27, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 31, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 45, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 64, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 42, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 56, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 27, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 60, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 65, + "endColumn": 81, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 57, + "lineCount": 2 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 49, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 50, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 64, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 45, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_kernels.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 48, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 48, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 66, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 12, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 46, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 48, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 63, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 31, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 42, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 33, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 16, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 50, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 51, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 52, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 59, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 64, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 33, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 42, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 27, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 41, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 7, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 60, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 65, + "endColumn": 81, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 57, + "lineCount": 2 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 35, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 35, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 45, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 45, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 63, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 63, + "endColumn": 79, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 8, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 15, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 15, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 42, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 36, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 70, + "endColumn": 80, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 21, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 21, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 26, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 26, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 41, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 41, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 56, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 56, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 73, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 73, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 27, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 33, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 33, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 54, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 60, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 45, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 77, + "endColumn": 82, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 59, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 31, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 26, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 28, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 75, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 75, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 80, + "endColumn": 85, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 80, + "endColumn": 85, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 26, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 58, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 59, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 70, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 70, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 9, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 33, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 24, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 24, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 31, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 48, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportEmptyAbstractUsage", + "range": { + "startColumn": 22, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 10, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 7, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 14, + "endColumn": 35, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_l2l_coeffs.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 29, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 39, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 16, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 44, + "endColumn": 60, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_m2m_coeffs.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 26, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 43, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 37, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 21, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 43, + "endColumn": 59, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_matrixgen.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 20, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 20, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 26, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 26, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 36, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 36, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 46, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 46, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 52, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 9, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 26, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 26, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 32, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 32, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 42, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 42, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 52, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 52, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 11, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 21, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 21, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 41, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportEmptyAbstractUsage", + "range": { + "startColumn": 11, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 71, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 42, + "endColumn": 17, + "lineCount": 3 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 32, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 71, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportCallIssue", + "range": { + "startColumn": 42, + "endColumn": 70, + "lineCount": 2 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 32, + "endColumn": 68, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_misc.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 47, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 27, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 27, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 45, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 36, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 74, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 62, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 62, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 72, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 10, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 10, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 32, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 34, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 46, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 19, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 19, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 24, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 36, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 43, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 32, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 26, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 19, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 42, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 29, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 29, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 19, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 42, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 30, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 30, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 38, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 38, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 37, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 11, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 18, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 56, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 10, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 10, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 10, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 10, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 10, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 57, + "endColumn": 5, + "lineCount": 7 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 11, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 16, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 34, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 46, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 44, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 22, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 33, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 22, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 27, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 41, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 49, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 59, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 7, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 10, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 14, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 9, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 42, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 5, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 7, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 10, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 13, + "endColumn": 14, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 31, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 7, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 10, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 25, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 55, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 7, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 10, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 25, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 55, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 7, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 10, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 25, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 55, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 7, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 10, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 22, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 6, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 39, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 7, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 9, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 7, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 35, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 17, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 39, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 32, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 43, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 32, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 43, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 54, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 32, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 43, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 32, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 43, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 54, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 16, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 27, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 16, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 32, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 53, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 32, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 53, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 74, + "endColumn": 82, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 27, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 16, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 27, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 22, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 22, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 28, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 28, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 27, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 27, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 39, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 39, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 10, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 56, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 63, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 78, + "endColumn": 81, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 46, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 27, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 34, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 41, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 28, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 43, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 50, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 58, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 28, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 43, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 50, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 58, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 28, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 43, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 50, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 58, + "endColumn": 63, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_qbx.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 31, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 32, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 44, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 50, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 62, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 34, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 34, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 22, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 22, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 18, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 18, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 31, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 32, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 44, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 50, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 62, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 26, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 34, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 34, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 31, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 38, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_target_deriv.py": [ + { + "code": "reportEmptyAbstractUsage", + "range": { + "startColumn": 16, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 28, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 37, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 44, + "endColumn": 54, + "lineCount": 1 + } + } + ], + "./sumpy/test/test_tools.py": [ + { + "code": "reportUnusedImport", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 43, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 46, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 49, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 40, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 43, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 14, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 47, + "endColumn": 50, + "lineCount": 1 + } + } + ], + "./sumpy/tools.py": [ + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 17, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 17, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 21, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 21, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 33, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 33, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 31, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 26, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportOperatorIssue", + "range": { + "startColumn": 12, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 18, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 24, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 21, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 66, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 32, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 27, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 37, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 73, + "endColumn": 82, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 22, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 22, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 9, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 9, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 19, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 30, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 64, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 32, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 36, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 22, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 34, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 42, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 57, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 17, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 31, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 38, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 19, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 21, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 21, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 46, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 45, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 20, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 67, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 52, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 26, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 26, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 37, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 37, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 35, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 33, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 33, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportConstantRedefinition", + "range": { + "startColumn": 14, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 27, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 55, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnusedImport", + "range": { + "startColumn": 15, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 46, + "lineCount": 5 + } + }, + { + "code": "reportMissingTypeStubs", + "range": { + "startColumn": 13, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 4, + "endColumn": 7, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 11, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 13, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 11, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 44, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 40, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportAssignmentType", + "range": { + "startColumn": 8, + "endColumn": 30, + "lineCount": 1 + } + } + ], + "./sumpy/toys.py": [ + { + "code": "reportArgumentType", + "range": { + "startColumn": 32, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 9, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 9, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 15, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 15, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 24, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 24, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 32, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 32, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 52, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 52, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 57, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 57, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 69, + "endColumn": 80, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 69, + "endColumn": 80, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 33, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 51, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 51, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 51, + "endColumn": 69, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 36, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 9, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 9, + "endColumn": 13, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 15, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 15, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 24, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 24, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 33, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 33, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 15, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 33, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 59, + "endColumn": 73, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 59, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 59, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 13, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 19, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 9, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 9, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 55, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 55, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 60, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 60, + "endColumn": 70, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 72, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 72, + "endColumn": 83, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 9, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 9, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 10, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 26, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 4, + "endColumn": 8, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 9, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 9, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 29, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 40, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 50, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 50, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 55, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 55, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 67, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 67, + "endColumn": 78, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 9, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 9, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 52, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 62, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 48, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 18, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 69, + "endColumn": 76, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 36, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 14, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 63, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 26, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 18, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 22, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 34, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 45, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 56, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 17, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 29, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 48, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 49, + "endColumn": 59, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 42, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 42, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 18, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 22, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 38, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 52, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 38, + "endColumn": 47, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 21, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 32, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 43, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 56, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 58, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 19, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnusedParameter", + "range": { + "startColumn": 39, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 4, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 4, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 44, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 44, + "endColumn": 49, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 30, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 23, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 48, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 48, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 48, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 27, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 20, + "lineCount": 3 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 32, + "lineCount": 3 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 32, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 32, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 60, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 60, + "endColumn": 66, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 68, + "endColumn": 80, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 68, + "endColumn": 80, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 12, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 12, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 25, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 25, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 42, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 42, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 26, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 26, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 40, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 53, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 64, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 50, + "endColumn": 61, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 27, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 56, + "endColumn": 72, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportImplicitOverride", + "range": { + "startColumn": 8, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 23, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAttributeAccessIssue", + "range": { + "startColumn": 31, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 26, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 25, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 10, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 10, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 36, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 36, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 71, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 71, + "endColumn": 77, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportArgumentType", + "range": { + "startColumn": 65, + "endColumn": 71, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 62, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 11, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 13, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 13, + "endColumn": 15, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 17, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 17, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 23, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 32, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 39, + "endColumn": 45, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 16, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 16, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 24, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 24, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 34, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 34, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 24, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportPrivateImportUsage", + "range": { + "startColumn": 28, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 59, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 69, + "endColumn": 75, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 15, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 15, + "endColumn": 18, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 22, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 22, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 4, + "endColumn": 12, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 14, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 20, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 20, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 27, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 27, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 36, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 36, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 43, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 43, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 62, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 62, + "endColumn": 68, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 4, + "endColumn": 22, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 33, + "endColumn": 38, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 47, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 40, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 23, + "endColumn": 41, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 13, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 18, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 18, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 33, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 33, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 17, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 36, + "endColumn": 50, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 36, + "endColumn": 53, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 24, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 24, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 18, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 21, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 4, + "endColumn": 17, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 39, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 39, + "endColumn": 46, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 43, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 25, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 21, + "endColumn": 35, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 25, + "endColumn": 39, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 25, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 46, + "endColumn": 60, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 46, + "endColumn": 65, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 28, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 28, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 44, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 44, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 31, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 21, + "endColumn": 28, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 16, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 16, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 11, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 39, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 20, + "endColumn": 48, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 31, + "endColumn": 55, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 22, + "endColumn": 42, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 44, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 24, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 53, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 53, + "endColumn": 67, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 16, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 17, + "endColumn": 37, + "lineCount": 1 + } + }, + { + "code": "reportUnannotatedClassAttribute", + "range": { + "startColumn": 4, + "endColumn": 24, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 19, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 19, + "endColumn": 26, + "lineCount": 1 + } + }, + { + "code": "reportUnknownParameterType", + "range": { + "startColumn": 30, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportMissingParameterType", + "range": { + "startColumn": 30, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 4, + "endColumn": 34, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 23, + "endColumn": 29, + "lineCount": 1 + } + }, + { + "code": "reportUnknownArgumentType", + "range": { + "startColumn": 35, + "endColumn": 42, + "lineCount": 1 + } + } + ], + "./sumpy/visualization.py": [ + { + "code": "reportAny", + "range": { + "startColumn": 12, + "endColumn": 23, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 26, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 25, + "endColumn": 51, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 32, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportReturnType", + "range": { + "startColumn": 32, + "endColumn": 58, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 31, + "endColumn": 40, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 26, + "endColumn": 30, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 32, + "endColumn": 36, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 54, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 8, + "endColumn": 33, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 15, + "endColumn": 63, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportMissingImports", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 12, + "endColumn": 21, + "lineCount": 1 + } + }, + { + "code": "reportAny", + "range": { + "startColumn": 14, + "endColumn": 20, + "lineCount": 1 + } + }, + { + "code": "reportMissingImports", + "range": { + "startColumn": 13, + "endColumn": 19, + "lineCount": 1 + } + }, + { + "code": "reportUnknownMemberType", + "range": { + "startColumn": 8, + "endColumn": 17, + "lineCount": 1 + } + } + ] + } +} \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 000000000..db66bec8c --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,9 @@ +version: 2 +updates: + # Set update schedule for GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + +# vim: sw=4 diff --git a/.github/workflows/autopush.yml b/.github/workflows/autopush.yml index f89b08ac5..be4271c64 100644 --- a/.github/workflows/autopush.yml +++ b/.github/workflows/autopush.yml @@ -7,14 +7,15 @@ on: jobs: autopush: name: Automatic push to gitlab.tiker.net + if: startsWith(github.repository, 'inducer/') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - run: | - mkdir ~/.ssh && echo -e "Host gitlab.tiker.net\n\tStrictHostKeyChecking no\n" >> ~/.ssh/config - eval $(ssh-agent) && echo "$GITLAB_AUTOPUSH_KEY" | ssh-add - - git fetch --unshallow - git push "git@gitlab.tiker.net:inducer/$(basename $GITHUB_REPOSITORY).git" main + curl -L -O https://tiker.net/ci-support-v0 + . ./ci-support-v0 + mirror_github_to_gitlab + env: GITLAB_AUTOPUSH_KEY: ${{ secrets.GITLAB_AUTOPUSH_KEY }} diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12c8d4ce1..74bd3d976 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,27 +7,50 @@ on: schedule: - cron: '17 3 * * 0' +concurrency: + group: ${{ github.head_ref || github.ref_name }} + cancel-in-progress: true + jobs: - flake8: - name: Flake8 + ruff: + name: Ruff + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - uses: astral-sh/setup-uv@v7 + - name: "Main Script" + run: | + uv run --only-group lint ruff check + + typos: + name: Typos runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 + - uses: crate-ci/typos@master + + basedpyright: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 - - uses: actions/setup-python@v1 + uses: actions/setup-python@v7 with: - # matches compat target in setup.py - python-version: '3.6' + python-version: '3.x' - name: "Main Script" run: | - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-flake8.sh - . ./prepare-and-run-flake8.sh "$(basename $GITHUB_REPOSITORY)" test examples benchmarks + curl -L -O https://tiker.net/ci-support-v0 + . ./ci-support-v0 + build_py_project_in_conda_env + cipip install pytest pyfmmlib scipy scipy-stubs matplotlib pyvisfile optype + cipip install basedpyright + basedpyright docs: name: Documentation runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - name: "Main Script" run: | CONDA_ENVIRONMENT=.test-conda-env-py3.yml @@ -40,7 +63,7 @@ jobs: name: Conda Pytest runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - name: "Main Script" run: | grep -v symengine .test-conda-env-py3.yml > .test-conda-env.yml @@ -48,11 +71,22 @@ jobs: curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project-within-miniconda.sh . ./build-and-test-py-project-within-miniconda.sh + pytest_symengine_loopy_fft: + name: Conda Pytest Symengine with Loopy FFT + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v7 + - name: "Main Script" + run: | + curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project-within-miniconda.sh + export SUMPY_FFT_BACKEND=loopy + . ./build-and-test-py-project-within-miniconda.sh + pytest_symengine: name: Conda Pytest Symengine runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - name: "Main Script" run: | curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project-within-miniconda.sh @@ -62,7 +96,7 @@ jobs: name: Conda Examples runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - name: "Main Script" run: | grep -v symengine .test-conda-env-py3.yml > .test-conda-env.yml @@ -73,7 +107,6 @@ jobs: build_py_project_in_conda_env run_examples - downstream_tests: strategy: matrix: @@ -81,15 +114,15 @@ jobs: name: Tests for downstream project ${{ matrix.downstream_project }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v7 - name: "Main Script" env: DOWNSTREAM_PROJECT: ${{ matrix.downstream_project }} run: | curl -L -O https://tiker.net/ci-support-v0 . ./ci-support-v0 - if [[ "$DOWNSTREAM_PROJECT" == "pytential" && "$GITHUB_HEAD_REF" == "rscale" ]]; then - DOWNSTREAM_PROJECT=https://github.com/isuruf/pytential.git@level_to_rscale + if [[ "$DOWNSTREAM_PROJECT" == "pytential" && "$GITHUB_HEAD_REF" == "towards-array-context-merge" ]]; then + DOWNSTREAM_PROJECT=https://github.com/alexfikl/pytential.git@towards-array-context fi test_downstream "$DOWNSTREAM_PROJECT" diff --git a/.gitignore b/.gitignore index ab75b2eeb..3f27eed10 100644 --- a/.gitignore +++ b/.gitignore @@ -20,5 +20,6 @@ doc/_build sumpy/_git_rev.py .asv +.venv *.vts diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 6a79583bc..13bca8b85 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -17,16 +17,11 @@ # reports: # junit: test/pytest.xml -stages: - - test - - deploy - Pytest POCL: - stage: test script: - export PY_EXE=python3 - export PYOPENCL_TEST=portable:pthread - - export EXTRA_INSTALL="pybind11 numpy mako" + - export EXTRA_INSTALL="pybind11 numpy mako mpi4py" - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" tags: @@ -39,11 +34,10 @@ Pytest POCL: junit: test/pytest.xml Pytest Titan V: - stage: test script: - py_version=3 - export PYOPENCL_TEST=nvi:titan - - EXTRA_INSTALL="pybind11 numpy mako" + - EXTRA_INSTALL="pybind11 numpy mako mpi4py" - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-test-py-project.sh - ". ./build-and-test-py-project.sh" tags: @@ -57,7 +51,6 @@ Pytest Titan V: junit: test/pytest.xml Pytest Conda: - stage: test script: # Disable caching to ensure SymEngine code generation is exercised. - export SUMPY_NO_CACHE=1 @@ -74,7 +67,6 @@ Pytest Conda: junit: test/pytest.xml Pytest POCL Titan V: - stage: test script: # Disable caching to ensure SymEngine code generation is exercised. - export SUMPY_NO_CACHE=1 @@ -91,7 +83,6 @@ Pytest POCL Titan V: junit: test/pytest.xml Examples Conda: - stage: test script: | grep -v symengine .test-conda-env-py3.yml > .test-conda-env.yml CONDA_ENVIRONMENT=.test-conda-env.yml @@ -106,7 +97,6 @@ Examples Conda: - tags Documentation: - stage: deploy script: | EXTRA_INSTALL="pybind11 numpy mako" curl -L -O https://tiker.net/ci-support-v0 @@ -118,26 +108,12 @@ Documentation: tags: - linux -Flake8: - stage: test +Ruff: script: - - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/prepare-and-run-flake8.sh - - . ./prepare-and-run-flake8.sh "$CI_PROJECT_NAME" test examples benchmarks - tags: - - python3 - except: - - tags - -Benchmarks: - stage: test - script: | - PROJECT=sumpy - PYOPENCL_TEST=portable:pthread - curl -L -O https://gitlab.tiker.net/inducer/ci-support/raw/main/build-and-benchmark-py-project.sh - . ./build-and-benchmark-py-project.sh + - pipx install uv + - uv run --only-group lint ruff check tags: - - linux - - benchmark + - docker-runner except: - tags diff --git a/.test-conda-env-py3.yml b/.test-conda-env-py3.yml index 208f46e08..46ddbbee7 100644 --- a/.test-conda-env-py3.yml +++ b/.test-conda-env-py3.yml @@ -6,12 +6,23 @@ channels: dependencies: - git - numpy +- scipy - sympy -- pocl +# https://github.com/pocl/pocl/issues/2069 +- pocl<7 - pocl-cuda - islpy - pyopencl - python=3 -- python-symengine=0.6.0 +- python-symengine - pyfmmlib -- pyrsistent +- pyvkfft +- mpi4py + +# This is intended to prevent conda from selecting 'external' (i.e. empty) builds +# of OpenMPI to satisfy the MPI dependency of mpi4py. It did so in May 2024, leading +# to confusing failues saying +# 'libmpi.so.40: cannot open shared object file: No such file or directory'. +# https://github.com/conda-forge/openmpi-feedstock/issues/153 +# https://conda-forge.org/docs/user/tipsandtricks/#using-external-message-passing-interface-mpi-libraries +- openmpi>=5=h* diff --git a/CITATION.cff b/CITATION.cff new file mode 100644 index 000000000..dddf5c235 --- /dev/null +++ b/CITATION.cff @@ -0,0 +1,23 @@ +cff-version: 1.2.0 +message: "If you use this software, please cite it as below." +authors: +- family-names: "Kloeckner" + given-names: "Andreas" + orcid: "https://orcid.org/0000-0003-1228-519X" +- family-names: Fernando + given-names: Isuru +- family-names: Wala + given-names: Matt +- family-names: Fikl + given-names: Alexandru +- family-names: Beams + given-names: Natalie +- family-names: Gao + given-names: Hao + +title: "sumpy" +version: 2022.1 +doi: 10.5281/zenodo.7349787 +date-released: 2022-11-23 +url: "https://github.com/inducer/sumpy" +license: MIT diff --git a/MANIFEST.in b/MANIFEST.in deleted file mode 100644 index 6afc650e9..000000000 --- a/MANIFEST.in +++ /dev/null @@ -1,12 +0,0 @@ -include test/*.py -include examples/*.py - -include doc/*.rst -include doc/Makefile -include doc/*.py -include doc/images/*.png -include doc/_static/*.css -include doc/_templates/*.html - -include README.rst -include requirements.txt diff --git a/README.rst b/README.rst index 3c5bf836f..c866cabc4 100644 --- a/README.rst +++ b/README.rst @@ -4,35 +4,33 @@ sumpy: n-body kernels and translation operators .. image:: https://gitlab.tiker.net/inducer/sumpy/badges/main/pipeline.svg :alt: Gitlab Build Status :target: https://gitlab.tiker.net/inducer/sumpy/commits/main -.. image:: https://github.com/inducer/sumpy/workflows/CI/badge.svg?branch=main&event=push +.. image:: https://github.com/inducer/sumpy/actions/workflows/ci.yml/badge.svg :alt: Github Build Status - :target: https://github.com/inducer/sumpy/actions?query=branch%3Amain+workflow%3ACI+event%3Apush -.. image:: https://badge.fury.io/py/sumpy.png + :target: https://github.com/inducer/sumpy/actions/workflows/ci.yml +.. image:: https://badge.fury.io/py/sumpy.svg :alt: Python Package Index Release Page :target: https://pypi.org/project/sumpy/ +.. image:: https://zenodo.org/badge/1856097.svg + :alt: Zenodo DOI for latest release + :target: https://zenodo.org/badge/latestdoi/1856097 -Sumpy is mainly a 'scaffolding' package for Fast Multipole and quadrature methods. -If you're building one of those and need code generation for the required Multipole -and local expansions, come right on in. Together with boxtree, there is a full, +sumpy is mainly a 'scaffolding' package for Fast Multipole and quadrature methods. +If you're building one of those and need code generation for the required multipole +and local expansions, come right on in. Together with ``boxtree``, there is a full, symbolically kernel-independent FMM implementation here. -Sumpy relies on +It relies on -* `numpy `_ for arrays -* `boxtree `_ for FMM tree building -* `sumpy `_ for expansions and analytical routines -* `loopy `_ for fast array operations -* `pytest `_ for automated testing +* `boxtree `__ for FMM tree building +* `loopy `__ for fast array operations +* `pytest `__ for automated testing and, indirectly, -* `PyOpenCL `_ as computational infrastructure - -PyOpenCL is likely the only package you'll have to install -by hand, all the others will be installed automatically. +* `PyOpenCL `__ as computational infrastructure Resources: -* `documentation `_ -* `source code via git `_ -* `benchmarks `_ +* `documentation `__ +* `source code via git `__ +* `benchmarks `__ diff --git a/asv.conf.json b/asv.conf.json index 9d214154f..674de5478 100644 --- a/asv.conf.json +++ b/asv.conf.json @@ -72,6 +72,7 @@ "pyopencl" : [""], "islpy" : [""], "pocl" : [""], + "pyvkfft": [""], "pip+git+https://github.com/inducer/pymbolic#egg=pymbolic": [""], "pip+git+https://gitlab.tiker.net/inducer/boxtree#egg=boxtree": [""], "pip+git+https://github.com/inducer/loopy#egg=loopy": [""], diff --git a/benchmarks/bench_translations.py b/benchmarks/bench_translations.py index f8d718f41..8aba95fca 100644 --- a/benchmarks/bench_translations.py +++ b/benchmarks/bench_translations.py @@ -1,18 +1,26 @@ +from __future__ import annotations + +import logging + import numpy as np -from pyopencl.tools import ( # noqa - pytest_generate_tests_for_pyopencl as pytest_generate_tests) +from pyopencl.tools import ( # ruff:ignore[unused-import] + pytest_generate_tests_for_pyopencl as pytest_generate_tests, +) -from sumpy.expansion.multipole import ( - VolumeTaylorMultipoleExpansion, H2DMultipoleExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion) from sumpy.expansion.local import ( - VolumeTaylorLocalExpansion, H2DLocalExpansion, - LinearPDEConformingVolumeTaylorLocalExpansion) + H2DLocalExpansion, + LinearPDEConformingVolumeTaylorLocalExpansion, + VolumeTaylorLocalExpansion, +) +from sumpy.expansion.multipole import ( + H2DMultipoleExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion, + VolumeTaylorMultipoleExpansion, +) +from sumpy.kernel import HelmholtzKernel, LaplaceKernel -from sumpy.kernel import LaplaceKernel, HelmholtzKernel -import logging logger = logging.getLogger(__name__) import pymbolic.mapper.flop_counter @@ -33,19 +41,19 @@ def __repr__(self): class TranslationBenchmarkSuite: - params = [ + params = ( Param(2, 10), Param(2, 15), Param(2, 20), Param(3, 5), Param(3, 10), - ] + ) - param_names = ["order"] + param_names = ("order",) def setup(self, param): logging.basicConfig(level=logging.INFO) - np.random.seed(17) + np.random.seed(17) # ruff:ignore[numpy-legacy-random] if self.__class__ == TranslationBenchmarkSuite: raise NotImplementedError mpole_expn_class = self.mpole_expn_class @@ -74,11 +82,11 @@ def track_m2l_op_count(self, param): dvec, tgt_rscale) for i, expr in enumerate(result): sac.assign_unique(f"coeff{i}", expr) - sac.run_global_cse() + sac = sac.run_global_cse() insns = to_loopy_insns(sac.assignments.items()) counter = pymbolic.mapper.flop_counter.CSEAwareFlopCounter() - return sum([counter.rec(insn.expression)+1 for insn in insns]) + return sum(counter.rec(insn.expression)+1 for insn in insns) track_m2l_op_count.unit = "ops" track_m2l_op_count.timeout = 300.0 @@ -88,10 +96,10 @@ class LaplaceVolumeTaylorTranslation(TranslationBenchmarkSuite): knl = LaplaceKernel local_expn_class = VolumeTaylorLocalExpansion mpole_expn_class = VolumeTaylorMultipoleExpansion - params = [ + params = ( Param(2, 10), Param(3, 5), - ] + ) class LaplaceConformingVolumeTaylorTranslation(TranslationBenchmarkSuite): @@ -104,10 +112,10 @@ class HelmholtzVolumeTaylorTranslation(TranslationBenchmarkSuite): knl = HelmholtzKernel local_expn_class = VolumeTaylorLocalExpansion mpole_expn_class = VolumeTaylorMultipoleExpansion - params = [ + params = ( Param(2, 10), Param(3, 5), - ] + ) class HelmholtzConformingVolumeTaylorTranslation(TranslationBenchmarkSuite): @@ -120,8 +128,8 @@ class Helmholtz2DTranslation(TranslationBenchmarkSuite): knl = HelmholtzKernel local_expn_class = H2DLocalExpansion mpole_expn_class = H2DMultipoleExpansion - params = [ + params = ( Param(2, 10), Param(2, 15), Param(2, 20), - ] + ) diff --git a/contrib/translations/PDE-reduction and translations.ipynb b/contrib/translations/PDE-reduction and translations.ipynb index 62a626236..d4df4f2e1 100644 --- a/contrib/translations/PDE-reduction and translations.ipynb +++ b/contrib/translations/PDE-reduction and translations.ipynb @@ -2,35 +2,43 @@ "cells": [ { "cell_type": "code", - "execution_count": 35, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "import pyopencl as cl\n", - "import sumpy.toys as t\n", + "from __future__ import annotations\n", + "\n", + "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import numpy.linalg as la\n", - "import matplotlib.pyplot as plt\n", - "from sumpy.visualization import FieldPlotter\n", + "\n", + "import pyopencl as cl\n", "from pytools import add_tuples\n", "\n", + "import sumpy.toys as t\n", "from sumpy.expansion.local import VolumeTaylorLocalExpansion\n", "from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansion\n", - "from sumpy.kernel import (YukawaKernel, HelmholtzKernel, LaplaceKernel)\n", + "from sumpy.kernel import ( # ruff:ignore[unused-import]\n", + " HelmholtzKernel,\n", + " LaplaceKernel,\n", + " YukawaKernel,\n", + ")\n", + "\n", "\n", + "rng = np.random.default_rng(seed=42)\n", "order = 4\n", "\n", "if 0:\n", " knl = LaplaceKernel(2)\n", - " pde = [(1, (2,0)), (1, (0, 2))]\n", + " pde = [(1, (2, 0)), (1, (0, 2))]\n", " extra_kernel_kwargs = {}\n", - " \n", + "\n", "else:\n", " helm_k = 1.2\n", " knl = HelmholtzKernel(2)\n", - " extra_kernel_kwargs={\"k\": helm_k}\n", + " extra_kernel_kwargs = {\"k\": helm_k}\n", "\n", - " pde = [(1, (2,0)), (1, (0, 2)), (helm_k**2, (0, 0))]\n", + " pde = [(1, (2, 0)), (1, (0, 2)), (helm_k**2, (0, 0))]\n", "\n", "mpole_expn = VolumeTaylorMultipoleExpansion(knl, order)\n", "local_expn = VolumeTaylorLocalExpansion(knl, order)\n", @@ -38,203 +46,145 @@ "cl_ctx = cl.create_some_context(answers=[\"port\"])\n", "\n", "tctx = t.ToyContext(\n", - " cl_ctx,\n", - " knl,\n", - " mpole_expn_class=type(mpole_expn),\n", - " local_expn_class=type(local_expn),\n", - " extra_kernel_kwargs=extra_kernel_kwargs,\n", - " )\n" + " cl_ctx,\n", + " knl,\n", + " mpole_expn_class=type(mpole_expn),\n", + " local_expn_class=type(local_expn),\n", + " extra_kernel_kwargs=extra_kernel_kwargs,\n", + ")" ] }, { "cell_type": "code", - "execution_count": 36, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ - "pt_src = t.PointSources(\n", - " tctx,\n", - " np.random.rand(2, 50) - 0.5,\n", - " np.ones(50))\n", + "pt_src = t.PointSources(tctx, rng.uniform(-0.5, 0.5, size=(2, 50)), np.ones(50))\n", "\n", "mexp = t.multipole_expand(pt_src, [0, 0], order)" ] }, { "cell_type": "code", - "execution_count": 37, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "array([ 5.00000000e+01, 4.76258789e+00, 6.63902810e-01,\n", - " 2.17149444e+00, 6.22396090e-01, 2.36567252e+00,\n", - " 5.93173776e-02, 6.33392972e-02, 1.15590385e-01,\n", - " 2.35250166e-02, 2.60421537e-02, 1.58948983e-02,\n", - " 9.97399769e-02, 1.12510066e-02, 3.13387666e-02])" - ] - }, - "execution_count": 37, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "mexp.coeffs" ] }, { "cell_type": "code", - "execution_count": 38, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def build_pde_mat(expn, pde):\n", " coeff_ids = expn.get_coefficient_identifiers()\n", " id_to_index = expn._storage_loc_dict\n", - " \n", + "\n", " # FIXME: specific to scalar PDEs\n", " pde_mat = np.zeros((len(coeff_ids), len(coeff_ids)))\n", - " \n", + "\n", " row = 0\n", " for base_coeff_id in coeff_ids:\n", " valid = True\n", - " \n", + "\n", " for pde_coeff, coeff_id_offset in pde:\n", " other_coeff = add_tuples(base_coeff_id, coeff_id_offset)\n", - " if not other_coeff in id_to_index:\n", + " if other_coeff not in id_to_index:\n", " valid = False\n", " break\n", - " \n", + "\n", " pde_mat[row, id_to_index[other_coeff]] = pde_coeff\n", - " \n", + "\n", " if valid:\n", " row += 1\n", " else:\n", " pde_mat[row] = 0\n", - " \n", + "\n", " return pde_mat[:row]\n", "\n", + "\n", "pde_mat = build_pde_mat(mpole_expn, pde)" ] }, { "cell_type": "code", - "execution_count": 39, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def find_nullspace(mat, tol=1e-10):\n", - " u, sig, vt = la.svd(pde_mat, full_matrices=True)\n", + " _u, sig, vt = la.svd(pde_mat, full_matrices=True)\n", " zerosig = np.where(np.abs(sig) < tol)[0]\n", - " if zerosig:\n", + " if zerosig.size:\n", " nullsp_start = zerosig[0]\n", " assert np.array_equal(zerosig, np.arange(nullsp_start, pde_mat.shape[1]))\n", " else:\n", " nullsp_start = pde_mat.shape[0]\n", - " \n", + "\n", " return vt[nullsp_start:].T\n", - " \n", + "\n", + "\n", "nullsp = find_nullspace(pde_mat)" ] }, { "cell_type": "code", - "execution_count": 40, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "4.3183836498795062e-16" - ] - }, - "execution_count": 40, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "la.norm(pde_mat @ nullsp)" ] }, { "cell_type": "code", - "execution_count": 41, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "def build_translation_mat(mexp, to_center):\n", " n = len(mexp.coeffs)\n", " result = np.zeros((n, n))\n", - " \n", + "\n", " for j in range(n):\n", " unit_coeffs = np.zeros(n)\n", " unit_coeffs[j] = 1\n", " unit_mexp = mexp.with_coeffs(unit_coeffs)\n", - " \n", + "\n", " result[:, j] = t.multipole_expand(unit_mexp, to_center).coeffs\n", - " \n", + "\n", " return result\n", "\n", + "\n", "new_center = np.array([0, 0.5])\n", "tmat = build_translation_mat(mexp, new_center)" ] }, { "cell_type": "code", - "execution_count": 42, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "" - ] - }, - "execution_count": 42, - "metadata": {}, - "output_type": "execute_result" - }, - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAP8AAAD8CAYAAAC4nHJkAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4xLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvAOZPmwAADcBJREFUeJzt3X+s3XV9x/Hn29tS1gLSgijQZoVJ\nmoBZBmmw6uLMOn6OUP/wj5K5dWJCzOIGxkVLSGayv+ZcdFtmZhpwYxkBM4TZGBg0VbMsWaultoVa\naCtjUFspk4UKxpay9/4430su13N/fX/dc/08H8nN+fX59vvu99zX/Z7zPd/PeUdmIqk8b5vvAiTN\nD8MvFcrwS4Uy/FKhDL9UKMMvFcrwS4Uy/FKhDL9UqEV9ruz8FWO5etXiOS93cN/SDqqRfvn8nNc4\nlSdjNmN7Df/qVYv57mOr5rzcdRf9RgfVSL98dub2WY/1Zb9UKMMvFapR+CPi+oh4JiIOR8TmtoqS\n1L3a4Y+IMeDLwA3A5cAtEXF5W4VJ6laTPf/VwOHMfDYzTwEPABvaKUtS15qE/2LghQm3j1T3SVoA\nmoR/2GeJv/C1QBFxW0TsiohdL/3kjQark9SmJuE/Akz80H4lcHTyoMzckplrM3PtO84ba7A6SW1q\nEv7vAZdFxCURcQawEdjaTlmSulb7DL/MPB0RnwQeA8aAr2bm/tYqk9SpRqf3ZuYjwCMt1SKpR57h\nJxXK8EuF6nVW38F9S2vN0Hvs6J7a63RGoDSce36pUIZfKpThlwpl+KVCGX6pUIZfKpThlwpl+KVC\nGX6pUIZfKpThlwpl+KVCGX6pUL3O6ju5ahmHP71uzstdd1H9dTojUBrOPb9UKMMvFcrwS4Vq0qtv\nVUR8OyIORMT+iLi9zcIkdavJAb/TwKczc3dEnA08ERHbMvMHLdUmqUO19/yZeSwzd1fXfwocwF59\n0oLRynv+iFgNXAnsbOPfk9S9xuGPiLOArwN3ZOaJIY+/2ajzjVdfa7o6SS1pFP6IWMwg+Pdl5kPD\nxkxs1Dl21rImq5PUoiZH+wO4BziQmV9sryRJfWiy5/8A8PvAb0fEnurnxpbqktSxJl16/wOIFmuR\n1CPP8JMKZfilQkVm9rayc2JFvjfW97a+pupOB3YqsObLztzOiXx5Vm/H3fNLhTL8UqEMv1Qowy8V\nyvBLhTL8UqEMv1Qowy8VyvBLhTL8UqEMv1Qowy8VyvBLheq1UWddh7809+ae4979qR21l607O8/m\noFoI3PNLhTL8UqEMv1SoNpp2jEXE9yPim20UJKkfbez5b2fQp0/SAtK0Y89K4HeBu9spR1Jfmu75\n/xr4DPB/LdQiqUdN2nXdBBzPzCdmGPdmo87XOVl3dZJa1rRd180R8RzwAIO2Xf88edDERp2LWdJg\ndZLaVDv8mXlnZq7MzNXARuBbmfnR1iqT1Ck/55cK1cq5/Zn5HeA7bfxbkvrhnl8qlOGXCtXrlN44\ncwlj714z5+WaTMudj+nATabl2hxUfXHPLxXK8EuFMvxSoQy/VCjDLxXK8EuFMvxSoQy/VCjDLxXK\n8EuFMvxSoQy/VCjDLxUqMrO3lZ0TK/K9sb639ZXE5qAC2JnbOZEvx2zGuueXCmX4pUIZfqlQTdt1\nnRsRD0bE0xFxICLe11ZhkrrV9Gu8/gb4t8z8SEScASxtoSZJPagd/og4B/gg8IcAmXkKONVOWZK6\n1uRl/6XAS8A/RMT3I+LuiFjWUl2SOtYk/IuAq4C/z8wrgdeAzZMH2ahTGk1Nwn8EOJKZO6vbDzL4\nY/AWNuqURlOTRp0/Bl6IiPEv4l8P/KCVqiR1runR/j8G7quO9D8LfKx5SZL60Cj8mbkHWNtSLZJ6\n5Bl+UqEMv1SoXht1LjR1m3w2aSxal81BNVfu+aVCGX6pUIZfKpThlwpl+KVCGX6pUIZfKpThlwpl\n+KVCGX6pUIZfKpThlwpl+KVCLYhGnWNXrJl50BTe2P9M7WXrqjsbEOZnRmBdNgcdPTbqlDQjwy8V\nyvBLhWraqPNTEbE/Ip6KiPsj4sy2CpPUrdrhj4iLgT8B1mbme4AxYGNbhUnqVtOX/YuAX4mIRQw6\n9B5tXpKkPjTp2PMj4K+A54FjwCuZ+XhbhUnqVpOX/cuBDcAlwEXAsoj46JBxNuqURlCTl/2/A/xX\nZr6Uma8DDwHvnzzIRp3SaGoS/ueBdRGxNCKCQaPOA+2UJalrTd7z72TQlns38GT1b21pqS5JHWva\nqPNzwOdaqkVSjzzDTyqU4ZcK1e+U3redl+uW3DDn5fJk/Y8IS5kOvJCmAoPNQbvilF5JMzL8UqEM\nv1Qowy8VyvBLhTL8UqEMv1Qowy8VyvBLhTL8UqEMv1Qowy8VyvBLhVoQjTqlcTYHnZ6z+iTNyPBL\nhTL8UqFmDH9EfDUijkfEUxPuWxER2yLiUHW5vNsyJbVtNnv+fwSun3TfZmB7Zl4GbK9uS1pAZgx/\nZv478PKkuzcA91bX7wU+3HJdkjpW9z3/OzPzGEB1eUF7JUnqQ6OmHbMREbcBtwGcydKuVydpluru\n+V+MiAsBqsvjUw20Uac0muqGfyuwqbq+CfhGO+VI6stsPuq7H/hPYE1EHImIjwN/AVwTEYeAa6rb\nkhaQGd/zZ+YtUzzkSfrSAuYZflKhDL9UqM4/6lM/6jb4hIXV5LPJtFybg76Ve36pUIZfKpThlwpl\n+KVCGX6pUIZfKpThlwpl+KVCGX6pUIZfKpThlwpl+KVCGX6pUDbqnMbYFWtqLffG/mdarqRbdWcE\nLqTZgE0spOagNuqUNCPDLxXK8EuFqtuo8wsR8XRE7IuIhyPi3G7LlNS2uo06twHvycxfBw4Cd7Zc\nl6SO1WrUmZmPZ+bp6uYOYGUHtUnqUBvv+W8FHm3h35HUo0bf3hsRdwGngfumGWOjTmkE1Q5/RGwC\nbgLW5zRnCmXmFmALDE7yqbs+Se2qFf6IuB74LPBbmfmzdkuS1Ie6jTr/Djgb2BYReyLiKx3XKall\ndRt13tNBLZJ65Bl+UqEMv1SoBTGlN5Ysqb3OPHmy9rJ11Z0KDAtrOnApzUGb6Ls5qFN6Jc3I8EuF\nMvxSoQy/VCjDLxXK8EuFMvxSoQy/VCjDLxXK8EuFMvxSoQy/VCjDLxVqQczqa6KUGYELaTYg2Bx0\nJnVnA1593Qvs2vtzZ/VJmprhlwpl+KVC1WrUOeGxP42IjIjzuylPUlfqNuokIlYB1wDPt1yTpB7U\natRZ+RLwGcAuPNICVOs9f0TcDPwoM/e2XI+knsy5XVdELAXuAq6d5XgbdUojqM6e/9eAS4C9EfEc\nsBLYHRHvGjY4M7dk5trMXLuY+ifcSGrXnPf8mfkkcMH47eoPwNrM/J8W65LUsbqNOiUtcHUbdU58\nfHVr1UjqjWf4SYUy/FKhep3SGxEvAf89xcPnA6N00HDU6oHRq8l6pjcf9fxqZr5jNgN7Df90ImJX\nZq6d7zrGjVo9MHo1Wc/0Rq2eyXzZLxXK8EuFGqXwb5nvAiYZtXpg9GqynumNWj1vMTLv+SX1a5T2\n/JJ61Hv4I+L6iHgmIg5HxOYhjy+JiK9Vj++MiNUd1rIqIr4dEQciYn9E3D5kzIci4pWI2FP9/FlX\n9UxY53MR8WS1vl1DHo+I+NtqG+2LiKs6rGXNhP/7nog4ERF3TBrT6TYa9m1SEbEiIrZFxKHqcvkU\ny26qxhyKiE0d1vOFiHi6ej4ejohzp1h22ue2V5nZ2w8wBvwQuBQ4A9gLXD5pzB8BX6mubwS+1mE9\nFwJXVdfPBg4OqedDwDd73k7PAedP8/iNwKNAAOuAnT0+fz9m8Flyb9sI+CBwFfDUhPv+EthcXd8M\nfH7IciuAZ6vL5dX15R3Vcy2wqLr++WH1zOa57fOn7z3/1cDhzHw2M08BDwAbJo3ZANxbXX8QWB8R\ns/oe8rnKzGOZubu6/lPgAHBxF+tq2Qbgn3JgB3BuRFzYw3rXAz/MzKlO1OpEDv82qYm/J/cCHx6y\n6HXAtsx8OTP/F9jGkK+ka6OezHw8M09XN3cwmOo+0voO/8XACxNuH+EXw/bmmGpjvgKc13Vh1duL\nK4GdQx5+X0TsjYhHI+KKrmth8NVoj0fEE9WXoUw2m+3YhY3A/VM81vc2emdmHoPBH3EmTDOfYL62\n060MXpkNM9Nz25s5z+dvaNgefPLHDbMZ06qIOAv4OnBHZp6Y9PBuBi9zX42IG4F/BS7rsh7gA5l5\nNCIuALZFxNPV3ubNkocs0/U2OgO4GbhzyMPzsY1mYz62013AaeC+KYbM9Nz2pu89/xFg1YTbK4Gj\nU42JiEXA2xn+BaKtiIjFDIJ/X2Y+NPnxzDyRma9W1x8BFnf9VeWZebS6PA48zODt0kSz2Y5tuwHY\nnZkvTn5gPrYR8OL4W53q8viQMb1up+qA4k3A72X1Bn+yWTy3vek7/N8DLouIS6o9yUZg66QxW4Hx\no7IfAb411YZsqjqWcA9wIDO/OMWYd40fc4iIqxlss590UU+1jmURcfb4dQYHkib3TNgK/EF11H8d\n8Mr4S+AO3cIUL/n73kaVib8nm4BvDBnzGHBtRCyvPg24trqvdRFxPfBZ4ObM/NkUY2bz3Pan7yOM\nDI5UH2Rw1P+u6r4/Z7DRAM4E/gU4DHwXuLTDWn6TwcvAfcCe6udG4BPAJ6oxnwT2M/hkYgfw/o63\nz6XVuvZW6x3fRhNrCuDL1TZ8ksHXqHVZ01IGYX77hPt620YM/ugcA15nsDf/OIPjQNuBQ9Xlimrs\nWuDuCcveWv0uHQY+1mE9hxkcXxj/PRr/xOoi4JHpntv5+vEMP6lQnuEnFcrwS4Uy/FKhDL9UKMMv\nFcrwS4Uy/FKhDL9UqP8H8zJzDMuV3ZMAAAAASUVORK5CYII=\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "plt.imshow(tmat)" ] }, { "cell_type": "code", - "execution_count": 43, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "(15, 9)" - ] - }, - "execution_count": 43, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "nullsp.shape" ] }, { "cell_type": "code", - "execution_count": 69, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -243,23 +193,25 @@ " expansion_mat = nullsp\n", "elif 1:\n", " chosen_indices_and_coeff_ids = [\n", - " (i, cid) for i, cid in enumerate(mpole_expn.get_coefficient_identifiers())\n", + " (i, cid)\n", + " for i, cid in enumerate(mpole_expn.get_coefficient_identifiers())\n", " if cid[0] < 2\n", " ]\n", " chosen_indices = [idx for idx, _ in chosen_indices_and_coeff_ids]\n", - " \n", - " expansion_mat = np.zeros(\n", - " (len(mpole_expn.get_coefficient_identifiers()), len(chosen_indices_and_coeff_ids))\n", - " )\n", + "\n", + " expansion_mat = np.zeros((\n", + " len(mpole_expn.get_coefficient_identifiers()),\n", + " len(chosen_indices_and_coeff_ids),\n", + " ))\n", " for i, (idx, _) in enumerate(chosen_indices_and_coeff_ids):\n", " expansion_mat[idx, i] = 1\n", - " \n", + "\n", " reduction_mat = (nullsp @ la.inv(nullsp[chosen_indices])).T" ] }, { "cell_type": "code", - "execution_count": 70, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ @@ -269,163 +221,84 @@ " plt.scatter(x, y, c=coeffs, **kwargs)\n", " plt.colorbar()\n", "\n", - " for cid, coeff in zip(expn.get_coefficient_identifiers(), coeffs):\n", - " plt.text(cid[0], cid[1]+0.2, \"%.1f\" % coeff)\n" + " for cid, coeff in zip(expn.get_coefficient_identifiers(), coeffs, strict=True):\n", + " plt.text(cid[0], cid[1] + 0.2, f\"{coeff:.1f}\")" ] }, { "cell_type": "code", - "execution_count": 71, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAWgAAAEACAYAAACeQuziAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDIuMS4xLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvAOZPmwAAIABJREFUeJzt3Xt8VOW56PHfk2RIuN+SoBAQFaQI\nxagBTBXrrdWmFqpSodLWitWj4m6r5Wi7PWUXD91bdz3Y5sCpxehGqRtrtSqtKB8QL20tYtDINQoI\nSriGO4FcJjPP+WMGGpNJspK5rTXzfD+f9WFmzZp3PYvJPHnzrvciqooxxhj3yUh2AMYYYyKzBG2M\nMS5lCdoYY1zKErQxxriUJWhjjHEpS9DGGONSnk3QIvIFEfmHiNSLyMw2jrtbRLaIiIpIbiJjNMaY\naHg2QQMHgR8Cj7Rz3N+Bq4BP4x6RMcbEkGcTtKruU9X3AH87x32gqtsTE5UxxsSOZxO0McYkg4gM\nFpE3RGSTiGwQkR/F61xZ8SrYGGNSVCPwE1V9X0R6AmtEZLmqboz1iTxVgxaRGSJSEd4GJjseY0z6\nUdXdqvp++PExYBMwKB7n8lQNWlXnA/OTHYcxxgCIyFDgfODduJSfrNnscnNzdejQoZ1+v9/vZ9Om\nTQQCAUSEjIwMRo0aRWZmJps3b+aMM86gS5cu7Nu3jz179uD3+/H5fPTq1YtozmuMSbw1a9bsV9W8\naMq4+vLueuBgwNn51tZvAOqa7FqgqguaHiMiPYC3gF+q6p+iia01SatBDx06lPLy8mSd3hjjISIS\ndTfZAwcDrF42xNGxmadvrlPVojbi8QEvAM/EKzmDx5o4jDGmsxQIEoy6HBER4Algk6rOjbrANliC\nNsakBUXxq7MmjnZcDHwXWCciFeF9/6qqS2NReFOWoI0xaSMWNWhV/Rsg0UfTPkvQxpi0oCgBjy3x\nZwnaGJM2gqRoghaRTKAc2Kmq1zZ7LRt4GrgQOABMidX8F6rKjhMbqDz6d7KkC6P7XE5+ztBYFG2M\nSSMKBFI1QQM/IjRipleE124FDqnqMBGZCjwMTIk2OFXllV2lbDzyNn5tQBDeO/hnLs+/mXG5k6It\n3hiTZrxWg3Y01FtECoCvA2WtHDIJeCr8+HngynBXlKh8dmJ9ODnXE2pBCtKoDazct5Bj/oPRFm+M\nSSMK+FUdbW7hdC6OXwP3Qau3QAcBOwBUtRE4AvSPNrjKo+/g14YW+zPIZGuNDXIxxjinKAGHm1u0\nm6BF5Fpgn6quaeuwCPtaXKWI3C4i5SJSXl1d3W5wWeJDIhQtImSJr933G2PMKQoBh5tbOKlBXwxM\nFJHtwLPAFSLy+2bHVAGDAUQkC+hNaMWTz1HVBapapKpFeXntD6sf3edyMqVlM7lqkGE9xzkI3Rhj\nQkIjCZ1tbtFuglbVn6lqgaoOBaYCK1X1O80OWwLcHH48OXxM1L+HBuScyWX53yNTfPgkG5/k4JNs\nrhv8U3Iyu0dbvDEmrQgBh5tbdLoftIg8CJSr6hJC49IXicgWQjXnqTGKj/G53+Tc3hPYWrOGTPEx\nvOc4S87GmA4L3SR0T/J1okMJWlXfBN4MP57VZH8d8K1YBtZUT19/Cvt+NV7FG2PSQKgfdAonaGOM\n8bJgKtegjTHGq6wGbYwxLqUIAW8tw2oJ2hiTPqyJwxhjXEgRGjQz2WF0iCVoY0xaCA1U8VYTh7ei\nNcaYKMRqoIqIXCMiH4nIFhH5abzitRq0MSYtqAoBjb5OGp4bfz7wFULTXLwnIktUdWPUhTdjNWhj\nTNoIIo62dowDtqjqJ6raQGiOorhMUG81aGNMWgjdJIxJyjs1vXJYFTA+FgU3ZwnaGJMWOniTMFdE\nmk46v0BVF4QfO5peORYsQRtj0kbAeT/o/apa1Mprp6ZXDisAdkUTV2ssQRtj0kIMRxK+BwwXkTOB\nnYRm77wpFgU3ZwnaGJM2gjHoxaGqjSJyN7AMyASeVNUNURccgSVoY0xaCE2WFJuOa6q6FFgak8La\nYAnaGJMWFMHvsaHeThaNzRGR1SLyoYhsEJHZEY75vohUi0hFePtBfMI1xpjOUYWAZjja3MJJDboe\nuEJVa0TEB/xNRF5V1VXNjvuDqt4d+xCNMSYWHA1CcZV2E3R48dea8FNfeHPRwuTGGNM+BVfVjp1w\nFK2IZIpIBbAPWK6q70Y47AYRWSsiz4vI4AivIyK3i0i5iJRXV1dHEbYxxnRcgAxHm1s4ikRVA6pa\nSKhD9jgRGd3skD8DQ1V1DLACeKqVchaoapGqFuXl5UUTtzHGdIgiBNXZ5hYd+lWhqocJrep9TbP9\nB1S1Pvz0ceDCmERnjDExooBfsxxtbuGkF0eeiPQJP+4KXAVUNjvm9CZPJwKbYhmkMcZEz9lc0G5a\nWNbJr4rTgafCc6BmAM+p6l9E5EGgXFWXAD8UkYlAI3AQ+H68AjbGmM5QYjOSMJGc9OJYC5wfYf+s\nJo9/BvwstqEZY0xsual27IR7GluMMSaOVMVzNWhvRdtEZWUlxcXFZGdn88gjj7R63LRp0xgxYgSj\nR49m+vTp+P3+BEYZW06v+fXXX+eCCy6gsLCQSy65hC1btiQwSmPcKXSTMNPR5haeTdD9+vWjtLSU\nmTNntnnctGnTqKysZN26ddTW1lJWVpagCGPP6TXfeeedPPPMM1RUVHDTTTcxZ86cBEVojJuJ54Z6\nuyeSDsrPz2fs2LH4fL42jyspKUFEEBHGjRtHVVVVgiKMPafXLCIcPXoUgCNHjjBw4MBEhGeMq4Vu\nEnqrH3TatEH7/X4WLVrEb37zm2SHEndlZWWUlJTQtWtXevXqxapVzadNMSY9uWmUoBPeijYKd911\nF5deeikTJkxIdihx9+ijj7J06VKqqqq45ZZbuPfee5MdkjFJl/IjCZNt/vz5FBYWUlhYyK5dzpcA\nmz17NtXV1cydOzeO0cVHR6+5urqaDz/8kPHjQ4sMT5kyhXfeeSfeYRrjCUEyHG1u4akmjhkzZjBj\nxowOvaesrIxly5bx+uuvk5Hhnv94pzp6zX379uXIkSN8/PHHnHPOOSxfvpyRI0fGMUJjvEEV/MH4\n5wAR+RXwDaAB2ArcEp4mo8O8l7HC9uzZQ0FBAXPnzmXOnDkUFBScujFWUlJyqrZ5xx13sHfvXoqL\niyksLOTBBx9MZthRcXLNWVlZPP7449xwww2cd955LFq0iF/96ldJjtyY5As1cWQ42qK0HBgdnjzu\nY6IYxCeh6Z4Tr6ioSMvLy5NybmOMt4jIGlUtiqaM/HP76+Tff83Rsb+98JmozwcgItcBk1V1Wmfe\n76kmDmOM6ayT3ewSbDrwh86+2RK0MSZNdGiod66INP0Tf4GqLjhVksgK4LQI73tAVV8OH/MAoQnk\nnulkwJagjTHpowNrEu5vq4lDVa9q680icjNwLXClRtGObAnaGJMWQr044j/PhohcA9wPfFlVT0RT\nliVoY0xaODlQJQHmAdnAchEBWKWqd3SmoHYTtIjkAG+HT5gFPK+q/9bsmGzgaUJLXR0Apqjq9s4E\nFEl13Wd8dGw1WeLj3N4X08uXG6uiXSmgjaw7/AF76qoYkDOQL/a+kKwM+11qTLQ60MTRaao6LFZl\nOfnW1wNXqGqNiPiAv4nIq6radIKHW4FDqjpMRKYCDwNTYhHg63ue5t0DSwhqAJEMVu5dxNcH3sV5\nfa+IRfGuc8x/lLkf/YJjjUeoD9aRnZFDt6zu/GTEbHr7+iY7PGM8K0m9OKLS7i1NDakJP/WFt+aN\n3pP450rezwNXSrhuH42dJz5m9YE/06gNBAkQUD+N2sAru/4fJxqPRlu8K71QtYhDDfupD9YBUB+s\n40jDIZ777L+SHJkx3peggSox4ygSEckUkQpgH7BcVd9tdsggYAeAqjYCR4D+0Qa34chf8WtDy3jI\nYPOx96It3pXWHi4nQOBz+4IEWX+kgmQNKjImFagKjZrhaHMLR5GoakBVC4ECYJyIjG52SKTacots\nIiK3i0i5iJRXV1d3PFpjjIlCSs9mF57w403gmmYvVQGDAUQkC+hNaHXv5u9foKpFqlqUl5fX7vlG\n9Z6AT7q0jIMgw3uO7UjonnFenyIy+XxXoAwyGN27kBi0GhmTtrw4YX+7CVpE8kSkT/hxV+AqoLLZ\nYUuAm8OPJwMro+mcfdKgbucwrv83yJIuZJBJpvjIki58feBddMvqFW3xrnR9wXfp26U/2Rk5gJCd\nkUNvX19uHHJLskMzxvO8lqCd9OI4HXhKRDIJJfTnVPUvIvIgUK6qS4AngEUisoVQzXlqrAK88rTv\n8cU+l/FxmnSz6+nrxf8a9SvrZmdMjCWwH3TMtPutV9W1wPkR9s9q8rgO+FZsQ/un/Jwh5OcMiVfx\nrpMpWRT2HQukZjOOMcmSiH7QsWTVMmNMWlCFxgRM2B9LlqCNMWkj5Zo4jDEmFaRkG7QxxqQKtQRt\njDHuZDcJjTHGhVStDdoYY1xKCFgvDmOMcSevtUF769eJMcZ0UqLn4hCRmSKiItLpoc9WgzbGpAcN\ntUMngogMBr4CfBZNOVaDNsakjSDiaIuBR4H7iDDtckdYDdoYkxY0QTcJRWQisFNVP4x2imBL0MaY\ntNGBJo5cESlv8nyBqi44+UREVgCnRXjfA8C/Al/tbIxNWYI2xqSNDvTi2K+qRa2Xo1dF2i8iXwTO\nBE7WnguA90VknKru6WC4lqCNMelBNf7d7FR1HZB/8rmIbAeKVHV/Z8qzBG2MSRteG0noZMmrwSLy\nhohsEpENIvKjCMdcJiJHRKQivM2KVJYxxiSTqrMtdufToZ2tPYOzGnQj8BNVfV9EegJrRGS5qm5s\ndtxfVfXazgZijDHxpAhBjw31bjdaVd2tqu+HHx8DNgGD4h2YMcbEmjrc3KJDv05EZCih9QnfjfBy\nsYh8KCKvisioGMRmjDGxE75J6GRzC8c3CUWkB/AC8GNVPdrs5feBM1S1RkRKgJeA4RHKuB24HWDI\nkPRZBNYY4xJuqh474KgGLSI+Qsn5GVX9U/PXVfWoqtaEHy8FfJEmCFHVBapapKpFeXl5UYZujDEd\nk3I1aAn1tn4C2KSqc1s55jRgr6qqiIwjlPgPxDRSY4yJggLBoHuSrxNOmjguBr4LrBORivC+fwWG\nAKjqY8Bk4E4RaQRqgamqiZo3yhhjHFDARbVjJ9pN0Kr6N2h7eidVnQfMi1VQxhgTD16rNtpIQmNM\n+vBYgvZWr+2w1157jREjRjBs2DAeeuihVo97/vnnERHKy8tbPcZLKisrKS4uJjs7m0ceeaTV46ZN\nm8aIESMYPXo006dPx+/3JzDK2HJ6zdu2bWP8+PEMHz6cKVOm0NDQkMAojTc4u0HoppuEnkvQgUCA\nGTNm8Oqrr7Jx40YWL17Mxo3NBzXCsWPHKC0tZfz48UmIMj769etHaWkpM2fObPO4adOmUVlZybp1\n66itraWsrCxBEcae02u+//77ueeee9i8eTN9+/bliSeeSFCExlM8NlLFcwl69erVDBs2jLPOOosu\nXbowdepUXn755RbH/fznP+e+++4jJycnCVHGR35+PmPHjsXn87V5XElJCSKCiDBu3DiqqqoSFGHs\nOblmVWXlypVMnjwZgJtvvpmXXnopUSEar1DQoDja3MJzCXrnzp0MHjz41POCggJ27tz5uWM++OAD\nduzYwbXXpvfUIH6/n0WLFnHNNdckO5S4OnDgAH369CErK3RLJdLPhDEh4nBzB8/dJIzUe6/psjLB\nYJB77rmHhQsXJjAqd7rrrru49NJLmTBhQrJDiav2fiaMOcVFzRdOeK4GXVBQwI4dO049r6qqYuDA\ngaeeHzt2jPXr13PZZZcxdOhQVq1axcSJEz17o3D+/PkUFhZSWFjIrl27HL9v9uzZVFdXM3duxLFF\nrtbRa87NzeXw4cM0NjYCLX8mjDnF2qDja+zYsWzevJlt27bR0NDAs88+y8SJE0+93rt3b/bv38/2\n7dvZvn07F110EUuWLKGoqNXVa1xtxowZVFRUUFFR4TjplJWVsWzZMhYvXkxGhuc+4g5fs4hw+eWX\n8/zzzwPw1FNPMWnSpHiHabzm5EAVJ5tLeO7bm5WVxbx587j66qsZOXIkN954I6NGjWLWrFksWbIk\n2eHF1Z49eygoKGDu3LnMmTOHgoICjh4NzVtVUlJyqrZ5xx13sHfvXoqLiyksLOTBBx9MZthRcXrN\nDz/8MHPnzmXYsGEcOHCAW2+9NZlhG5dK9IT90ZJkjcguKipSrzY7GGMSS0TWtLWIqxPZQwv0tP/V\nYkGoiD677b6ozici/wLcTWjBk1dU9b7OlOO5m4TGGNNZkoD6qIhcDkwCxqhqvYjkt/ee1liCNsak\nh8TdALwTeEhV6wFUdV9nC/JcG7QxxnSOwxuE0d8kPAeYICLvishbIjK2swVZDdoYkz6c16BzRaTp\nTbIFqrrg5BMRWQGcFuF9DxDKq32Bi4CxwHMiclZnpmC2BG2MSR9Bx0fub+smoape1dprInIn8Kdw\nQl4tIkEgF6juQKSAgyYOERksIm+IyCYR2SAiLW6DSkipiGwRkbUickFHA2lLfeNedh/7I3trXqYx\n0Hw5xNSjqnx6fD3lB19lW83aiCPlUk194Dgbj7zBh4de46i/wz/HxrQvcf2gXwKuABCRc4AuwP7O\nFOSkBt0I/ERV3xeRnsAaEVmuqk2nkPsaoUVihwPjgd+G/43ajiP/xfbDc4EMBOFjZnFu3q/p3+3y\nWBTvOvWBEzy97QEONOwkqEEyJIM+vgHcfNZ/0DWzR7LDi4vtNR/w4o4HERFUgwRRvpT7bYrzpiY7\nNJNiEtGLA3gSeFJE1gMNwM2dXWGq3Rq0qu5W1ffDj48Bm4BBzQ6bBDytIauAPiJyemcCaqqmoZLt\nhx8lqPUEtZaAniCotWys/jGNwWPRFu9Ky/f8F/vqP6UhWEejNtAQrGN//U5e3fW7ZIcWFw3BOl6s\n+t/4tY6GYC1+rSegDfxj/7Psrv0o2eGZVJOAod6q2qCq31HV0ap6gaqu7GxZHerFISJDgfOBd5u9\nNAjY0eR5FS2TeIftq1lCUFtOvC4IB068EW3xrrTuyFsEtPFz+4I0suno31OyqWN7zRokwuxhAW1g\n3eHlSYjIGPdwnKBFpAfwAvBjVW3eEByp0aZFNhGR20WkXETKq6vbb2cMJeeWSUnRiIk7FQQ10Mr+\nIK6axSVGAupHW/mMA8HU/IxN8og629zCUYIWER+h5PyMqv4pwiFVwOAmzwuAFtOQqeoCVS1S1aK8\nvLx2z5vb/atkSMsJ91UD9Ot6qZPQPWdYjwuQZh+LIJzZfQwiqddtfWj3Cwg2+4sBwCc5jOidmp+x\nSRIFguJscwknvTgEeALYpKqtzV25BPheuDfHRcARVd0dbXC9s8eS372EDOlKqJKeSYbkcFbfmWRn\ndXr0pKtdc/rtdMvqhU+yAfBJNl0ze/L1QXclObL46JrViysH3EGWdEHIBASf5HB2z/Gc2f3CZIdn\nUo3Hpht10ovjYuC7wDoRqQjv+1dgCICqPgYsBUqALcAJ4JZYBCcinNP/3xnQ43r2H19GhmST32Mi\nPbqMiEXxrtS7Sx7/Mvx3rDv8FnvqPiE/5wzG9Lmc7MxuyQ4tbgr7lTC4+2g2HH6dhmAdw3sVM6Tb\neTbpvok5NzVfONFuglbVv9HOGjDhLiQzYhVUUyJCn5yx9Mnp9GhJz+mS2ZUL+6f2MlXN9c8ewqUD\nYvJ73ZjWpVqCNsaYlGEJ2hhj3MdtPTScsARtjEkfLuqh4YQlaGNM2rAatDHGuJUlaGOMcSFrgzbG\nGBezBG2MMe4kzifsd4XUm9zBGGNShNWgjTHpw2NNHFaDNsakB4dTjUZ7I1FECkVklYhUhKdXHtfZ\nsixBG2PSR2Jms/tPYLaqFgKzws87xZo4jDHpIzFNHAr0Cj/uTYS58Z2yBG2MSQtCwnpx/BhYJiKP\nEGql+FJnC7IEbYxJDx1rX84VkfImzxeo6oKTT0RkBXBahPc9AFwJ3KOqL4jIjYQWPLmqMyFbgjbG\npA/nCXq/qha1WoxqqwlXRJ4GfhR++kegzPFZm3Gy5NWTIrJPRNa38vplInIkfMeyQkRmdTYYY4yJ\nq8TcJNwFfDn8+Apgc2cLclKDXgjMA55u45i/quq1nQ3CGGMSIUFzcdwG/EZEsoA64PbOFuRkyau3\nRWRoZ09gjDGukYAEHV4mMCYrHseqH3SxiHwoIq+KyKgYlWmMMbGjoV4cTja3iMVNwveBM1S1RkRK\ngJeA4ZEOFJHbCVf3hwwZEoNTG2NMB6TbUG9VPaqqNeHHSwGfiOS2cuwCVS1S1aK8vLxoT22MMR2S\niKHesRR1ghaR00REwo/Hhcs8EG25xhgTc4npxREz7TZxiMhi4DJCHbergH8DfACq+hgwGbhTRBqB\nWmCqqrroEo0xBtclXyec9OL4djuvzyPUDc8YY1xLcFfzhRM2ktAYkza8lqA9O91oZWUlxcXFZGdn\n88gjj7R63LZt2xg/fjzDhw9nypQpNDQ0JDDK2HJ6zarKAw88wDnnnMPIkSMpLS1NYJSx5fSap02b\nxogRIxg9ejTTp0/H7/cnMMrYcXq9r7/+OhdccAGFhYVccsklbNmyJYFRepjH2qA9m6D79etHaWkp\nM2fObPO4+++/n3vuuYfNmzfTt29fnnjiiQRFGHtOr3nhwoXs2LGDyspKNm3axNSpUxMUYew5veZp\n06ZRWVnJunXrqK2tpays09MfJJXT673zzjt55plnqKio4KabbmLOnDkJitDjLEEnRn5+PmPHjsXn\n87V6jKqycuVKJk+eDMDNN9/MSy+9lKgQY87JNQP89re/ZdasWWRkZJx6n1c5veaSkhJEBBFh3Lhx\nVFVVJSjC2HJ6vSLC0aNHAThy5AgDBw5MRHjelqAVVWIppdugDxw4QJ8+fcjKCl1mQUEBO3fuTHJU\n8bd161b+8Ic/8OKLL5KXl0dpaSnDh0ccO5Ry/H4/ixYt4je/+U2yQ4mrsrIySkpK6Nq1K7169WLV\nqlXJDskbXJR8nfBsDdqJSL39wl22U1p9fT05OTmUl5dz2223MX369GSHlDB33XUXl156KRMmTEh2\nKHH16KOPsnTpUqqqqrjlllu49957kx2SJ3htqLenEvT8+fMpLCyksLCQXbvaX0UmNzeXw4cP09jY\nCEBVVZXn/hTs6DVD6C+FG264AYDrrruOtWvXxjPEmOvMNQPMnj2b6upq5s6dG8foYq+j11tdXc2H\nH37I+PHjAZgyZQrvvPNOvMNMCV5r4vBUgp4xYwYVFRVUVFQ4SrQiwuWXX87zzz8PwFNPPcWkSZPi\nHWZMdfSaAb75zW+ycuVKAN566y3OOeeceIYYc5255rKyMpYtW8bixYtPtb17RUevt2/fvhw5coSP\nP/4YgOXLlzNy5Mh4h+l9Tm8QuihBS7IG/RUVFWl5eXn7B7Ziz549FBUVcfToUTIyMujRowcbN26k\nV69elJSUUFZWxsCBA/nkk0+YOnUqBw8e5Pzzz+f3v/892dnZMbySxHF6zYcPH2batGl89tln9OjR\ng8cee4zzzjsv2eF3itNrzsrK4owzzqBnz54AXH/99cya5b21I5xe74svvnjqRnDfvn158sknOeus\ns5IdftyIyJq2VjhxolveYP3C9c6agj5YcG/U54sFzyZoY0z6iEWC7p43WL9wnbME/f7j7kjQKd2L\nwxhjmpKgi9ovHPBWY50xxnRWgtqgReRbIrJBRIIiUtTstZ+JyBYR+UhErm6vLKtBG2PSRoJ6aKwH\nrgd+97lzi5wLTAVGAQOBFSJyjqoGWivIatDGmPSRgBq0qm5S1Y8ivDQJeFZV61V1G7AFGNdWWZag\njTFpI8n9oAcBO5o8rwrva1W7CVpEnhSRfSKyvpXXRURKw+0qa0Xkgg6F7EB94Dibj/2DT2rKaQx6\ndza6jthTV8WHh1exu/azZIeSEAFtZOuxD6g8uoq6QE2yw0mIIw2HqDi0mq01HxFUFw1fS2XOa9C5\nIlLeZLu9aTEiskJE1kfY2hpoEWkYc5u/Dpy0QS8kNCH/0628/jVCi8QOB8YDvw3/GxMbDr/Ost3/\nlwzJBEAQrh/8CwZ3Hx2rU7iKP9jAwm2P8MnxTWRIJkENMKTbMKafeT/ZmTnJDi8uqk58xOJPZxPU\nACAEtJGrT7+NC/u1ew/Fk1SVF3c+w1+rV5AlWShKj6ye/MvwB+ifbWt1xo12aBj3/ra62anqVZ2I\noAoY3OR5AdDm0NF2a9Cq+jZwsI1DJgFPa8gqoI+InO4g2HYdrK9i2e5SGrWehuAJGoInqA8e54Ud\ns2gI1sXiFK6zdPdith7fiF8bqA/W4tcGPj2xmT/vau33o7c1Bv389/ZfUBuooT5YS33wBI3awLLd\nj7O3bluyw4uLisOr+fv+lTSqn7pgLfXBOg427GfBVm8NUfeakyuqJLGJYwkwVUSyReRMQpXa1W29\nIRZt0B1uV3Fq/eEV4VrV56nC1mPvxuIUrvPewTdp1M9PNt+ofsoPvR1x8iev21rzAUFaVmsC2sgH\nB1ckIaL4e2vfMhqC9Z/bpyjV9XvYV7cnSVGlCVVnWxRE5Lrw+q3FwCsisix0at0APAdsBF4DZrTV\ngwNi083OcbtKuB3ndoAhQ4a0W3Bd8DhBIiRogjQEazsWpUf4tT7i/oA2oigS8b/buxqCJyJ+IZQg\ndcHUbIuua+VnN0MyqE/Rn2u3SEQ3O1V9EXixldd+CfzSaVmxqEE7bldR1QWqWqSqRXl57be1De95\nET5p2e6qBBna/fxOhutuZ3cfFTEJD+0+ggxJvU43Q7uPIRDhl7AvI4cv9CpOQkTxV9hnHFnSckL+\nDMlgYNfBEd5hYsKDkyXF4hu/BPheuDfHRcARVd0dg3IZ2v18zuhe2CRJCz7JZlz/yfTuMiAWp3Cd\n6wpuISez66kvcJb4yMnoyvWDbk1yZPHR09ePL+dNxSfZnPxjzCc5DO76Bc7pOTa5wcXJl/Ovpl+X\nXLpkhCbtyiADn3ThpiG3kSk2diyevDYfdLs/DSKyGLiMULeTKuDfAB+Aqj4GLAVKCHW6PgHcEqvg\nRDK4bvDP2XzsH2w88iZZGdl47SUqAAAPGElEQVSM6fNVhnQfE6tTuE5e9kDuG/FrVh1YwY7arQzM\nOYMv5X6VXr6+yQ4tbi7J/xZDuo/ig0PLaQjWcm7vSxjZq/hUz51U0zWzG/eP/CWrD/yVjUc/pE+X\n/kzIvYrTuxYkO7SU56bk64TNZmeMcb1YzGbXo+9gPe/KHzk69p0X/qfNZmeMMYnkptVSnLAEbYxJ\nH5agjTHGfU4OVPESS9DGmPSg6rkJ+y1BG2PSh7fysyVoY0z6sCYOY4xxIwWsicMYY1zKW/nZErQx\nJn1YE4cxxriU9eIwxhg3ctlMdU5YgjbGpIXQQBVvZWhL0MaY9OGx2exSbwZ4Y4xphag62qI6h8i3\nRGSDiARFpKjJ/q+IyBoRWRf+94r2yrIatDEmPSSuDXo9cD3wu2b79wPfUNVdIjIaWEY767dagjbG\npInEzMWhqpsARKT5/g+aPN0A5IhItmorC5HisIlDRK4RkY9EZIuI/DTC698XkWoRqQhvP3B0JcYY\nk0jOV/XOFZHyJtvtMY7kBuCDtpIzOFvyKhOYD3yF0AKx74nIElXd2OzQP6jq3Z2N1hhj4ko7tOTV\n/rZWVBGRFcBpEV56QFVfbqtgERkFPAx8tb0gnDRxjAO2qOon4cKfBSYBzRO0Mca4W4y62anqVZ15\nn4gUAC8C31PVre0d76SJYxCwo8nzKiI3bN8gImtF5HkRsbXjjTHuow63OBCRPsArwM9U9e9O3uMk\nQUuEfc0v4c/AUFUdA6wAnmolwNtPtulUV1c7ic8YY2JGgkFHW1TnELlORKqAYuAVEVkWfuluYBjw\n8yb36/LbKstJE0cV0LRGXADsanqAqh5o8vRxQu0rLajqAmABhFb1dnBuY4yJDSUhA1VU9UVCzRjN\n988B5nSkLCc16PeA4SJypoh0AaYCS5oeICKnN3k6EdjUkSCMMSbeBGeDVNw0HLzdGrSqNorI3YQ6\nVWcCT6rqBhF5EChX1SXAD0VkItAIHAS+H8eYjTGmc1yUfJ1wNFBFVZcCS5vtm9Xk8c+An8U2NGOM\nibFUTNDGGON5CWqDjiVL0MaYtBFtD41E8+Rsdq+99hojRoxg2LBhPPTQQy1eX7hwIXl5eRQWFlJY\nWEhZWVkSooy9yspKiouLyc7O5pFHHmn1uHnz5jFs2DBEhP379ycwwthp7zN+7LHH+OIXv0hhYSGX\nXHIJGzemxrgpp5/xtGnTGDFiBKNHj2b69On4/f4ERhlbTq9ZQn4pIh+LyCYR+WHHzuRwmLeLmkE8\nl6ADgQAzZszg1VdfZePGjSxevDjil3PKlClUVFRQUVHBD36QGlOD9OvXj9LSUmbOnNnmcRdffDEr\nVqzgjDPOSFBkseXkM77ppptYt24dFRUV3Hfffdx7771Jija2nH7G06ZNo7KyknXr1lFbW+vpSojT\naybU+WAw8AVVHQk826ETKZag42316tUMGzaMs846iy5dujB16lRefrnNoe8pIz8/n7Fjx+Lz+do8\n7vzzz2fo0KGJCSoOnHzGvXr1OvX4+PHjLWYO8yqnn3FJSQkigogwbtw4qqqqEhRh7Dm9ZuBO4EFV\nDQKo6r4OnyzocHMJzyXonTt3MnjwP8fNFBQUsHPnzhbHvfDCC4wZM4bJkyezY8eOFq8b93L6Gc+f\nP5+zzz6b++67j9LS0kSG6Bp+v59FixZxzTXXJDuURDgbmBIejfyqiAzvaAFe6wftuQStEf7zmtee\nvvGNb7B9+3bWrl3LVVddxc0335yo8EwMOPmMAWbMmMHWrVt5+OGHmTOnQwO0UsZdd93FpZdeyoQJ\nE5IdSiJkA3XhWeYeB57scAnWxBFfBQUFn6sRV1VVMXDgwM8d079/f7KzswG47bbbWLNmTUJjjKX5\n8+efutm5a9eu9t+QApx8xk1NnTqVl156KRGhxUVnP+PZs2dTXV3N3Llz4xhdfHTymquAF8KPXwTG\ndOikqhAIOttcwnMJeuzYsWzevJlt27bR0NDAs88+y8SJEz93zO7du089XrJkCSNHjkx0mDEzY8aM\nUzc720pSqcTJZ7x58+ZTj1955RWGD+/wX7uu0ZnPuKysjGXLlrF48WIyMjz3Ne7sz/VLwMl1/L4M\nfNzhE1sNOr6ysrKYN28eV199NSNHjuTGG29k1KhRzJo1iyVLQlOElJaWMmrUKM477zxKS0tZuHBh\ncoOOkT179lBQUMDcuXOZM2cOBQUFHD16FAjdNDpZEyktLaWgoICqqirGjBnjuV4sTj7jefPmMWrU\nKAoLC5k7dy5PPRVxAkXPcfoZ33HHHezdu5fi4mIKCwt58MEHkxl2VJxeM/AQoWmN1wH/AXT8B9tj\nCVoitfclQlFRkZaXlyfl3MYYbxGRNW2tcOJE7+zT9EuDvuPo2Ne2/Z+ozxcLNpLQGJMmFNQ97ctO\nWII2xqQHxVU3AJ3wXBu0McZ0WgLaoEXkWyKyQUSCItKimUREhohIjYi0O3TSErQxJn0k5ibheuB6\n4O1WXn8UeNVJQY4StIhcIyIficgWEflphNezReQP4dffFZGhTsp1KqgNHK1bQ039OtRjbUidVeM/\nyGfH13HM783JjjpKVTlWv5HDdWsIakOyw0mI+sAJPj2+nv313h2m3VHV9bv5pGYjtYHjSTh7YiZL\nUtVNqvpRpNdE5JvAJ8AGJ2W12wYtIpnAfOArhDqKvyciS1S16ew1twKHVHWYiEwltCbhFCcBtOfg\niZVs3n8PoChBsjJ6MTK/jO5dzo1F8a4T0EaW7vo1m47+lSzx0ah+hve4iIkFM8mUducq8KSahs2s\n3fs/8AcPcbLOMDL338nvfnVyA4ujd6r/xJv7/ptMySKgjeTnDGHqGT+nR1bfZIcWF8cbj/HUtofZ\nWbuNTMmiUf1clv9NvjLgW4mbR0WBJE43KiLdgfsJ5dJ2mzfAWQ16HLBFVT9R1QZCM0hNanbMJP65\nkvfzwJUSg//1usadfLz/bgJ6jIDWENQTNAT2sGHvdwhqfbTFu9LfqxdTefTvBNRPffAEAfWzpeZd\n3ty7MNmhxUVQ/Xyw52bqAjsJ6AkCWkNAa9i4/z5O+LclO7y42HysnLf2LaZRG6gPnqBRG9hTu43n\nPv2PZIcWN898+ig7TmzBrw3UBU/QqH7e3reEdUdWJTYQ5zXo3PCcHye325sWIyIrRGR9hK15bmxq\nNvCoqtY4DddJL45BQNPZhqqA8a0dE17D8AjQH4jq7/N9NX9ENdBif1D9HKp9g/7dUm+CmDUH/0Jj\ns18+jdrAB4de5YoBP0iZWdtOOlT7DkGta7E/qI3sPPYcw/vdn4So4mvV/pfxN/uMgwTYU/cJhxv2\n0qfLgCRFFh/H/IfZfrySAJ//LjdoPW9V/5kxfYoTFIl2pBfH/rb6QavqVZ0IYDwwWUT+E+gDBEWk\nTlXntfYGJwk6UkZo3kjj5BjCv4VuBxgyZEi7J/YHDqBEmog8iD9wqN33e1FD8ETE/aEvtBL5v9q7\n/MHDRPhRARppCKRm+/vxxiMR92dIJicCx+hDaiXo2sBxMiQTtOV3+UTj0cQFoiT1HpaqnprRSkR+\nAdS0lZzBWRNHFaFJsk8qAJrPbnLqGBHJAnoTWt27eYALVLVIVYvy8vLaPXGfrhPIkG4t9itBeuc0\nr8SnhkHdvhBx/2k5ZyOSep1ueucUobT8KylTutG/65eTEFH8De95Yav3E/Kz26+4eE3/7NPIlMwW\n+zPIZETP8xMbTFCdbVEQketEpAooBl4RkWWdLcvJN/49YLiInCkiXYCpwJJmxywBTs7pORlYqTEY\nQ96v65V07zKKDOn6z4ClG/ndr6er76xoi3elr5x2B76MHDII/UALmfgkh6tPn5HkyOKja9YgBvX8\ndrPPuCvdfGen7E3C4tzr6JbZi6xTSVrwSTZXn/YDsjK6JDW2eMiUTK4bdBs+6YKE/wLMFB/dsnpw\nxYDrExtMYnpxvKiqBaqaraoDVLXFD7Kq/kJVW1/fK6zdJo5wm/LdwDIgE3hSVTeIyINAuaouAZ4A\nFonIFkI156kdvahIRDIZNWAR+479keoTL5MhOQzoMZX+3b4Wi+JdaUDO2dx61nzePfACe2q3MCDn\nLMb1v57+2QXJDi1uhvX9KX1yxrLz6GICeoIB3a/l9B6TyUjRXivdsnpxx7BSVh/4C1tq1tDLl8v4\n/hMZ0j01eyYBFPa9mH7Z+by9788catjP8J5f5JK8Enpk9U5cEKpJ7cXRGTZZkjHG9WIyWVJmrhZ3\n/4ajY5cdW2iTJRljTOIoGmh5v8PNLEEbY9KDEvUNwESzBG2MSR8emyrCErQxJi0ooFaDNsYYF1Kb\nsN8YY1zLazcJk9bNTkSqgU87+LZcopzfw4PS7ZrT7Xoh/a65M9d7hqq2P/y4DSLyWvjcTuxX1aRP\n9pO0BN0ZIlLuhr6JiZRu15xu1wvpd83pdr3RSL3JHYwxJkVYgjbGGJfyWoJekOwAkiDdrjndrhfS\n75rT7Xo7zVNt0MYYk068VoM2xpi04YkE3d6q4qlGRJ4UkX0isj7ZsSSKiAwWkTdEZJOIbBCRHyU7\npngSkRwRWS0iH4avd3ayY0oUEckUkQ9E5C/JjsXtXJ+gm6wq/jXgXODbIpK6E+eGLASS3gczwRqB\nn6jqSOAiYEaKf871wBWqeh5QCFwjIhclOaZE+RGwKdlBeIHrEzTOVhVPKar6NhGWDEtlqrpbVd8P\nPz5G6As8KLlRxY+GnFzd2RfeUv6GkIgUAF8HypIdixd4IUFHWlU8Zb+4BkRkKHA+8G5yI4mv8J/6\nFcA+YLmqpvT1hv0auA/w1qQYSeKFBO1oxXCTGkSkB/AC8GNVTeCSz4mnqgFVLSS0EPM4ERmd7Jji\nSUSuBfap6ppkx+IVXkjQTlYVNylARHyEkvMzqvqnZMeTKKp6GHiT1L/vcDEwUUS2E2qqvEJEfp/c\nkNzNCwnayarixuNERAgtPrxJVecmO554E5E8EekTftwVuAqoTG5U8aWqPwuvdj2U0Pd4pap+J8lh\nuZrrE7SqNgInVxXfBDynqhuSG1V8ichi4B/ACBGpEpFbkx1TAlwMfJdQraoivJUkO6g4Oh14Q0TW\nEqqELFdV63ZmPsdGEhpjjEu5vgZtjDHpyhK0Mca4lCVoY4xxKUvQxhjjUpagjTHGpSxBG2OMS1mC\nNsYYl7IEbYwxLvX/AcRnoY4TI7qLAAAAAElFTkSuQmCC\n", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ "proj_mexp = mexp.with_coeffs(expansion_mat @ reduction_mat @ mexp.coeffs)\n", "\n", "proj_resid = proj_mexp.coeffs - mexp.coeffs\n", "\n", - "plot_coeffs(mpole_expn, np.log10(1e-15+np.abs(proj_resid)), vmin=-15, vmax=2)" + "plot_coeffs(mpole_expn, np.log10(1e-15 + np.abs(proj_resid)), vmin=-15, vmax=2)" ] }, { "cell_type": "code", - "execution_count": 72, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "2.80866677486e-15\n" - ] - } - ], + "outputs": [], "source": [ - "print(t.l_inf(proj_mexp - mexp, 1.2, center=[3,0]))" + "print(t.l_inf(proj_mexp - mexp, 1.2, center=[3, 0]))" ] }, { "cell_type": "code", - "execution_count": 73, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.0116062369243\n" - ] - } - ], + "outputs": [], "source": [ "trans_unproj = t.multipole_expand(mexp, new_center)\n", "trans_proj = t.multipole_expand(proj_mexp, new_center)\n", "\n", - "print(t.l_inf(trans_unproj - trans_proj, 1.2, center=[3,0]))" + "print(t.l_inf(trans_unproj - trans_proj, 1.2, center=[3, 0]))" ] }, { "cell_type": "code", - "execution_count": 74, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "[-3.07295099 -0.08541702 -1.62768408 -2.17149444 -0.06559717 -2.66984178\n", - " -0.05931738 -1.14908652 -0.08143883 -1.25881949 -0.02604215 -0.04555359\n", - " -0.40284643 -0.05019419 -0.39528495]\n" - ] - } - ], + "outputs": [], "source": [ "print(trans_proj.coeffs - trans_unproj.coeffs)" ] }, { "cell_type": "code", - "execution_count": 75, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.1169116976203841" - ] - }, - "execution_count": 75, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "la.norm(reduction_mat @ (trans_proj.coeffs - trans_unproj.coeffs))" ] }, { "cell_type": "code", - "execution_count": 76, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.011804658035654577" - ] - }, - "execution_count": 76, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "t.l_inf(trans_unproj - pt_src, 1.2, center=[3, 0])" ] }, { "cell_type": "code", - "execution_count": 77, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "data": { - "text/plain": [ - "0.00029429326299543407" - ] - }, - "execution_count": 77, - "metadata": {}, - "output_type": "execute_result" - } - ], + "outputs": [], "source": [ "t.l_inf(mexp - pt_src, 1.2, center=[3, 0])" ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true - }, - "outputs": [], - "source": [] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -439,9 +312,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.4+" + "version": "3.12.7" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 4 } diff --git a/contrib/translations/PDE-reduction-symbolic.ipynb b/contrib/translations/PDE-reduction-symbolic.ipynb index da9c37a9b..29a806826 100644 --- a/contrib/translations/PDE-reduction-symbolic.ipynb +++ b/contrib/translations/PDE-reduction-symbolic.ipynb @@ -6,21 +6,22 @@ "metadata": {}, "outputs": [], "source": [ - "import pyopencl as cl\n", - "import sumpy.toys as t\n", - "import numpy as np\n", - "import numpy.linalg as la\n", - "import matplotlib.pyplot as plt\n", - "from sumpy.visualization import FieldPlotter\n", - "from pytools import add_tuples\n", + "from __future__ import annotations\n", "\n", "from sumpy.expansion.local import VolumeTaylorLocalExpansion\n", - "from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansion, LinearPDEConformingVolumeTaylorMultipoleExpansion\n", - " \n", - "from sumpy.kernel import (YukawaKernel, HelmholtzKernel, LaplaceKernel)\n", - "\n", + "from sumpy.expansion.multipole import (\n", + " LaplaceConformingVolumeTaylorMultipoleExpansion,\n", + " LinearPDEConformingVolumeTaylorMultipoleExpansion,\n", + " VolumeTaylorMultipoleExpansion,\n", + ")\n", + "from sumpy.kernel import ( # ruff:ignore[unused-import]\n", + " HelmholtzKernel,\n", + " LaplaceKernel,\n", + " YukawaKernel,\n", + ")\n", "from sumpy.symbolic import make_sym_vector\n", "\n", + "\n", "order = 2\n", "dim = 2\n", "\n", @@ -28,16 +29,16 @@ " knl = LaplaceKernel(dim)\n", " extra_kernel_kwargs = {}\n", " mpole_expn_reduced_class = LaplaceConformingVolumeTaylorMultipoleExpansion\n", - " \n", + "\n", "else:\n", " helm_k = 1.2\n", " knl = HelmholtzKernel(dim)\n", - " extra_kernel_kwargs={\"k\": helm_k}\n", + " extra_kernel_kwargs = {\"k\": helm_k}\n", " mpole_expn_reduced_class = LinearPDEConformingVolumeTaylorMultipoleExpansion\n", "\n", "mpole_expn_reduced = mpole_expn_reduced_class(knl, order)\n", "mpole_expn = VolumeTaylorMultipoleExpansion(knl, order)\n", - "local_expn = VolumeTaylorLocalExpansion(knl, order)\n" + "local_expn = VolumeTaylorLocalExpansion(knl, order)" ] }, { @@ -49,8 +50,12 @@ "reduced_wrangler = mpole_expn_reduced.expansion_terms_wrangler\n", "full_wrangler = mpole_expn.expansion_terms_wrangler\n", "\n", - "reduced_derivatives = list(make_sym_vector(\"deriv\", len(reduced_wrangler.stored_identifiers)))\n", - "full_derivatives = reduced_wrangler.get_full_kernel_derivatives_from_stored(reduced_derivatives, 1)\n", + "reduced_derivatives = list(\n", + " make_sym_vector(\"deriv\", len(reduced_wrangler.stored_identifiers))\n", + ")\n", + "full_derivatives = reduced_wrangler.get_full_kernel_derivatives_from_stored(\n", + " reduced_derivatives, 1\n", + ")\n", "\n", "print(reduced_derivatives)\n", "print(full_derivatives)" @@ -62,9 +67,13 @@ "metadata": {}, "outputs": [], "source": [ - "full_coeffs = list(make_sym_vector(\"coeff\", len(reduced_wrangler.get_full_coefficient_identifiers())))\n", + "full_coeffs = list(\n", + " make_sym_vector(\"coeff\", len(reduced_wrangler.get_full_coefficient_identifiers()))\n", + ")\n", "\n", - "reduced_coeffs = reduced_wrangler.get_stored_mpole_coefficients_from_full(full_mpole_coefficients=full_coeffs, rscale=1)\n", + "reduced_coeffs = reduced_wrangler.get_stored_mpole_coefficients_from_full(\n", + " full_mpole_coefficients=full_coeffs, rscale=1\n", + ")\n", "\n", "print(full_coeffs)\n", "print(reduced_coeffs)" @@ -77,7 +86,9 @@ "outputs": [], "source": [ "dvec = make_sym_vector(\"d\", dim)\n", - "translated_reduce_coeffs = mpole_expn_reduced.translate_from(mpole_expn_reduced, reduced_coeffs, 1, dvec, 1)\n", + "translated_reduce_coeffs = mpole_expn_reduced.translate_from(\n", + " mpole_expn_reduced, reduced_coeffs, 1, dvec, 1\n", + ")\n", "translated_full_coeffs = mpole_expn.translate_from(mpole_expn, full_coeffs, 1, dvec, 1)\n", "translated_full_coeffs" ] @@ -88,10 +99,12 @@ "metadata": {}, "outputs": [], "source": [ - "eval_reduced = sum(a*b for a, b in zip(translated_reduce_coeffs, reduced_derivatives))\n", - "eval_full = sum(a*b for a, b in zip(translated_full_coeffs, full_derivatives))\n", + "eval_reduced = sum(a * b for a, b in zip(translated_reduce_coeffs, reduced_derivatives,\n", + " strict=True))\n", + "eval_full = sum(a * b for a, b in zip(translated_full_coeffs, full_derivatives,\n", + " strict=True))\n", "\n", - "(eval_full-eval_reduced).simplify()" + "(eval_full - eval_reduced).simplify()" ] } ], @@ -111,7 +124,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.4" + "version": "3.12.7" } }, "nbformat": 4, diff --git a/doc/codegen.rst b/doc/codegen.rst index 2d5d6f4dc..cf872592d 100644 --- a/doc/codegen.rst +++ b/doc/codegen.rst @@ -4,3 +4,27 @@ Code Generation .. automodule:: sumpy.codegen .. automodule:: sumpy.assignment_collection .. automodule:: sumpy.cse + +References +---------- + +This is only here because Sphinx (the documentation tool) fails to resolve these +references properly. + +.. currentmodule:: lp + +.. class:: TranslationUnit + + See :class:`loopy.TranslationUnit`. + +.. currentmodule:: sp + +.. class:: Expr + + See :class:`sympy.core.expr.Expr`. + +.. currentmodule:: np + +.. class:: ndarray + + See :class:`numpy.ndarray`. diff --git a/doc/conf.py b/doc/conf.py index 5af7433cc..e21a5e1f7 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -1,29 +1,78 @@ -import os +from importlib import metadata from urllib.request import urlopen -_conf_url = \ - "https://raw.githubusercontent.com/inducer/sphinxconfig/main/sphinxconfig.py" + +_conf_url = "https://raw.githubusercontent.com/inducer/sphinxconfig/main/sphinxconfig.py" with urlopen(_conf_url) as _inf: exec(compile(_inf.read(), _conf_url, "exec"), globals()) copyright = "2016-21, sumpy contributors" - -os.environ["AKPYTHON_EXEC_FROM_WITHIN_WITHIN_SETUP_PY"] = "1" -ver_dic = {} -exec(compile(open("../sumpy/version.py").read(), "../sumpy/version.py", "exec"), - ver_dic) -version = ".".join(str(x) for x in ver_dic["VERSION"]) -release = ver_dic["VERSION_TEXT"] +release = metadata.version("sumpy") +version = ".".join(release.split(".")[:2]) intersphinx_mapping = { - "https://docs.python.org/3/": None, - "https://numpy.org/doc/stable/": None, - "https://documen.tician.de/modepy/": None, - "https://documen.tician.de/pyopencl/": None, - "https://documen.tician.de/pymbolic/": None, - "https://documen.tician.de/loopy/": None, - "https://documen.tician.de/pytential/": None, - "https://documen.tician.de/boxtree/": None, - "https://docs.sympy.org/latest/": None, - "https://matplotlib.org/stable/": None, + "arraycontext": ("https://documen.tician.de/arraycontext/", None), + "boxtree": ("https://documen.tician.de/boxtree/", None), + "namedisl": ("https://documen.tician.de/namedisl", None), + "loopy": ("https://documen.tician.de/loopy/", None), + "matplotlib": ("https://matplotlib.org/stable/", None), + "numpy": ("https://numpy.org/doc/stable/", None), + "pymbolic": ("https://documen.tician.de/pymbolic/", None), + "pyopencl": ("https://documen.tician.de/pyopencl/", None), + "pytential": ("https://documen.tician.de/pytential/", None), + "python": ("https://docs.python.org/3/", None), + "pytools": ("https://documen.tician.de/pytools/", None), + "sympy": ("https://docs.sympy.org/latest/", None), } + +nitpick_ignore_regex = [ + ["py:class", r"symengine\.(.+)"], # :cry: + ["py:class", r"ToTagSetConvertible"], # :cry: + # NOTE: optype does not have Sphinx compatible documentation + ["py:class", r"op.*"], + ["py:class", r"onp.*"], +] + +sphinxconfig_missing_reference_aliases = { + # numpy + "Array1D": "class:numpy.ndarray", + "Array2D": "class:numpy.ndarray", + "ArrayND": "class:numpy.ndarray", + "ToArray1D": "class:numpy.ndarray", + "np.floating": "class:numpy.floating", + "np.complexfloating": "class:numpy.complexfloating", + "np.inexact": "class:numpy.inexact", + "np.dtype": "class:numpy.dtype", + "np.number": "class:numpy.number", + # pytools + "obj_array.ObjectArray1D": "obj:pytools.obj_array.ObjectArray1D", + # sympy + "sp.Matrix": "class:sympy.matrices.dense.DenseMatrix", + "sym.Basic": "class:sympy.core.basic.Basic", + "sym.Expr": "class:sympy.core.expr.Expr", + "sym.Symbol": "class:sympy.core.symbol.Symbol", + "sym.Matrix": "class:sympy.matrices.dense.DenseMatrix", + # pytools + "ObjectArray1D": "obj:pytools.obj_array.ObjectArray1D", + # pymbolic + "ArithmeticExpression": "obj:pymbolic.ArithmeticExpression", + "Expression": "obj:pymbolic.typing.Expression", + # namedisl + "nisl.Set": "class:namedisl.Set", + # loopy + "Assignment": "class:loopy.kernel.instruction.Assignment", + "CallInstruction": "class:loopy.kernel.instruction.CallInstruction", + "InstructionBase": "class:loopy.kernel.instruction.InstructionBase", + # arraycontext + "Array": "obj:arraycontext.Array", + "ArrayContext": "class:arraycontext.ArrayContext", + # boxtree + "FMMTraversalInfo": "class:boxtree.traversal.FMMTraversalInfo", + # sumpy + "ArithmeticExpr": "obj:sumpy.kernel.ArithmeticExpr", + "OptimizationPair": "obj:sumpy.cse.OptimizationPair", +} + + +def setup(app): + app.connect("missing-reference", process_autodoc_missing_reference) # ruff:ignore[undefined-name] diff --git a/doc/eval.rst b/doc/eval.rst index e37447e7e..73b45b43f 100644 --- a/doc/eval.rst +++ b/doc/eval.rst @@ -1,5 +1,5 @@ -Differentiation and Evaluation -============================== +Working with Values of Potentials +================================= Visualization of Potentials --------------------------- @@ -11,7 +11,7 @@ Differentiation of Potentials .. automodule:: sumpy.point_calculus -Support for Numerical Experiments with Expansions -------------------------------------------------- +Support for Numerical Experiments with Expansions ("Expansion toys") +-------------------------------------------------------------------- .. automodule:: sumpy.toys diff --git a/doc/expansion.rst b/doc/expansion.rst index ffb1e1220..5d72d735a 100644 --- a/doc/expansion.rst +++ b/doc/expansion.rst @@ -18,6 +18,11 @@ Multipole Expansions .. automodule:: sumpy.expansion.multipole +Multipole to Local Translations +------------------------------- + +.. automodule:: sumpy.expansion.m2l + Estimating Expansion Orders --------------------------- diff --git a/doc/misc.rst b/doc/misc.rst index b281d3785..3080189b9 100644 --- a/doc/misc.rst +++ b/doc/misc.rst @@ -1,8 +1,13 @@ Misc Tools ========== +.. automodule:: sumpy.derivative_taker + +.. automodule:: sumpy.symbolic + .. automodule:: sumpy.tools +.. automodule:: sumpy.array_context Installation ============ @@ -106,7 +111,7 @@ The FAQ is maintained collaboratively on the Acknowledgments =============== -Work on meshmode was supported in part by +Work on sumpy was supported in part by * the US National Science Foundation under grant numbers DMS-1418961, DMS-1654756, SHF-1911019, and OAC-1931577. diff --git a/doc/upload-docs.sh b/doc/upload-docs.sh index c4d621d9b..525668c10 100755 --- a/doc/upload-docs.sh +++ b/doc/upload-docs.sh @@ -1,3 +1,3 @@ #! /bin/sh -rsync --verbose --archive --delete _build/html/* doc-upload:doc/sumpy +rsync --verbose --archive --delete _build/html/ doc-upload:doc/sumpy diff --git a/examples/curve-pot.py b/examples/curve-pot.py index 810990ad1..a2669da43 100644 --- a/examples/curve-pot.py +++ b/examples/curve-pot.py @@ -1,16 +1,26 @@ -import pyopencl as cl import numpy as np import numpy.linalg as la +from numpy.typing import NDArray + +import pyopencl as cl + try: import matplotlib.pyplot as plt -except ModuleNotFoundError: - plt = None + USE_MATPLOTLIB = True +except ImportError: + USE_MATPLOTLIB = False try: from mayavi import mlab -except ModuleNotFoundError: - mlab = None + USE_MAYAVI = True +except ImportError: + USE_MAYAVI = False + +import logging + + +logging.basicConfig(level=logging.INFO) def process_kernel(knl, what_operator): @@ -19,18 +29,14 @@ def process_kernel(knl, what_operator): if what_operator == "S": pass elif what_operator == "S0": - from sumpy.kernel import TargetDerivative - target_knl = TargetDerivative(0, knl) + from sumpy.kernel import AxisTargetDerivative + target_knl = AxisTargetDerivative(0, knl) elif what_operator == "S1": - from sumpy.kernel import TargetDerivative - target_knl = TargetDerivative(1, knl) + from sumpy.kernel import AxisTargetDerivative + target_knl = AxisTargetDerivative(1, knl) elif what_operator == "D": from sumpy.kernel import DirectionalSourceDerivative source_knl = DirectionalSourceDerivative(knl) - # DirectionalTargetDerivative (temporarily?) removed - # elif what_operator == "S'": - # from sumpy.kernel import DirectionalTargetDerivative - # knl = DirectionalTargetDerivative(knl) else: raise RuntimeError(f"unrecognized operator '{what_operator}'") @@ -45,17 +51,16 @@ def draw_pot_figure(aspect_ratio, ovsmp_center_exp=0.66, force_center_side=None): - import logging - logging.basicConfig(level=logging.INFO) - if novsmp is None: novsmp = 4*nsrc if what_operator_lpot is None: what_operator_lpot = what_operator + from sumpy.array_context import PyOpenCLArrayContext ctx = cl.create_some_context() queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) # {{{ make plot targets @@ -67,9 +72,9 @@ def draw_pot_figure(aspect_ratio, # {{{ make p2p kernel calculator - from sumpy.p2p import P2P - from sumpy.kernel import LaplaceKernel, HelmholtzKernel from sumpy.expansion.local import H2DLocalExpansion, LineTaylorLocalExpansion + from sumpy.kernel import HelmholtzKernel, LaplaceKernel + from sumpy.p2p import P2P if helmholtz_k: if isinstance(helmholtz_k, complex): knl = HelmholtzKernel(2, allow_evanescent=True) @@ -86,7 +91,8 @@ def draw_pot_figure(aspect_ratio, knl_kwargs = {} vol_source_knl, vol_target_knl = process_kernel(knl, what_operator) - p2p = P2P(ctx, source_kernels=(vol_source_knl,), + p2p = P2P( + source_kernels=(vol_source_knl,), target_kernels=(vol_target_knl,), exclude_self=False, value_dtypes=np.complex128) @@ -94,8 +100,10 @@ def draw_pot_figure(aspect_ratio, lpot_source_knl, lpot_target_knl = process_kernel(knl, what_operator_lpot) from sumpy.qbx import LayerPotential - lpot = LayerPotential(ctx, expansion=expn_class(knl, order=order), - source_kernels=(lpot_source_knl,), target_kernels=(lpot_target_knl,), + lpot = LayerPotential( + expansion=expn_class(knl, order=order), + source_kernels=(lpot_source_knl,), + target_kernels=(lpot_target_knl,), value_dtypes=np.complex128) # }}} @@ -109,7 +117,7 @@ def draw_pot_figure(aspect_ratio, a = 1 b = 1/aspect_ratio - def map_to_curve(t): + def map_to_curve(t: NDArray[np.floating]): t = t*(2*np.pi) x = a*np.cos(t) @@ -119,7 +127,7 @@ def map_to_curve(t): return x, y, w - from curve import CurveGrid + from sumpy.test.curve import CurveGrid native_t = np.linspace(0, 1, nsrc, endpoint=False) native_x, native_y, native_weights = map_to_curve(native_t) @@ -142,46 +150,56 @@ def map_to_curve(t): + center_side[:, np.newaxis] * center_dist*native_curve.normal) - #native_curve.plot() - #plt.show() + if 0: + native_curve.plot() + plt.show() volpot_kwargs = knl_kwargs.copy() lpot_kwargs = knl_kwargs.copy() if what_operator == "D": - volpot_kwargs["src_derivative_dir"] = native_curve.normal + volpot_kwargs["src_derivative_dir"] = actx.from_numpy(native_curve.normal) if what_operator_lpot == "D": - lpot_kwargs["src_derivative_dir"] = ovsmp_curve.normal + lpot_kwargs["src_derivative_dir"] = actx.from_numpy(ovsmp_curve.normal) if what_operator_lpot == "S'": - lpot_kwargs["tgt_derivative_dir"] = native_curve.normal + lpot_kwargs["tgt_derivative_dir"] = actx.from_numpy(native_curve.normal) # }}} + targets = actx.from_numpy(fp.points) + sources = actx.from_numpy(native_curve.pos) + ovsmp_sources = actx.from_numpy(ovsmp_curve.pos) + if 0: # {{{ build matrix from fourier import make_fourier_interp_matrix fim = make_fourier_interp_matrix(novsmp, nsrc) - from sumpy.tools import build_matrix + from scipy.sparse.linalg import LinearOperator - def apply_lpot(x): - xovsmp = np.dot(fim, x) - evt, (y,) = lpot(queue, native_curve.pos, ovsmp_curve.pos, - centers, - [xovsmp * ovsmp_curve.speed * ovsmp_weights], - expansion_radii=np.ones(centers.shape[1]), + from sumpy.tools import build_matrix + + def apply_lpot(x: NDArray[np.inexact]) -> NDArray[np.inexact]: + xovsmp = fim @ x + y, = lpot( + actx, + sources, + ovsmp_sources, + actx.from_numpy(centers), + [actx.from_numpy(xovsmp * ovsmp_curve.speed * ovsmp_weights)], + expansion_radii=actx.from_numpy(np.ones(centers.shape[1])), **lpot_kwargs) - return y + return actx.to_numpy(y) - op = LinearOperator((nsrc, nsrc), apply_lpot) + op = LinearOperator((nsrc, nsrc), np.dtype(np.complex128), apply_lpot) mat = build_matrix(op, dtype=np.complex128) - w, v = la.eig(mat) + w, _v = la.eig(mat) plt.plot(w.real, "o-") - #import sys; sys.exit(0) + # import sys; sys.exit(0) return # }}} @@ -190,19 +208,32 @@ def apply_lpot(x): mode_nr = 0 density = np.cos(mode_nr*2*np.pi*native_t).astype(np.complex128) - ovsmp_density = np.cos(mode_nr*2*np.pi*ovsmp_t).astype(np.complex128) - evt, (vol_pot,) = p2p(queue, fp.points, native_curve.pos, - [native_curve.speed*native_weights*density], **volpot_kwargs) + strength = actx.from_numpy(native_curve.speed * native_weights * density) - evt, (curve_pot,) = lpot(queue, native_curve.pos, ovsmp_curve.pos, - centers, - [ovsmp_density * ovsmp_curve.speed * ovsmp_weights], - expansion_radii=np.ones(centers.shape[1]), + vol_pot, = p2p( + actx, + targets, + sources, + [strength], **volpot_kwargs) + vol_pot = actx.to_numpy(vol_pot) + + ovsmp_density = np.cos(mode_nr*2*np.pi*ovsmp_t).astype(np.complex128) + ovsmp_strength = actx.from_numpy( + ovsmp_curve.speed * ovsmp_weights * ovsmp_density) + + curve_pot, = lpot( + actx, + sources, + ovsmp_sources, + actx.from_numpy(centers), + [ovsmp_strength], + expansion_radii=actx.from_numpy(np.ones(centers.shape[1])), **lpot_kwargs) + curve_pot = actx.to_numpy(curve_pot) # }}} - if 0: + if USE_MATPLOTLIB: # {{{ plot on-surface potential in 2D plt.plot(curve_pot, label="pot") @@ -216,7 +247,7 @@ def apply_lpot(x): ("potential", vol_pot.real) ]) - if 0: + if USE_MATPLOTLIB: # {{{ 2D false-color plot plt.clf() @@ -230,12 +261,8 @@ def apply_lpot(x): # close the curve plt.plot(src[-1::-len(src)+1, 0], src[-1::-len(src)+1, 1], "o-k") - #plt.gca().set_aspect("equal", "datalim") cb = plt.colorbar(shrink=0.9) cb.set_label(r"$\log_{10}(\mathdefault{Error})$") - #from matplotlib.ticker import NullFormatter - #plt.gca().xaxis.set_major_formatter(NullFormatter()) - #plt.gca().yaxis.set_major_formatter(NullFormatter()) fp.set_matplotlib_limits() # }}} @@ -261,7 +288,7 @@ def apply_lpot(x): plotval_vol[outlier_flag] = sum( nb[outlier_flag] for nb in neighbors)/len(neighbors) - if mlab is not None: + if USE_MAYAVI: fp.show_scalar_in_mayavi(scale*plotval_vol, max_val=1) mlab.colorbar() if 1: @@ -275,17 +302,23 @@ def apply_lpot(x): if __name__ == "__main__": - draw_pot_figure(aspect_ratio=1, nsrc=100, novsmp=100, helmholtz_k=(35+4j)*0.3, + draw_pot_figure( + aspect_ratio=1, nsrc=100, novsmp=100, helmholtz_k=(35+4j)*0.3, what_operator="D", what_operator_lpot="D", force_center_side=1) + if USE_MATPLOTLIB: + plt.savefig("eigvals-ext-nsrc100-novsmp100.pdf") + plt.clf() -# plt.savefig("eigvals-ext-nsrc100-novsmp100.pdf") - #plt.clf() - #draw_pot_figure(aspect_ratio=1, nsrc=100, novsmp=100, helmholtz_k=0, - # what_operator="D", what_operator_lpot="D", force_center_side=-1) - #plt.savefig("eigvals-int-nsrc100-novsmp100.pdf") - #plt.clf() - #draw_pot_figure(aspect_ratio=1, nsrc=100, novsmp=200, helmholtz_k=0, - # what_operator="D", what_operator_lpot="D", force_center_side=-1) - #plt.savefig("eigvals-int-nsrc100-novsmp200.pdf") + # draw_pot_figure( + # aspect_ratio=1, nsrc=100, novsmp=100, helmholtz_k=0, + # what_operator="D", what_operator_lpot="D", force_center_side=-1) + # plt.savefig("eigvals-int-nsrc100-novsmp100.pdf") + # plt.clf() + + # draw_pot_figure( + # aspect_ratio=1, nsrc=100, novsmp=200, helmholtz_k=0, + # what_operator="D", what_operator_lpot="D", force_center_side=-1) + # plt.savefig("eigvals-int-nsrc100-novsmp200.pdf") + # plt.clf() # vim: fdm=marker diff --git a/examples/curve.py b/examples/curve.py deleted file mode 100644 index 589ad16e7..000000000 --- a/examples/curve.py +++ /dev/null @@ -1,24 +0,0 @@ -import numpy as np - -import scipy as sp -import scipy.fftpack - - -class CurveGrid: - def __init__(self, x, y): - self.pos = np.vstack([x, y]).copy() - xp = self.xp = sp.fftpack.diff(x, period=1) - yp = self.yp = sp.fftpack.diff(y, period=1) - xpp = self.xpp = sp.fftpack.diff(xp, period=1) - ypp = self.ypp = sp.fftpack.diff(yp, period=1) - self.mean_curvature = (xp*ypp-yp*xpp)/((xp**2+yp**2)**(3/2)) - - speed = self.speed = np.sqrt(xp**2+yp**2) - self.normal = (np.vstack([yp, -xp])/speed).copy() - - def __len__(self): - return len(self.pos) - - def plot(self): - import matplotlib.pyplot as pt - pt.plot(self.pos[:, 0], self.pos[:, 1]) diff --git a/examples/expansion-toys.py b/examples/expansion-toys.py index e774b17ae..5f70931a5 100644 --- a/examples/expansion-toys.py +++ b/examples/expansion-toys.py @@ -1,47 +1,60 @@ +import numpy as np + import pyopencl as cl + import sumpy.toys as t -import numpy as np +from sumpy.kernel import ( # ruff:ignore[unused-import] + HelmholtzKernel, + LaplaceKernel, + YukawaKernel, +) from sumpy.visualization import FieldPlotter + + try: import matplotlib.pyplot as plt -except ModuleNotFoundError: - plt = None + USE_MATPLOTLIB = True +except ImportError: + USE_MATPLOTLIB = False def main(): - from sumpy.kernel import ( # noqa: F401 - YukawaKernel, HelmholtzKernel, LaplaceKernel) + from sumpy.array_context import PyOpenCLArrayContext + ctx = cl.create_some_context() + queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) + tctx = t.ToyContext( - cl.create_some_context(), - #LaplaceKernel(2), + # LaplaceKernel(2), YukawaKernel(2), extra_kernel_kwargs={"lam": 5}, - #HelmholtzKernel(2), extra_kernel_kwargs={"k": 0.3}, + # HelmholtzKernel(2), extra_kernel_kwargs={"k": 0.3}, ) + rng = np.random.default_rng() pt_src = t.PointSources( tctx, - np.random.rand(2, 50) - 0.5, + rng.uniform(size=(2, 50)) - 0.5, np.ones(50)) fp = FieldPlotter([3, 0], extent=8) - if 0 and plt is not None: - t.logplot(fp, pt_src, cmap="jet") + if USE_MATPLOTLIB: + t.logplot(actx, fp, pt_src, cmap="jet") plt.colorbar() plt.show() - mexp = t.multipole_expand(pt_src, [0, 0], 5) - mexp2 = t.multipole_expand(mexp, [0, 0.25]) # noqa: F841 - lexp = t.local_expand(mexp, [3, 0]) - lexp2 = t.local_expand(lexp, [3, 1], 3) + mexp = t.multipole_expand(actx, pt_src, [0, 0], order=5) + mexp2 = t.multipole_expand(actx, mexp, [0, 0.25]) # ruff:ignore[unused-variable] + lexp = t.local_expand(actx, mexp, [3, 0]) + lexp2 = t.local_expand(actx, lexp, [3, 1], order=3) - #diff = mexp - pt_src - #diff = mexp2 - pt_src + # diff = mexp - pt_src + # diff = mexp2 - pt_src diff = lexp2 - pt_src - print(t.l_inf(diff, 1.2, center=lexp2.center)) - if 1 and plt is not None: - t.logplot(fp, diff, cmap="jet", vmin=-3, vmax=0) + print(t.l_inf(actx, diff, 1.2, center=lexp2.center)) + if USE_MATPLOTLIB: + t.logplot(actx, fp, diff, cmap="jet", vmin=-3, vmax=0) plt.colorbar() plt.show() diff --git a/examples/fourier.py b/examples/fourier.py index a4a04b9fc..a72702985 100644 --- a/examples/fourier.py +++ b/examples/fourier.py @@ -1,13 +1,14 @@ import numpy as np +from numpy.typing import NDArray -def make_fourier_vdm(n, inverse): +def make_fourier_vdm(n: int, inverse: bool) -> NDArray[np.complex128]: i = np.arange(n, dtype=np.float64) imat = i[:, np.newaxis]*i/n result = np.exp((2j*np.pi)*imat) if inverse: - result = result.T.conj()/n + result = np.conj(result.T)/n return result @@ -16,7 +17,7 @@ def make_fourier_mode_extender(m, n, dtype): result = np.zeros((m, n), dtype) # https://docs.scipy.org/doc/numpy/reference/routines.fft.html - if k % 2 == 0: + if k % 2 == 0: # ruff:ignore[if-else-block-instead-of-if-exp] peak_pos_freq = k/2 else: peak_pos_freq = (k-1)/2 @@ -30,9 +31,7 @@ def make_fourier_mode_extender(m, n, dtype): return result -def make_fourier_interp_matrix(m, n): - return np.dot( - np.dot( - make_fourier_vdm(m, inverse=False), - make_fourier_mode_extender(m, n, np.float64)), - make_fourier_vdm(n, inverse=True)) +def make_fourier_interp_matrix(m: int, n: int): + return (make_fourier_vdm(m, inverse=False) + @ make_fourier_mode_extender(m, n, np.float64) + @ make_fourier_vdm(n, inverse=True)) diff --git a/examples/sym-exp-complexity.py b/examples/sym-exp-complexity.py index ae91a932c..c0d0c8163 100644 --- a/examples/sym-exp-complexity.py +++ b/examples/sym-exp-complexity.py @@ -1,22 +1,30 @@ import numpy as np -import pyopencl as cl + import loopy as lp -from sumpy.kernel import LaplaceKernel, HelmholtzKernel +import pyopencl as cl + +from sumpy.e2e import E2EFromCSR from sumpy.expansion.local import ( - LinearPDEConformingVolumeTaylorLocalExpansion, - ) + LinearPDEConformingVolumeTaylorLocalExpansion, +) from sumpy.expansion.multipole import ( - LinearPDEConformingVolumeTaylorMultipoleExpansion, - ) -from sumpy.e2e import E2EFromCSR + LinearPDEConformingVolumeTaylorMultipoleExpansion, +) +from sumpy.kernel import HelmholtzKernel, LaplaceKernel + + try: import matplotlib.pyplot as plt -except ModuleNotFoundError: - plt = None + USE_MATPLOTLIB = True +except ImportError: + USE_MATPLOTLIB = False def find_flops(): + from sumpy.array_context import PyOpenCLArrayContext ctx = cl.create_some_context() + queue = cl.CommandQueue(ctx) + actx = PyOpenCLArrayContext(queue) if 0: knl = LaplaceKernel(2) @@ -35,7 +43,7 @@ def find_flops(): print(order) m_expn = m_expn_cls(knl, order) l_expn = l_expn_cls(knl, order) - m2l = E2EFromCSR(ctx, m_expn, l_expn) + m2l = E2EFromCSR(actx.context, m_expn, l_expn) loopy_knl = m2l.get_kernel() loopy_knl = lp.add_and_infer_dtypes( @@ -48,7 +56,7 @@ def find_flops(): flops = lp.get_op_map(loopy_knl).filter_by(dtype=[flop_type]).sum() flop_counts.append( flops.eval_with_dict( - dict(isrc_start=0, isrc_stop=1, ntgt_boxes=1))) + {"isrc_start": 0, "isrc_stop": 1, "ntgt_boxes": 1})) print(orders) print(flop_counts) @@ -73,8 +81,10 @@ def plot_flops(): orders = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] flops = [45, 194, 474, 931, 1650, 2632, 3925, 5591, 7706, 10272] filename = "helmholtz-m2l-complexity-2d.pdf" + else: + raise ValueError() - if plt is not None: + if USE_MATPLOTLIB: plt.rc("font", size=16) plt.title(case) plt.ylabel("Flop count") @@ -86,5 +96,5 @@ def plot_flops(): if __name__ == "__main__": - #find_flops() + # find_flops() plot_flops() diff --git a/notes/.gitignore b/notes/.gitignore deleted file mode 100644 index ee0fa6e3f..000000000 --- a/notes/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -*converted-to*pdf -out diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..3eb8e7643 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,227 @@ +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[project] +name = "sumpy" +version = "2024.0" +description = "Fast summation in Python" +readme = "README.rst" +license = "MIT" +authors = [ + { name = "Andreas Kloeckner", email = "inform@tiker.net" }, +] +requires-python = ">=3.10" +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Developers", + "Intended Audience :: Other Audience", + "Intended Audience :: Science/Research", + "Programming Language :: Python", + "Programming Language :: Python :: 3 :: Only", + "Topic :: Scientific/Engineering", + "Topic :: Scientific/Engineering :: Information Analysis", + "Topic :: Scientific/Engineering :: Mathematics", + "Topic :: Scientific/Engineering :: Visualization", + "Topic :: Software Development :: Libraries", + "Topic :: Utilities", +] +dependencies = [ + "arraycontext>=2021.1", + "boxtree>=2023.1", + "constantdict>=2024.4", + "loopy>=2024.1", + "numpy", + "pyopencl>=2022.1", + "pytools>=2024.1", + "pymbolic>=2024.2", + "sympy>=0.7.2", +] + +[dependency-groups] +dev = [ + {include-group = "type"}, + {include-group = "doc"}, + {include-group = "test"}, + {include-group = "lint"}, +] +type = [ + "optype" +] +lint = [ + # https://github.com/astral-sh/ruff/issues/16943 + "ruff!=0.11.1,!=0.11.2", +] +doc = [ + "furo", + "sphinx-copybutton", + "sphinx>=4", +] +test = [ + "pytest", +] + +[project.optional-dependencies] +fmmlib = [ + "pyfmmlib>=2023.1", +] +symengine = [ + "symengine>=0.9.0", +] +pyvkfft = [ + "pyvkfft>=2024.1", +] + +[project.urls] +Documentation = "https://documen.tician.de/sumpy" +Repository = "https://github.com/inducer/sumpy" + +[tool.ruff] +preview = true + +[tool.ruff.lint] +extend-select = [ + "B", # flake8-bugbear + "C", # flake8-comprehensions + "E", # pycodestyle + "F", # pyflakes + "G", # flake8-logging-format + "I", # flake8-isort + "N", # pep8-naming + "NPY", # numpy + "Q", # flake8-quotes + "RUF", # ruff + "SIM", # flake8-simplify + "TC", # flake8-type-checking + "UP", # pyupgrade + "W", # pycodestyle +] +extend-ignore = [ + "complex-structure", + "missing-whitespace-around-arithmetic-operator", + "module-import-not-at-top-of-file", + "multiple-spaces-before-operator", + "non-empty-init-module", + "try-consider-else", + "type-check-without-type-error", +] + +[tool.ruff.lint.flake8-quotes] +docstring-quotes = "double" +inline-quotes = "double" +multiline-quotes = "double" + +[tool.ruff.lint.isort] +combine-as-imports = true +known-first-party = [ + "arraycontext", + "loopy", + "pymbolic", + "pyopencl", + "pytools", +] +known-local-folder = [ + "sumpy", +] +lines-after-imports = 2 +required-imports = ["from __future__ import annotations"] + +[tool.ruff.lint.per-file-ignores] +"doc/**/*.py" = ["missing-required-import"] +"examples/**/*.py" = ["missing-required-import"] +"sumpy/test/test_*.py" = ["exec-builtin"] +"doc/conf.py" = ["exec-builtin"] + +[tool.typos.default] +extend-ignore-re = [ + "(?Rm)^.*(#|//)\\s*spellchecker:\\s*disable-line$" +] + +[tool.typos.default.extend-words] +# short for multi-indices +mis = "mis" +# short for n-dimensional +nd = "nd" +# short for Theorem +thm = "thm" + +[tool.typos.files] +extend-exclude = [ + "contrib/*/*.ipynb", + "notes/*/*.eps", +] + +[tool.pytest.ini_options] +markers = [ + "mpi: tests distributed FMM", +] + +[tool.basedpyright] +reportImplicitStringConcatenation = "none" +reportUnnecessaryIsInstance = "none" +reportUnusedCallResult = "none" +reportExplicitAny = "none" +reportPrivateUsage = "none" + +# Multiple reasons for this: +# - make_subst_func is reported as having an incomplete type (but only in CI?) +# - numpy scalar types are reported as incomplete (because of "any" precision) +reportUnknownVariableType = "none" + +reportUnreachable = "hint" +reportUnnecessaryComparison = "hint" +reportPossiblyUnboundVariable = "hint" + +# This reports even cycles that are qualified by 'if TYPE_CHECKING'. Not what +# we care about at this moment. +# https://github.com/microsoft/pyright/issues/746 +reportImportCycles = "none" + +pythonVersion = "3.12" +pythonPlatform = "All" + +exclude = [ + "doc", + "build", + "benchmarks", + "contrib", + ".conda-root", + ".venv", +] + +# covered by ruff +reportUnusedImport = "hint" + +[[tool.basedpyright.executionEnvironments]] +root = "test" +reportUnknownArgumentType = "none" +reportUnknownVariableType = "none" +reportUnknownParameterType = "hint" +reportMissingParameterType = "none" +reportAttributeAccessIssue = "hint" +reportMissingTypeStubs = "hint" +reportUnknownLambdaType = "hint" +reportUnusedParameter = "none" +reportUnannotatedClassAttribute = "hint" +reportAny = "hint" +reportUnknownMemberType = "hint" +reportMissingImports = "none" +reportArgumentType = "hint" +reportOperatorIssue = "hint" + +[[tool.basedpyright.executionEnvironments]] +root = "examples" +reportUnknownArgumentType = "none" +reportUnknownVariableType = "none" +reportUnknownParameterType = "hint" +reportMissingParameterType = "none" +reportAttributeAccessIssue = "hint" +reportMissingTypeStubs = "hint" +reportUnknownLambdaType = "hint" +reportUnusedParameter = "none" +reportUnannotatedClassAttribute = "hint" +reportAny = "hint" +reportUnknownMemberType = "hint" +reportMissingImports = "none" +reportArgumentType = "hint" +reportOperatorIssue = "hint" diff --git a/requirements.txt b/requirements.txt index ed07527b7..57085a116 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,16 @@ numpy sympy -pyrsistent +constantdict +pyvkfft + +# used in mpi-based tests +platformdirs + git+https://github.com/inducer/pytools.git#egg=pytools git+https://github.com/inducer/pymbolic.git#egg=pymbolic git+https://github.com/inducer/islpy.git#egg=islpy git+https://github.com/inducer/pyopencl.git#egg=pyopencl git+https://github.com/inducer/boxtree.git#egg=boxtree git+https://github.com/inducer/loopy.git#egg=loopy +git+https://github.com/inducer/arraycontext.git#egg=arraycontext git+https://github.com/inducer/pyfmmlib.git#egg=pyfmmlib diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 9c1172912..000000000 --- a/setup.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[flake8] -ignore = E126,E127,E128,E123,E226,E241,E242,E265,E402,W503 -max-line-length=85 - -inline-quotes = " -docstring-quotes = """ -multiline-quotes = """ - -# enable-flake8-bugbear diff --git a/setup.py b/setup.py deleted file mode 100644 index 5ca544177..000000000 --- a/setup.py +++ /dev/null @@ -1,110 +0,0 @@ -#!/usr/bin/env python - -import os -from setuptools import setup - -ver_dic = {} -version_file = open("sumpy/version.py") -try: - version_file_contents = version_file.read() -finally: - version_file.close() - -os.environ["AKPYTHON_EXEC_FROM_WITHIN_WITHIN_SETUP_PY"] = "1" -exec(compile(version_file_contents, "sumpy/version.py", "exec"), ver_dic) - - -# {{{ capture git revision at install time - -# authoritative version in pytools/__init__.py -def find_git_revision(tree_root): - # Keep this routine self-contained so that it can be copy-pasted into - # setup.py. - - from os.path import join, exists, abspath - - tree_root = abspath(tree_root) - - if not exists(join(tree_root, ".git")): - return None - - from subprocess import Popen, PIPE, STDOUT - - p = Popen( - ["git", "rev-parse", "HEAD"], - shell=False, - stdin=PIPE, - stdout=PIPE, - stderr=STDOUT, - close_fds=True, - cwd=tree_root, - ) - (git_rev, _) = p.communicate() - - git_rev = git_rev.decode() - git_rev = git_rev.rstrip() - - retcode = p.returncode - assert retcode is not None - if retcode != 0: - from warnings import warn - - warn("unable to find git revision") - return None - - return git_rev - - -def write_git_revision(package_name): - from os.path import dirname, join - - dn = dirname(__file__) - git_rev = find_git_revision(dn) - - with open(join(dn, package_name, "_git_rev.py"), "w") as outf: - outf.write(f'GIT_REVISION = "{git_rev}"\n') - - -write_git_revision("sumpy") - -# }}} - - -setup( - name="sumpy", - version=ver_dic["VERSION_TEXT"], - description="Fast summation in Python", - long_description=""" - Code-generating FMM etc. - """, - classifiers=[ - "Development Status :: 3 - Alpha", - "Intended Audience :: Developers", - "Intended Audience :: Other Audience", - "Intended Audience :: Science/Research", - "License :: OSI Approved :: MIT License", - "Natural Language :: English", - "Programming Language :: Python", - "Topic :: Scientific/Engineering", - "Topic :: Scientific/Engineering :: Information Analysis", - "Topic :: Scientific/Engineering :: Mathematics", - "Topic :: Scientific/Engineering :: Visualization", - "Topic :: Software Development :: Libraries", - "Topic :: Utilities", - ], - author="Andreas Kloeckner", - author_email="inform@tiker.net", - license="MIT", - packages=["sumpy", "sumpy.expansion"], - python_requires="~=3.6", - install_requires=[ - "pytools>=2021.1.1", - "loopy>=2021.1", - "boxtree>=2018.1", - "pytest>=2.3", - "pyrsistent>=0.16.0", - "dataclasses>=0.7;python_version<='3.6'", - "sympy>=0.7.2", - "pymbolic>=2021.1", - ], -) diff --git a/sumpy/__init__.py b/sumpy/__init__.py index b39ead2ec..f2dc45abc 100644 --- a/sumpy/__init__.py +++ b/sumpy/__init__.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2013 Andreas Kloeckner" __license__ = """ @@ -21,27 +24,50 @@ """ import os -from sumpy.p2p import P2P, P2PFromCSR -from sumpy.p2e import P2EFromSingleBox, P2EFromCSR -from sumpy.e2p import E2PFromSingleBox, E2PFromCSR -from sumpy.e2e import (E2EFromCSR, E2EFromChildren, E2EFromParent, +from typing import TYPE_CHECKING + +from pytools.persistent_dict import WriteOncePersistentDict + +from sumpy.e2e import ( + E2EFromChildren, + E2EFromCSR, + E2EFromParent, + M2LGenerateTranslationClassesDependentData, + M2LPostprocessLocal, + M2LPreprocessMultipole, M2LUsingTranslationClassesDependentData, - M2LGenerateTranslationClassesDependentData, M2LPreprocessMultipole, - M2LPostprocessLocal) +) +from sumpy.e2p import E2PFromCSR, E2PFromSingleBox +from sumpy.p2e import P2EFromCSR, P2EFromSingleBox +from sumpy.p2p import P2P, P2PFromCSR from sumpy.version import VERSION_TEXT -from pytools.persistent_dict import WriteOncePersistentDict + + +if TYPE_CHECKING: + from collections.abc import Hashable + + import loopy as lp + __all__ = [ - "P2P", "P2PFromCSR", - "P2EFromSingleBox", "P2EFromCSR", - "E2PFromSingleBox", "E2PFromCSR", - "E2EFromCSR", "E2EFromChildren", "E2EFromParent", - "M2LUsingTranslationClassesDependentData", + "P2P", + "E2EFromCSR", + "E2EFromChildren", + "E2EFromParent", + "E2PFromCSR", + "E2PFromSingleBox", "M2LGenerateTranslationClassesDependentData", - "M2LPreprocessMultipole", "M2LPostprocessLocal"] + "M2LPostprocessLocal", + "M2LPreprocessMultipole", + "M2LUsingTranslationClassesDependentData", + "P2EFromCSR", + "P2EFromSingleBox", + "P2PFromCSR", +] -code_cache = WriteOncePersistentDict("sumpy-code-cache-v6-"+VERSION_TEXT) +code_cache: WriteOncePersistentDict[Hashable, lp.TranslationUnit] = ( + WriteOncePersistentDict(f"sumpy-code-cache-v8-{VERSION_TEXT}", safe_sync=False)) # {{{ optimization control @@ -61,18 +87,20 @@ def set_optimization_enabled(flag): # {{{ cache control -CACHING_ENABLED = True - CACHING_ENABLED = ( "SUMPY_NO_CACHE" not in os.environ and "CG_NO_CACHE" not in os.environ) +NO_CACHE_KERNELS = tuple(os.environ.get("SUMPY_NO_CACHE_KERNELS", + "").split(",")) + -def set_caching_enabled(flag): +def set_caching_enabled(flag, no_cache_kernels=()): """Set whether :mod:`loopy` is allowed to use disk caching for its various code generation stages. """ - global CACHING_ENABLED + global CACHING_ENABLED, NO_CACHE_KERNELS + NO_CACHE_KERNELS = no_cache_kernels CACHING_ENABLED = flag @@ -81,17 +109,18 @@ class CacheMode: disk caches. """ - def __init__(self, new_flag): + def __init__(self, new_flag, new_no_cache_kernels=()): self.new_flag = new_flag + self.new_no_cache_kernels = new_no_cache_kernels def __enter__(self): - global CACHING_ENABLED - self.previous_mode = CACHING_ENABLED - CACHING_ENABLED = self.new_flag + self.previous_flag = CACHING_ENABLED + self.previous_kernels = NO_CACHE_KERNELS + set_caching_enabled(self.new_flag, self.new_no_cache_kernels) def __exit__(self, exc_type, exc_val, exc_tb): - global CACHING_ENABLED - CACHING_ENABLED = self.previous_mode - del self.previous_mode + set_caching_enabled(self.previous_flag, self.previous_kernels) + del self.previous_flag + del self.previous_kernels # }}} diff --git a/sumpy/array_context.py b/sumpy/array_context.py new file mode 100644 index 000000000..c305fadc1 --- /dev/null +++ b/sumpy/array_context.py @@ -0,0 +1,159 @@ +from __future__ import annotations + + +__copyright__ = "Copyright (C) 2022 Alexandru Fikl" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +from typing import TYPE_CHECKING, Any + +from boxtree.array_context import PyOpenCLArrayContext as PyOpenCLArrayContextBase +from typing_extensions import override + +import loopy as lp +from arraycontext.pytest import ( + _PytestPyOpenCLArrayContextFactoryWithClass, + register_pytest_array_context_factory, +) + + +if TYPE_CHECKING: + from collections.abc import Iterator, Sequence + + import namedisl as nisl + from numpy.typing import DTypeLike + + from arraycontext import ArrayContext + from loopy import TranslationUnit + from loopy.codegen import PreambleInfo + from loopy.kernel.instruction import InstructionBase + from pytools.tag import ToTagSetConvertible + + +__doc__ = """ +Array Context +------------- + +.. autofunction:: make_loopy_program +.. autoclass:: PyOpenCLArrayContext +""" + + +# {{{ PyOpenCLArrayContext + +def make_loopy_program( + domains: str | Sequence[str | nisl.Set], + statements: Sequence[InstructionBase | str] | str, + kernel_data: list[Any] | None = None, *, + name: str = "sumpy_loopy_kernel", + silenced_warnings: list[str] | str | None = None, + assumptions: str = "", + fixed_parameters: dict[str, Any] | None = None, + index_dtype: DTypeLike | None = None, + tags: ToTagSetConvertible = None): + """Return a :class:`loopy.LoopKernel` suitable for use with + :meth:`arraycontext.ArrayContext.call_loopy`. + """ + if kernel_data is None: + kernel_data = [...] + + if silenced_warnings is None: + silenced_warnings = [] + + import loopy as lp + from arraycontext.loopy import _DEFAULT_LOOPY_OPTIONS + + return lp.make_kernel( + domains, + statements, + kernel_data=kernel_data, + options=_DEFAULT_LOOPY_OPTIONS, + default_offset=lp.auto, + name=name, + lang_version=lp.MOST_RECENT_LANGUAGE_VERSION, + assumptions=assumptions, + fixed_parameters=fixed_parameters, + silenced_warnings=silenced_warnings, + index_dtype=index_dtype, + tags=tags) + + +def _fp_contract_fast_preamble( + preamble_info: PreambleInfo + ) -> Iterator[tuple[str, str]]: + yield ("fp_contract_fast_pocl", "#pragma clang fp contract(fast)") + + +class PyOpenCLArrayContext(PyOpenCLArrayContextBase): + @override + def transform_loopy_program(self, t_unit: TranslationUnit): + import pyopencl as cl + device = self.queue.device + if (device.platform.name == "Portable Computing Language" + and (device.type & cl.device_type.GPU)): + t_unit = lp.register_preamble_generators( + t_unit, + [_fp_contract_fast_preamble]) + + return t_unit + + +def is_cl_cpu(actx: ArrayContext) -> bool: + if not isinstance(actx, PyOpenCLArrayContext): + return False + + import pyopencl as cl + return all(dev.type & cl.device_type.CPU for dev in actx.context.devices) + +# }}} + + +# {{{ pytest + +def _acf() -> ArrayContext: + import pyopencl as cl + ctx = cl.create_some_context() + queue = cl.CommandQueue(ctx) + + return PyOpenCLArrayContext(queue) + + +class PytestPyOpenCLArrayContextFactory( + _PytestPyOpenCLArrayContextFactoryWithClass): + @property + @override + def actx_class(self) -> type[ArrayContext]: + return PyOpenCLArrayContext + + @override + def __call__(self) -> ArrayContext: + # NOTE: prevent any cache explosions during testing! + from sympy.core.cache import clear_cache + clear_cache() + + return super().__call__() + + +register_pytest_array_context_factory( + "sumpy.pyopencl", + PytestPyOpenCLArrayContextFactory) + +# }}} diff --git a/sumpy/assignment_collection.py b/sumpy/assignment_collection.py index 81bb066f9..45f4b7675 100644 --- a/sumpy/assignment_collection.py +++ b/sumpy/assignment_collection.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2012 Andreas Kloeckner" __license__ = """ @@ -20,40 +23,48 @@ THE SOFTWARE. """ +import logging +from collections import defaultdict +from typing import TYPE_CHECKING, overload + +from typing_extensions import Self, override import sumpy.symbolic as sym -import logging + +if TYPE_CHECKING: + from collections.abc import Mapping, Sequence + + logger = logging.getLogger(__name__) __doc__ = """ - Manipulating batches of assignments ----------------------------------- .. autoclass:: SymbolicAssignmentCollection - """ class _SymbolGenerator: + taken_symbols: Mapping[str, sym.Basic] + base_to_count: dict[str, int] - def __init__(self, taken_symbols): + def __init__(self, taken_symbols: Mapping[str, sym.Basic]) -> None: self.taken_symbols = taken_symbols - from collections import defaultdict self.base_to_count = defaultdict(lambda: 0) - def _normalize(self, base): + def _normalize(self, base: str) -> str: # Strip off any _N suffix, to avoid generating conflicting names. import re base = re.split(r"_\d+$", base)[0] return base if base != "" else "expr" - def __call__(self, base="expr"): + def __call__(self, base: str = "expr") -> sym.Symbol: base = self._normalize(base) count = self.base_to_count[base] - def make_id_str(base, count): + def make_id_str(base: str, count: int) -> str: return "{base}{suffix}".format( base=base, suffix="" if count == 0 else "_" + str(count - 1)) @@ -67,13 +78,14 @@ def make_id_str(base, count): return sym.Symbol(id_str) - def __iter__(self): + def __iter__(self) -> _SymbolGenerator: return self - def next(self): + def next(self) -> sym.Symbol: return self() - __next__ = next + def __next__(self) -> sym.Symbol: + return self.next() # {{{ collection of assignments @@ -84,22 +96,29 @@ class SymbolicAssignmentCollection: a = 5*x b = a**2-k - In the above, *x* and *k* are external variables, and *a* and *b* - are variables managed by this object. + In the above, *x* and *k* are external variables, and *a* and *b* are + variables managed by this object. - This is a stateful object, but the only state changes allowed - are additions to *assignments*, and corresponding updates of - its lookup tables. + This is a stateful object, but the only state changes allowed are additions + to *assignments*, and corresponding updates of its lookup tables. - Note that user code is *only* allowed to hold on to *names* generated - by this class, but not expressions using names defined in this collection. + Note that user code is *only* allowed to hold on to *names* generated by + this class, but not expressions using names defined in this collection. + + .. autoattribute:: assignments + .. automethod:: add_assignment + .. automethod:: assign_unique + .. automethod:: assign_temp + .. automethod:: run_global_cse """ - def __init__(self, assignments=None): - """ - :arg assignments: mapping from *var_name* to expression - """ + assignments: dict[str, sym.Basic] + """A mapping from *var_name* to expressions.""" + reversed_assignments: dict[sym.Basic, str] + symbol_generator: _SymbolGenerator + all_dependencies_cache: dict[str, set[sym.Symbol]] + def __init__(self, assignments: dict[str, sym.Basic] | None = None) -> None: if assignments is None: assignments = {} @@ -109,13 +128,15 @@ def __init__(self, assignments=None): self.symbol_generator = _SymbolGenerator(self.assignments) self.all_dependencies_cache = {} - def __str__(self): + @override + def __str__(self) -> str: return "\n".join( f"{name} <- {expr}" for name, expr in self.assignments.items()) - def get_all_dependencies(self, var_name): + def get_all_dependencies(self, var_name: str) -> set[sym.Symbol]: """Including recursive dependencies.""" + try: return self.all_dependencies_cache[var_name] except KeyError: @@ -124,7 +145,7 @@ def get_all_dependencies(self, var_name): if var_name not in self.assignments: return set() - result = set() + result: set[sym.Symbol] = set() for dep in self.assignments[var_name].atoms(): if not isinstance(dep, sym.Symbol): continue @@ -138,13 +159,14 @@ def get_all_dependencies(self, var_name): self.all_dependencies_cache[var_name] = result return result - def add_assignment(self, name, expr, root_name=None, wrt_set=None, - retain_name=True): + def add_assignment(self, + name: str, + expr: sym.Basic, + root_name: str | None = None, + retain_name: bool = True) -> str: assert isinstance(name, str) assert name not in self.assignments - if wrt_set is None: - wrt_set = frozenset() if root_name is None: root_name = name @@ -158,7 +180,7 @@ def add_assignment(self, name, expr, root_name=None, wrt_set=None, return name - def assign_unique(self, name_base, expr): + def assign_unique(self, name_base: str, expr: sym.Basic) -> str: """Assign *expr* to a new variable whose name is based on *name_base*. Return the new variable name. """ @@ -166,7 +188,7 @@ def assign_unique(self, name_base, expr): return self.add_assignment(new_name, expr) - def assign_temp(self, name_base, expr): + def assign_temp(self, name_base: str, expr: sym.Basic) -> str: """If *expr* is mapped to a existing variable, then return the existing variable or assign *expr* to a new variable whose name is based on *name_base*. Return the variable name *expr* is mapped to in either case. @@ -174,7 +196,18 @@ def assign_temp(self, name_base, expr): new_name = self.symbol_generator(name_base).name return self.add_assignment(new_name, expr, retain_name=False) - def run_global_cse(self, extra_exprs=None): + @overload + def run_global_cse(self, extra_exprs: None = None) -> Self: ... + + @overload + def run_global_cse(self, + extra_exprs: Sequence[sym.Expr] + ) -> tuple[Self, Sequence[sym.Basic]]: ... + + def run_global_cse(self, + extra_exprs: Sequence[sym.Expr] | None = None + ) -> tuple[Self, Sequence[sym.Basic]] | Self: + orig_extra_exprs = extra_exprs if extra_exprs is None: extra_exprs = [] @@ -191,34 +224,35 @@ def run_global_cse(self, extra_exprs=None): # Uses maxima to verify. # - sym.cse: The sympy thing. # - sumpy.cse.cse: Based on sympy, designed to go faster. - #from sumpy.symbolic import checked_cse + # from sumpy.symbolic import checked_cse from sumpy.cse import cse - new_assignments, new_exprs = cse(assign_exprs + extra_exprs, + new_cse_assignments, new_exprs = cse( + [*assign_exprs, *extra_exprs], symbols=self.symbol_generator) new_assign_exprs = new_exprs[:len(assign_exprs)] new_extra_exprs = new_exprs[len(assign_exprs):] - for name, new_expr in zip(assign_names, new_assign_exprs): - self.assignments[name] = new_expr + result_assignments: dict[str, sym.Basic] = {} - for name, value in new_assignments: + for name, value in new_cse_assignments: assert isinstance(name, sym.Symbol) - self.add_assignment(name.name, value) - - for name, new_expr in zip(assign_names, new_assign_exprs): - # We want the assignment collection to be ordered correctly - # to make it easier for loopy to schedule. - # Deleting the original assignments and adding them again - # makes them occur after the CSE'd expression preserving - # the order of operations. - del self.assignments[name] - self.assignments[name] = new_expr - - logger.info("common subexpression elimination: done after {dur:.2f} s" - .format(dur=time.time() - start_time)) - return new_extra_exprs + result_assignments[name.name] = value + + result_assignments = { + **result_assignments, + **dict(zip(assign_names, new_assign_exprs, strict=True)), + } + + logger.info("common subexpression elimination: done after %.2f s", + time.time() - start_time) + + result = type(self)(result_assignments) + if orig_extra_exprs is None: + return result + else: + return result, new_extra_exprs # }}} diff --git a/sumpy/codegen.py b/sumpy/codegen.py index ef7e059a1..bc72e8d14 100644 --- a/sumpy/codegen.py +++ b/sumpy/codegen.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2012 Andreas Kloeckner" __license__ = """ @@ -20,21 +23,43 @@ THE SOFTWARE. """ - +import logging import re +from abc import ABC +from typing import TYPE_CHECKING import numpy as np -import loopy as lp -from loopy.kernel.instruction import make_assignment +from constantdict import constantdict +from typing_extensions import override -from pymbolic.mapper import IdentityMapper, CSECachingMapperMixin +import loopy as lp import pymbolic.primitives as prim - +from loopy.kernel.instruction import Assignment, CallInstruction, make_assignment +from pymbolic.mapper import CSECachingMapperMixin, IdentityMapper, P +from pymbolic.mapper.flattener import FlattenMapper +from pymbolic.typing import ArithmeticExpression, Expression from pytools import memoize_method -from sumpy.symbolic import (SympyToPymbolicMapper as SympyToPymbolicMapperBase) +import sumpy.symbolic as sym + + +if TYPE_CHECKING: + from collections.abc import ( + Callable, + Iterable, + Iterator, + Mapping, + Sequence, + Set as AbstractSet, + ) + + from numpy.typing import DTypeLike + + from loopy.target import TargetBase + from loopy.translation_unit import CallablesInferenceContext + from loopy.types import LoopyType + -import logging logger = logging.getLogger(__name__) @@ -45,27 +70,30 @@ .. autoclass:: SympyToPymbolicMapper .. autofunction:: to_loopy_insns - """ +def wrap_in_cse(expr: Expression, + prefix: str | None = None) -> prim.CommonSubexpression: + return prim.make_common_subexpression(expr, prefix, wrap_vars=False) + + # {{{ sympy -> pymbolic mapper -import sumpy.symbolic as sym _SPECIAL_FUNCTION_NAMES = frozenset(dir(sym.functions)) -class SympyToPymbolicMapper(SympyToPymbolicMapperBase): - - def not_supported(self, expr): +class SympyToPymbolicMapper(sym.SympyToPymbolicMapper): + @override + def not_supported(self, expr: object) -> Expression: if isinstance(expr, int): return expr elif getattr(expr, "is_Function", False): - func_name = SympyToPymbolicMapperBase.function_name(self, expr) + func_name = sym.SympyToPymbolicMapper.function_name(self, expr) return prim.Variable(func_name)( *tuple(self.rec(arg) for arg in expr.args)) else: - return SympyToPymbolicMapperBase.not_supported(self, expr) + return sym.SympyToPymbolicMapper.not_supported(self, expr) # }}} @@ -112,10 +140,17 @@ def not_supported(self, expr): class BesselJvvp1(lp.ScalarCallable): - def with_types(self, arg_id_to_dtype, clbl_inf_ctx): + @override + def with_types(self, + arg_id_to_dtype: Mapping[int | str, LoopyType], + clbl_inf_ctx: CallablesInferenceContext, + ) -> tuple[BesselJvvp1, CallablesInferenceContext]: from loopy.types import NumpyType for i in arg_id_to_dtype: + if isinstance(i, str): + raise TypeError(f"{self.name} cannot handle keyword arguments") + if not (-2 <= i <= 1): raise TypeError(f"{self.name} can only take 2 arguments.") @@ -132,37 +167,44 @@ def with_types(self, arg_id_to_dtype, clbl_inf_ctx): if z_dtype.numpy_dtype.kind == "c": return (self.copy(name_in_target="bessel_jv_two_complex", - arg_id_to_dtype={ + arg_id_to_dtype=constantdict({ -2: NumpyType(np.complex128), -1: NumpyType(np.complex128), 0: NumpyType(np.int32), 1: NumpyType(np.complex128), - }), + })), clbl_inf_ctx) else: return (self.copy(name_in_target="bessel_jv_two", - arg_id_to_dtype={ + arg_id_to_dtype=constantdict({ -2: NumpyType(np.float64), -1: NumpyType(np.float64), 0: NumpyType(np.int32), 1: NumpyType(np.float64), - }), + })), clbl_inf_ctx) - def generate_preambles(self, target): - from loopy import PyOpenCLTarget - if not isinstance(target, PyOpenCLTarget): + @override + def generate_preambles(self, target: TargetBase) -> Iterator[tuple[str, str]]: + if not isinstance(target, lp.PyOpenCLTarget): raise NotImplementedError("Only the PyOpenCLTarget is supported as" "of now.") yield ("40-sumpy-bessel", BESSEL_PREAMBLE) -class Hankel1_01(lp.ScalarCallable): # noqa: N801 - def with_types(self, arg_id_to_dtype, clbl_inf_ctx): +class Hankel1_01(lp.ScalarCallable): # ruff:ignore[invalid-class-name] + @override + def with_types(self, + arg_id_to_dtype: Mapping[int | str, LoopyType], + clbl_inf_ctx: CallablesInferenceContext, + ) -> tuple[Hankel1_01, CallablesInferenceContext]: from loopy.types import NumpyType for i in arg_id_to_dtype: + if isinstance(i, str): + raise TypeError(f"{self.name} cannot handle keyword arguments") + if not (-2 <= i <= 0): raise TypeError(f"{self.name} can only take one argument.") @@ -174,36 +216,43 @@ def with_types(self, arg_id_to_dtype, clbl_inf_ctx): if z_dtype.numpy_dtype.kind == "c": return (self.copy(name_in_target="hank1_01_complex", - arg_id_to_dtype={ + arg_id_to_dtype=constantdict({ -2: NumpyType(np.complex128), -1: NumpyType(np.complex128), 0: NumpyType(np.complex128), - }), + })), clbl_inf_ctx) else: return (self.copy(name_in_target="hank1_01", - arg_id_to_dtype={ + arg_id_to_dtype=constantdict({ -2: NumpyType(np.complex128), -1: NumpyType(np.complex128), 0: NumpyType(np.float64), - }), + })), clbl_inf_ctx) - def generate_preambles(self, target): - from loopy import PyOpenCLTarget - if not isinstance(target, PyOpenCLTarget): + @override + def generate_preambles(self, target: TargetBase) -> Iterator[tuple[str, str]]: + if not isinstance(target, lp.PyOpenCLTarget): raise NotImplementedError("Only the PyOpenCLTarget is supported as" "of now.") yield ("50-sumpy-hankel", HANKEL_PREAMBLE) -def register_bessel_callables(loopy_knl): - from sumpy.codegen import BesselJvvp1, Hankel1_01 - loopy_knl = lp.register_callable(loopy_knl, "bessel_jvvp1", +def register_bessel_callables(loopy_knl: lp.TranslationUnit) -> lp.TranslationUnit: + if "bessel_jvvp1" not in loopy_knl.callables_table: + loopy_knl = lp.register_callable( + loopy_knl, + "bessel_jvvp1", BesselJvvp1("bessel_jvvp1")) - loopy_knl = lp.register_callable(loopy_knl, "hank1_01", + + if "hank1_01" not in loopy_knl.callables_table: + loopy_knl = lp.register_callable( + loopy_knl, + "hank1_01", Hankel1_01("hank1_01")) + return loopy_knl # }}} @@ -211,47 +260,57 @@ def register_bessel_callables(loopy_knl): # {{{ custom mapper base classes -class CSECachingIdentityMapper(IdentityMapper, CSECachingMapperMixin): +class CSECachingIdentityMapper(IdentityMapper[P], + CSECachingMapperMixin[Expression, P], + ABC): pass -class CallExternalRecMapper(IdentityMapper): - def rec(self, expr, rec_self=None, *args, **kwargs): - if rec_self: - return rec_self.rec(expr, *args, **kwargs) - else: - return super().rec(expr, *args, **kwargs) - # }}} # {{{ bessel handling -class BesselTopOrderGatherer(CSECachingIdentityMapper, CallExternalRecMapper): +class BesselTopOrderGatherer(CSECachingIdentityMapper[P]): """This mapper walks the expression tree to find the highest-order Bessel J being used, so that all other Js can be computed by the (stable) downward recurrence. """ - def __init__(self): + + bessel_j_arg_to_top_order: dict[Expression, int] + + def __init__(self) -> None: self.bessel_j_arg_to_top_order = {} - def map_call(self, expr, rec_self=None, *args): - if isinstance(expr.function, prim.Variable) \ - and expr.function.name == "bessel_j": + @override + def map_call(self, + expr: prim.Call, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: + function = expr.function + if isinstance(function, prim.Variable) and function.name == "bessel_j": order, arg = expr.parameters - self.rec(arg) + self.rec(arg, *args, **kwargs) + assert isinstance(order, int) self.bessel_j_arg_to_top_order[arg] = max( self.bessel_j_arg_to_top_order.get(arg, 0), abs(order)) - return CSECachingIdentityMapper.map_call(rec_self or self, - expr, rec_self, *args) - map_common_subexpression_uncached = IdentityMapper.map_common_subexpression + return super().map_call(expr, *args, **kwargs) + + @override + def map_common_subexpression_uncached( + self, + expr: prim.CommonSubexpression, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: + return IdentityMapper.map_common_subexpression(self, expr, *args, **kwargs) -class BesselDerivativeReplacer(CSECachingIdentityMapper, CallExternalRecMapper): - def map_call(self, expr, rec_self=None, *args): +class BesselDerivativeReplacer(CSECachingIdentityMapper[P]): + @override + def map_call(self, + expr: prim.Call, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: call = expr if (isinstance(call.function, prim.Variable) @@ -260,67 +319,95 @@ def map_call(self, expr, rec_self=None, *args): function = prim.Variable("hankel_1") else: function = prim.Variable("bessel_j") - order, arg, n_derivs = call.parameters - import sympy as sym + order, arg, k = call.parameters + assert isinstance(order, int) + assert isinstance(k, int) # AS (9.1.31) # https://dlmf.nist.gov/10.6.7 - if order >= 0: - order_str = str(order) + if order >= 0: # ruff:ignore[if-else-block-instead-of-if-exp] + order_str = f"{order}" else: - order_str = "m"+str(-order) - k = n_derivs + order_str = f"m{-order}" + + from math import comb return prim.CommonSubexpression( - 2**(-k)*sum( - (-1)**idx*int(sym.binomial(k, idx)) * function(i, arg) + 2.0**(-k) * sum( + (-1)**idx * comb(k, idx) * function(i, arg) for idx, i in enumerate(range(order-k, order+k+1, 2))), - f"d{n_derivs}_{function.name}_{order_str}") + f"d{k}_{function.name}_{order_str}", + scope=prim.cse_scope.EVALUATION) else: - return CSECachingIdentityMapper.map_call( - rec_self or self, expr, rec_self, *args) + return super().map_call(expr, *args, **kwargs) + + @override + def map_common_subexpression_uncached( + self, + expr: prim.CommonSubexpression, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: + return IdentityMapper.map_common_subexpression(self, expr, *args, **kwargs) + - map_common_subexpression_uncached = IdentityMapper.map_common_subexpression +class BesselSubstitutor(CSECachingIdentityMapper[P]): + name_gen: Callable[[str], str] + bessel_j_arg_to_top_order: dict[Expression, int] + assignments: list[Assignment | CallInstruction] + cse_cache: dict[Expression, prim.CommonSubexpression] -class BesselSubstitutor(CSECachingIdentityMapper): - def __init__(self, name_gen, bessel_j_arg_to_top_order, assignments): + def __init__(self, + name_gen: Callable[[str], str], + bessel_j_arg_to_top_order: dict[Expression, int]) -> None: self.name_gen = name_gen self.bessel_j_arg_to_top_order = bessel_j_arg_to_top_order self.cse_cache = {} - self.assignments = assignments + self.assignments = [] - def map_call(self, expr, *args): + @override + def map_call(self, expr: prim.Call, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: if isinstance(expr.function, prim.Variable): name = expr.function.name if name == "bessel_j": order, arg = expr.parameters - return self.bessel_j(order, self.rec(arg, *args)) + assert isinstance(order, int) + assert prim.is_arithmetic_expression(arg) + + return self.bessel_j(order, self.rec_arith(arg, *args, **kwargs)) elif name == "hankel_1": order, arg = expr.parameters - return self.hankel_1(order, self.rec(arg, *args)) + assert isinstance(order, int) + assert prim.is_arithmetic_expression(arg) + + return self.hankel_1(order, self.rec_arith(arg, *args, **kwargs)) - return super().map_call(expr) + return super().map_call(expr, *args, **kwargs) - def wrap_in_cse(self, expr, prefix): - cse = prim.wrap_in_cse(expr, prefix) + def wrap_in_cse(self, expr: Expression, prefix: str) -> prim.CommonSubexpression: + cse = wrap_in_cse(expr, prefix) return self.cse_cache.setdefault(expr, cse) # {{{ bessel implementation @memoize_method - def bessel_jv_two(self, order, arg): - name_om1 = self.name_gen(f"bessel_{order - 1}") - name_o = self.name_gen(f"bessel_{order}") + def bessel_jv_two( + self, order: int, arg: Expression + ) -> tuple[prim.Variable, prim.Variable]: + om0 = prim.Variable(self.name_gen(f"bessel_{order}")) + om1 = prim.Variable(self.name_gen(f"bessel_{order - 1}")) + self.assignments.append( make_assignment( - (prim.Variable(name_om1), prim.Variable(name_o),), + (om1, om0), prim.Variable("bessel_jvvp1")(order, arg), temp_var_types=(lp.Optional(None),)*2)) - return prim.Variable(name_om1), prim.Variable(name_o) + return om1, om0 @memoize_method - def bessel_j(self, order, arg): + def bessel_j( + self, order: int, arg: ArithmeticExpression + ) -> ArithmeticExpression: top_order = self.bessel_j_arg_to_top_order[arg] if order == top_order: return self.bessel_jv_two(top_order-1, arg)[1] @@ -328,7 +415,7 @@ def bessel_j(self, order, arg): return self.bessel_jv_two(top_order-1, arg)[0] elif order < 0: return self.wrap_in_cse( - (-1)**order*self.bessel_j(-order, arg), + (-1.0)**order*self.bessel_j(-order, arg), f"bessel_j_neg{-order}") else: assert abs(order) < top_order @@ -344,18 +431,20 @@ def bessel_j(self, order, arg): # {{{ hankel implementation @memoize_method - def hank1_01(self, arg): - name_0 = self.name_gen("hank1_0") - name_1 = self.name_gen("hank1_1") + def hank1_01(self, arg: Expression) -> tuple[prim.Variable, prim.Variable]: + hank1_0 = prim.Variable(self.name_gen("hank1_0")) + hank1_1 = prim.Variable(self.name_gen("hank1_1")) + self.assignments.append( make_assignment( - (prim.Variable(name_0), prim.Variable(name_1),), + (hank1_0, hank1_1), prim.Variable("hank1_01")(arg), temp_var_types=(lp.Optional(None),)*2)) - return prim.Variable(name_0), prim.Variable(name_1) + + return hank1_0, hank1_1 @memoize_method - def hankel_1(self, order, arg): + def hankel_1(self, order: int, arg: ArithmeticExpression) -> ArithmeticExpression: if order == 0: return self.hank1_01(arg)[0] elif order == 1: @@ -364,7 +453,7 @@ def hankel_1(self, order, arg): # AS (9.1.6) nu = -order return self.wrap_in_cse( - (-1) ** nu * self.hankel_1(nu, arg), + (-1.0) ** nu * self.hankel_1(nu, arg), f"hank1_neg{nu}") elif order > 1: # AS (9.1.27) @@ -377,82 +466,100 @@ def hankel_1(self, order, arg): # }}} - map_common_subexpression_uncached = IdentityMapper.map_common_subexpression + @override + def map_common_subexpression_uncached( + self, + expr: prim.CommonSubexpression, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: + return IdentityMapper.map_common_subexpression(self, expr, *args, **kwargs) # }}} # {{{ power rewriter -class PowerRewriter(CSECachingIdentityMapper, CallExternalRecMapper): - def map_power(self, expr, rec_self=None, *args): +class PowerRewriter(CSECachingIdentityMapper[P]): + @override + def map_power(self, + expr: prim.Power, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: exp = expr.exponent - if isinstance(exp, int): - new_base = prim.wrap_in_cse(expr.base) + new_base = wrap_in_cse(expr.base) + if isinstance(exp, int): if exp > 2 and exp % 2 == 0: - square = prim.wrap_in_cse(new_base*new_base) - return self.rec(prim.wrap_in_cse(square**(exp//2)), - rec_self, *args) + square = wrap_in_cse(new_base*new_base) + return self.rec(wrap_in_cse(square**(exp//2)), *args, **kwargs) elif exp == 2: return new_base * new_base elif exp > 1 and exp % 2 == 1: - square = prim.wrap_in_cse(new_base*new_base) - return self.rec(prim.wrap_in_cse(square**((exp-1)//2))*new_base, - rec_self, *args) + square = wrap_in_cse(new_base*new_base) + return self.rec(wrap_in_cse(square**((exp-1)//2))*new_base, + *args, **kwargs) elif exp == 1: return new_base elif exp < 0: - return self.rec((1/new_base)**(-exp), rec_self, *args) + return self.rec((1/new_base)**(-exp), *args, **kwargs) - if (isinstance(expr.exponent, prim.Quotient) - and isinstance(expr.exponent.numerator, int) - and isinstance(expr.exponent.denominator, int)): - - p, q = expr.exponent.numerator, expr.exponent.denominator + if (isinstance(exp, prim.Quotient) + and isinstance(exp.numerator, int) + and isinstance(exp.denominator, int)): + p, q = exp.numerator, exp.denominator if q < 0: q *= -1 p *= -1 if q == 1: - return self.rec(new_base**p, rec_self, *args) + return self.rec(new_base**p, *args, **kwargs) if q == 2: assert p != 0 if p > 0: - orig_base = prim.wrap_in_cse(expr.base) - new_base = prim.wrap_in_cse(prim.Variable("sqrt")(orig_base)) + orig_base = wrap_in_cse(expr.base) + new_base = wrap_in_cse(prim.Variable("sqrt")(orig_base)) else: - new_base = prim.wrap_in_cse(prim.Variable("rsqrt")(expr.base)) + new_base = wrap_in_cse(prim.Variable("rsqrt")(expr.base)) p *= -1 - return self.rec(new_base**p, rec_self, *args) + return self.rec(new_base**p, *args, **kwargs) - return CSECachingIdentityMapper.map_power(rec_self or self, expr) + return super().map_power(expr, *args, **kwargs) - map_common_subexpression_uncached = IdentityMapper.map_common_subexpression + @override + def map_common_subexpression_uncached( + self, + expr: prim.CommonSubexpression, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: + return IdentityMapper.map_common_subexpression(self, expr, *args, **kwargs) # }}} # {{{ convert big integers into floats -from loopy.tools import is_integer - -class BigIntegerKiller(CSECachingIdentityMapper, CallExternalRecMapper): +class BigIntegerKiller(CSECachingIdentityMapper[P]): + warn: bool + float_type: type[np.floating] + iinfo: np.iinfo - def __init__(self, warn_on_digit_loss=True, int_type=np.int64, - float_type=np.float64): + def __init__(self, + warn_on_digit_loss: bool = True, + int_type: type[np.integer] = np.int64, + float_type: type[np.floating] = np.float64) -> None: super().__init__() self.warn = warn_on_digit_loss self.float_type = float_type self.iinfo = np.iinfo(int_type) - def map_constant(self, expr, *args): + @override + def map_constant(self, expr: object, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: """Convert integer values not within the range of `self.int_type` to float. """ + from loopy.typing import is_integer + if not is_integer(expr): return expr @@ -464,7 +571,7 @@ def map_constant(self, expr, *args): if int(expr_as_float) != int(expr): from warnings import warn warn(f"Converting '{expr}' to " - f"'{self.float_type.__name__}' loses digits") + f"'{self.float_type.__name__}' loses digits", stacklevel=1) # Suppress further warnings. self.warn = False @@ -472,30 +579,38 @@ def map_constant(self, expr, *args): return self.float_type(expr) - map_common_subexpression_uncached = IdentityMapper.map_common_subexpression + @override + def map_common_subexpression_uncached( + self, + expr: prim.CommonSubexpression, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: + return IdentityMapper.map_common_subexpression(self, expr, *args, **kwargs) # }}} # {{{ convert complex to np.complex -class ComplexRewriter(CSECachingIdentityMapper, CallExternalRecMapper): +class ComplexRewriter(CSECachingIdentityMapper[[]]): + complex_dtype: np.dtype[np.complexfloating] | None - def __init__(self, complex_dtype=None): + def __init__(self, + complex_dtype: np.dtype[np.complexfloating] | None = None) -> None: super().__init__() self.complex_dtype = complex_dtype - def map_constant(self, expr, rec_self=None, *args, **kwargs): + @override + def map_constant(self, expr: object, /) -> Expression: """Convert complex values to numpy types """ if not isinstance(expr, (complex, np.complex64, np.complex128)): - return IdentityMapper.map_constant(rec_self or self, expr, - rec_self=rec_self, *args, **kwargs) + return super().map_constant(expr) complex_dtype = self.complex_dtype if complex_dtype is None: - if complex(np.complex64(expr)) == expr: + if complex(np.complex64(expr)) == expr: # ruff:ignore[float-equality-comparison] return np.complex64(expr) + complex_dtype = np.complex128 if isinstance(complex_dtype, np.dtype): @@ -503,50 +618,69 @@ def map_constant(self, expr, rec_self=None, *args, **kwargs): else: return complex_dtype(expr) - map_common_subexpression_uncached = IdentityMapper.map_common_subexpression + @override + def map_common_subexpression_uncached( + self, + expr: prim.CommonSubexpression, /) -> Expression: + return IdentityMapper.map_common_subexpression(self, expr) # }}} # {{{ vector component rewriter -INDEXED_VAR_RE = re.compile("^([a-zA-Z_]+)([0-9]+)$") +INDEXED_VAR_RE = re.compile(r"^([a-zA-Z_]+)([0-9]+)$") -class VectorComponentRewriter(CSECachingIdentityMapper, CallExternalRecMapper): +class VectorComponentRewriter(CSECachingIdentityMapper[P]): """For names in name_whitelist, turn ``a3`` into ``a[3]``.""" - def __init__(self, name_whitelist=frozenset()): + name_whitelist: frozenset[str] + + def __init__(self, name_whitelist: frozenset[str] | None = None) -> None: + if name_whitelist is None: + name_whitelist = frozenset() + self.name_whitelist = name_whitelist - def map_variable(self, expr, *args): + @override + def map_variable(self, expr: prim.Variable, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: match_obj = INDEXED_VAR_RE.match(expr.name) if match_obj is not None: name = match_obj.group(1) subscript = int(match_obj.group(2)) + if name in self.name_whitelist: - return prim.Variable(name).index(subscript) + return prim.Variable(name)[subscript] else: return expr else: return expr - map_common_subexpression_uncached = IdentityMapper.map_common_subexpression + @override + def map_common_subexpression_uncached( + self, + expr: prim.CommonSubexpression, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: + return IdentityMapper.map_common_subexpression(self, expr, *args, **kwargs) # }}} # {{{ sum sign grouper -class SumSignGrouper(CSECachingIdentityMapper, CallExternalRecMapper): +class SumSignGrouper(CSECachingIdentityMapper[P]): """Anti-cancellation cargo-cultism.""" - def map_sum(self, expr, *args): - first_group = [] - second_group = [] + @override + def map_sum(self, expr: prim.Sum, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: + first_group: list[ArithmeticExpression] = [] + second_group: list[ArithmeticExpression] = [] for orig_child in expr.children: - child = self.rec(orig_child, *args) + child = self.rec_arith(orig_child, *args, **kwargs) tchild = child if isinstance(tchild, prim.CommonSubexpression): tchild = tchild.child @@ -565,100 +699,69 @@ def map_sum(self, expr, *args): first_group.append(child) new_children = tuple(first_group + second_group) - if len(new_children) == len(expr.children) and \ - all(child is orig_child for child, orig_child in - zip(new_children, expr.children)): + if (len(new_children) == len(expr.children) + and all(child is orig_child for child, orig_child + in zip(new_children, expr.children, strict=True))): return expr - return prim.Sum(tuple(first_group+second_group)) - map_common_subexpression_uncached = IdentityMapper.map_common_subexpression + return prim.Sum(tuple(first_group + second_group)) + + @override + def map_common_subexpression_uncached( + self, + expr: prim.CommonSubexpression, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: + return IdentityMapper.map_common_subexpression(self, expr, *args, **kwargs) # }}} -class MathConstantRewriter(CSECachingIdentityMapper, CallExternalRecMapper): - def map_variable(self, expr, *args): +class MathConstantRewriter(CSECachingIdentityMapper[P]): + @override + def map_variable(self, expr: prim.Variable, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: if expr.name == "pi": return prim.Variable("M_PI") else: return expr - map_common_subexpression_uncached = IdentityMapper.map_common_subexpression - - -# {{{ combine mappers - -def combine_mappers(*mappers): - """Returns a mapper that combines the work of several other mappers. For - this to work, the mappers need to be instances of - :class:`sumpy.codegen.CallExternalRecMapper`. When calling parent class - methods, the mappers need to use the (first) argument *rec_self* as the - instance passed to the *map_* method. *rec_self* is a (custom-generated) - *CombinedMapper* instance which dispatches the object to all the mappers - given. The mappers need to commute and be idempotent. - """ - from collections import defaultdict - all_methods = defaultdict(list) - base_classes = [CSECachingMapperMixin, IdentityMapper] - for mapper in mappers: - assert isinstance(mapper, CallExternalRecMapper) - for method_name in dir(type(mapper)): - if not method_name.startswith("map_"): - continue - if method_name == "map_common_subexpression_uncached": - continue - method = getattr(type(mapper), method_name) - method_equals_base_class_method = False - for base_class in base_classes: - base_class_method = getattr(base_class, method_name, None) - if base_class_method is not None: - method_equals_base_class_method = (base_class_method == method) - break - else: - raise RuntimeError(f"Unknown mapping method {method_name}") - - if method_equals_base_class_method: - continue - all_methods[method_name].append((mapper, method)) - - class CombinedMapper(CSECachingIdentityMapper): - def __init__(self, all_methods): - self.all_methods = all_methods - map_common_subexpression_uncached = \ - IdentityMapper.map_common_subexpression - - def _map(method_name, self, expr, rec_self=None, *args): - if method_name not in self.all_methods: - return getattr(IdentityMapper, method_name)(self, expr) - for mapper, method in self.all_methods[method_name]: - new_expr = method(mapper, expr, self) - if new_expr is not expr: - # Re-traverse the whole thing from the get-go. - return self.rec(new_expr) - return expr - - from functools import partial - import types - combine_mapper = CombinedMapper(all_methods) - for method_name in all_methods.keys(): - setattr(combine_mapper, method_name, - types.MethodType(partial(_map, method_name), combine_mapper)) - return combine_mapper - -# }}} + @override + def map_common_subexpression_uncached( + self, + expr: prim.CommonSubexpression, /, + *args: P.args, **kwargs: P.kwargs) -> Expression: + return IdentityMapper.map_common_subexpression(self, expr, *args, **kwargs) # {{{ to-loopy conversion -def to_loopy_insns(assignments, vector_names=frozenset(), pymbolic_expr_maps=(), - complex_dtype=None, retain_names=frozenset()): +def to_loopy_insns( + assignments: Iterable[tuple[str, sym.Basic]], + vector_names: AbstractSet[str] | None = None, + pymbolic_expr_maps: Sequence[Callable[[Expression], Expression]] = (), + complex_dtype: DTypeLike | None = None, + retain_names: AbstractSet[str] | None = None, + ) -> Sequence[Assignment | CallInstruction]: + if vector_names is None: + vector_names = frozenset() + vector_names = frozenset(vector_names) + + if retain_names is None: + retain_names = frozenset() + retain_names = frozenset(retain_names) + + if complex_dtype is None: + complex_dtype = np.dtype(np.complex128) + complex_dtype = np.dtype(complex_dtype) + logger.info("loopy instruction generation: start") assignments = list(assignments) # convert from sympy sympy_conv = SympyToPymbolicMapper() - assignments = [(name, sympy_conv(expr)) for name, expr in assignments] + pymbolic_assignments = [(name, sympy_conv(expr)) for name, expr in assignments] + flat = FlattenMapper() bdr = BesselDerivativeReplacer() btog = BesselTopOrderGatherer() vcr = VectorComponentRewriter(vector_names) @@ -667,46 +770,44 @@ def to_loopy_insns(assignments, vector_names=frozenset(), pymbolic_expr_maps=(), bik = BigIntegerKiller() cmr = ComplexRewriter(complex_dtype) - cmb_mapper = combine_mappers(bdr, btog, vcr, pwr, ssg, bik, cmr) - - if 0: - # https://github.com/inducer/sumpy/pull/40#issuecomment-852635444 - cmb_mapper = combine_mappers(bdr, btog, vcr, pwr, ssg, bik, cmr) - else: - def cmb_mapper(expr): - expr = bdr(expr) - expr = vcr(expr) - expr = pwr(expr) - expr = ssg(expr) - expr = bik(expr) - expr = cmr(expr) - expr = btog(expr) - return expr - - def convert_expr(name, expr): + def cmb_mapper(expr: Expression, /) -> Expression: + expr = flat(expr) + expr = bdr(expr) + expr = vcr(expr) + expr = pwr(expr) + expr = ssg(expr) + expr = bik(expr) + expr = cmr(expr) + return btog(expr) + + def convert_expr(name: str, expr: Expression) -> Expression: logger.debug("generate expression for: %s", name) expr = cmb_mapper(expr) for m in pymbolic_expr_maps: expr = m(expr) + return expr - assignments = [(name, convert_expr(name, expr)) for name, expr in assignments] - from pytools import UniqueNameGenerator - name_gen = UniqueNameGenerator({name for name, expr in assignments}) + pymbolic_assignments = [ + (name, convert_expr(name, expr)) for name, expr in pymbolic_assignments + ] - result = [] - bessel_sub = BesselSubstitutor( - name_gen, btog.bessel_j_arg_to_top_order, - result) + from pytools import UniqueNameGenerator + name_gen = UniqueNameGenerator({name for name, _expr in pymbolic_assignments}) - import loopy as lp from pytools import MinRecursionLimit + + result: list[Assignment | CallInstruction] = [] + bessel_sub = BesselSubstitutor(name_gen, btog.bessel_j_arg_to_top_order) + with MinRecursionLimit(3000): - for name, expr in assignments: + for name, expr in pymbolic_assignments: result.append(lp.Assignment(id=None, assignee=name, expression=bessel_sub(expr), temp_var_type=lp.Optional(None))) + result.extend(bessel_sub.assignments) + logger.info("loopy instruction generation: done") return result diff --git a/sumpy/cse.py b/sumpy/cse.py index 8c9776711..0186718b6 100644 --- a/sumpy/cse.py +++ b/sumpy/cse.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = """ Copyright (C) 2017 Matt Wala Copyright (C) 2006-2016 SymPy Development Team @@ -64,33 +67,45 @@ # }}} -from sumpy.symbolic import ( - Basic, Mul, Add, Pow, Symbol, _coeff_isneg, Derivative, Subs) -from sympy.utilities.iterables import numbered_symbols +from typing import TYPE_CHECKING, TypeAlias, cast + +from typing_extensions import override + +import sumpy.symbolic as sym + + try: from sympy.utilities.iterables import iterable except ImportError: # NOTE: deprecated and moved in sympy 1.10 from sympy.core.compatibility import iterable +if TYPE_CHECKING: + from collections.abc import Callable, Iterator, Sequence __doc__ = """ Common subexpression elimination -------------------------------- +.. autoclass:: OptimizationCallable +.. autoclass:: OptimizationPair .. autofunction:: cse - """ # Don't CSE child nodes of these classes. -CSE_NO_DESCEND_CLASSES = (Derivative, Subs) +CSE_NO_DESCEND_CLASSES = (sym.Derivative, sym.Subs) + +OptimizationCallable: TypeAlias = "Callable[[sym.Basic], sym.Basic]" +OptimizationPair: TypeAlias = "tuple[OptimizationCallable, OptimizationCallable]" # {{{ cse pre/postprocessing -def preprocess_for_cse(expr, optimizations): +def preprocess_for_cse( + expr: sym.Basic, optimizations: Sequence[OptimizationPair], + ) -> sym.Basic: """ Preprocess an expression to optimize for common subexpression elimination. @@ -106,7 +121,9 @@ def preprocess_for_cse(expr, optimizations): return expr -def postprocess_for_cse(expr, optimizations): +def postprocess_for_cse( + expr: sym.Basic, optimizations: Sequence[OptimizationPair], + ) -> sym.Basic: """ Postprocess an expression after common subexpression elimination to return the expression to canonical sympy form. @@ -227,8 +244,7 @@ def get_subset_candidates(self, argset, restrict_to_funcset=None): """ iarg = iter(argset) - indices = { - fi for fi in self.arg_to_funcset[next(iarg)]} + indices = set(self.arg_to_funcset[next(iarg)]) if restrict_to_funcset is not None: indices &= restrict_to_funcset @@ -255,16 +271,23 @@ def update_func_argset(self, func_i, new_argset): class Unevaluated: + func: type[sym.Basic] + args: Sequence[int | float | sym.Expr] - def __init__(self, func, args): + def __init__(self, + func: type[sym.Basic], + args: Sequence[int | float | sym.Expr]) -> None: self.func = func self.args = args - def __str__(self): + @override + def __str__(self) -> str: return "Uneval<{}>({})".format( self.func, ", ".join(str(a) for a in self.args)) - __repr__ = __str__ + @override + def __repr__(self) -> str: + return str(self) def match_common_args(func_class, funcs, opt_subs): @@ -358,7 +381,7 @@ def match_common_args(func_class, funcs, opt_subs): arg_tracker.stop_arg_tracking(i) -def opt_cse(exprs): +def opt_cse(exprs: Sequence[sym.Basic]) -> dict[sym.Basic, sym.Basic | Unevaluated]: """ Find optimization opportunities in Adds, Muls, Pows and negative coefficient Muls @@ -366,18 +389,20 @@ def opt_cse(exprs): :arg exprs: A list of sympy expressions: the expressions to optimize. :return: A dictionary of expression substitutions """ - opt_subs = dict() + opt_subs: dict[sym.Basic, sym.Basic | Unevaluated] = {} from sumpy.tools import OrderedSet - adds = OrderedSet() - muls = OrderedSet() + adds: OrderedSet[sym.Add] = OrderedSet() + muls: OrderedSet[sym.Mul] = OrderedSet() - seen_subexp = set() + seen_subexp: set[sym.Basic] = set() # {{{ look for optimization opportunities, clean up minus signs - def find_opts(expr): - if not isinstance(expr, Basic): + from sumpy.symbolic import _coeff_isneg + + def find_opts(expr: sym.Basic) -> None: + if not isinstance(expr, sym.Basic): return if expr.is_Atom: @@ -389,10 +414,11 @@ def find_opts(expr): if iterable(expr): for item in expr: find_opts(item) + return if expr in seen_subexp: - return expr + return seen_subexp.add(expr) @@ -400,31 +426,31 @@ def find_opts(expr): find_opts(arg) if _coeff_isneg(expr): - neg_expr = -expr + neg_expr = cast("sym.Expr", -expr) if not neg_expr.is_Atom: - opt_subs[expr] = Unevaluated(Mul, (-1, neg_expr)) + opt_subs[expr] = Unevaluated(sym.Mul, (-1, neg_expr)) seen_subexp.add(neg_expr) expr = neg_expr - if isinstance(expr, Mul): + if isinstance(expr, sym.Mul): muls.add(expr) - elif isinstance(expr, Add): + elif isinstance(expr, sym.Add): adds.add(expr) - elif isinstance(expr, Pow): + elif isinstance(expr, sym.Pow): base, exp = expr.args if _coeff_isneg(exp): - opt_subs[expr] = Unevaluated(Pow, (Pow(base, -exp), -1)) + opt_subs[expr] = Unevaluated(sym.Pow, (sym.Pow(base, -exp), -1)) # }}} for e in exprs: - if isinstance(e, Basic): + if isinstance(e, sym.Basic): find_opts(e) - match_common_args(Add, list(adds), opt_subs) - match_common_args(Mul, list(muls), opt_subs) + match_common_args(sym.Add, list(adds), opt_subs) + match_common_args(sym.Mul, list(muls), opt_subs) return opt_subs @@ -433,7 +459,11 @@ def find_opts(expr): # {{{ tree cse -def tree_cse(exprs, symbols, opt_subs=None): +def tree_cse(exprs: Sequence[sym.Basic], + symbols: Iterator[sym.Symbol], + opt_subs: dict[sym.Basic, sym.Basic | Unevaluated] | None = None, + ) -> tuple[Sequence[tuple[sym.Symbol, sym.Basic]], + Sequence[sym.Basic]]: """ Perform raw CSE on an expression tree, taking opt_subs into account. @@ -446,27 +476,25 @@ def tree_cse(exprs, symbols, opt_subs=None): :return: A pair (replacements, reduced exprs) """ if opt_subs is None: - opt_subs = dict() + opt_subs = {} # {{{ find repeated sub-expressions and used symbols - to_eliminate = set() + to_eliminate: set[sym.Basic | Unevaluated] = set() + seen_subexp: set[sym.Basic | Unevaluated] = set() + excluded_symbols: set[sym.Symbol] = set() - seen_subexp = set() - excluded_symbols = set() - - def find_repeated(expr): - if not isinstance(expr, (Basic, Unevaluated)): + def find_repeated(expr: sym.Basic | Unevaluated) -> None: + if not isinstance(expr, sym.Basic | Unevaluated): return - if isinstance(expr, Basic) and expr.is_Atom: - if expr.is_Symbol: + if isinstance(expr, sym.Basic) and expr.is_Atom: + if isinstance(expr, sym.Symbol): excluded_symbols.add(expr) return if iterable(expr): args = expr - else: if expr in seen_subexp: to_eliminate.add(expr) @@ -475,9 +503,10 @@ def find_repeated(expr): seen_subexp.add(expr) if expr in opt_subs: + assert isinstance(expr, sym.Basic) expr = opt_subs[expr] - if isinstance(expr, CSE_NO_DESCEND_CLASSES): + if isinstance(expr, CSE_NO_DESCEND_CLASSES): # ruff:ignore[if-else-block-instead-of-if-exp] args = () else: args = expr.args @@ -488,7 +517,7 @@ def find_repeated(expr): # }}} for e in exprs: - if isinstance(e, Basic): + if isinstance(e, sym.Basic): find_repeated(e) # {{{ rebuild tree @@ -496,12 +525,11 @@ def find_repeated(expr): # Remove symbols from the generator that conflict with names in the expressions. symbols = (symbol for symbol in symbols if symbol not in excluded_symbols) - replacements = [] + replacements: list[tuple[sym.Symbol, sym.Basic]] = [] + subs: dict[sym.Basic | Unevaluated, sym.Symbol] = {} - subs = dict() - - def rebuild(expr): - if not isinstance(expr, (Basic, Unevaluated)): + def rebuild(expr: sym.Basic | Unevaluated) -> sym.Basic | Unevaluated: + if not isinstance(expr, sym.Basic | Unevaluated): return expr if not expr.args: @@ -516,6 +544,7 @@ def rebuild(expr): orig_expr = expr if expr in opt_subs: + assert isinstance(expr, sym.Basic) expr = opt_subs[expr] new_expr = expr @@ -526,21 +555,21 @@ def rebuild(expr): if orig_expr in to_eliminate: try: - sym = next(symbols) + symb = next(symbols) except StopIteration: - raise ValueError("Symbols iterator ran out of symbols.") + raise ValueError("Symbols iterator ran out of symbols.") from None - subs[orig_expr] = sym - replacements.append((sym, new_expr)) - return sym + subs[orig_expr] = symb + replacements.append((symb, new_expr)) + return symb return new_expr # }}} - reduced_exprs = [] + reduced_exprs: list[sym.Basic] = [] for e in exprs: - if isinstance(e, Basic): + if isinstance(e, sym.Basic): # ruff:ignore[if-else-block-instead-of-if-exp] reduced_e = rebuild(e) else: reduced_e = e @@ -551,7 +580,11 @@ def rebuild(expr): # }}} -def cse(exprs, symbols=None, optimizations=None): +def cse(exprs: sym.Basic | Sequence[sym.Basic], + symbols: Iterator[sym.Symbol] | None = None, + optimizations: Sequence[OptimizationPair] | None = None, + ) -> tuple[Sequence[tuple[sym.Symbol, sym.Basic]], + Sequence[sym.Basic]]: """ Perform common subexpression elimination on an expression. @@ -571,9 +604,8 @@ def cse(exprs, symbols=None, optimizations=None): * ``reduced_exprs`` is a list of sympy expressions. This contains the reduced expressions with all of the replacements above. """ - if isinstance(exprs, Basic): + if isinstance(exprs, sym.Basic): exprs = [exprs] - exprs = list(exprs) if optimizations is None: @@ -583,7 +615,8 @@ def cse(exprs, symbols=None, optimizations=None): reduced_exprs = [preprocess_for_cse(e, optimizations) for e in exprs] if symbols is None: - symbols = numbered_symbols(cls=Symbol) + from sympy.utilities.iterables import numbered_symbols + symbols = numbered_symbols(cls=sym.Symbol) else: # In case we get passed an iterable with an __iter__ method instead of # an actual iterator. @@ -594,11 +627,12 @@ def cse(exprs, symbols=None, optimizations=None): # Main CSE algorithm. replacements, reduced_exprs = tree_cse(reduced_exprs, symbols, opt_subs) + replacements = list(replacements) # Postprocess the expressions to return the expressions to canonical form. - for i, (sym, subtree) in enumerate(replacements): + for i, (symb, subtree) in enumerate(replacements): subtree = postprocess_for_cse(subtree, optimizations) - replacements[i] = (sym, subtree) + replacements[i] = (symb, subtree) reduced_exprs = [postprocess_for_cse(e, optimizations) for e in reduced_exprs] return replacements, reduced_exprs diff --git a/sumpy/derivative_taker.py b/sumpy/derivative_taker.py new file mode 100644 index 000000000..5f0ebd5ad --- /dev/null +++ b/sumpy/derivative_taker.py @@ -0,0 +1,463 @@ +from __future__ import annotations + + +__copyright__ = """ +Copyright (C) 2012 Andreas Kloeckner +Copyright (C) 2020 Isuru Fernando +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +__doc__ = """ + + Derivative Taker + ================ + + .. autoclass:: ExprDerivativeTaker + .. autoclass:: LaplaceDerivativeTaker + .. autoclass:: RadialDerivativeTaker + .. autoclass:: HelmholtzDerivativeTaker + .. autoclass:: DifferentiatedExprDerivativeTaker +""" + +import logging +from dataclasses import dataclass, field +from typing import TYPE_CHECKING + +import numpy as np +from typing_extensions import override + +from pytools import memoize_method + +import sumpy.symbolic as sym +from sumpy.tools import add_mi, add_to_sac, sub_mi + + +if TYPE_CHECKING: + from collections.abc import Iterable, Sequence + + import sympy as sp + + from sumpy.assignment_collection import SymbolicAssignmentCollection + from sumpy.expansion.diff_op import MultiIndex + + +logger = logging.getLogger(__name__) + + +# {{{ ExprDerivativeTaker + +@dataclass(frozen=True) +class ExprDerivativeTaker: + r"""Facilitates the efficient computation of (potentially) high-order + derivatives of a given :mod:`sympy` expression *expr* while attempting + to maximize the number of common subexpressions generated. + This class defines the interface and realizes a baseline implementation. + More specialized implementations may offer better efficiency for special + cases. + + A class to take scaled derivatives of the symbolic expression + expr w.r.t. variables var_list and the scaling parameter rscale. + + Consider a Taylor multipole expansion: + + .. math:: + f (x - y) = \sum_{i = 0}^{\infty} (\partial_y^i f) (x - y) \big|_{y = c} + \frac{(y - c)^i}{i!} . + + Now suppose we would like to use a scaled version :math:`g` of the + kernel :math:`f`: + .. math:: + + \begin{eqnarray*} + f (x) & = & g (x / \alpha),\\ + f^{(i)} (x) & = & \frac{1}{\alpha^i} g^{(i)} (x / \alpha) . + \end{eqnarray*} + + where :math:`\alpha` is chosen to be on a length scale similar to + :math:`x` (for example by choosing :math:`\alpha` proporitional to the + size of the box for which the expansion is intended) so that :math:`x / + \alpha` is roughly of unit magnitude, to avoid arithmetic issues with + small arguments. This yields + .. math:: + + f (x - y) = \sum_{i = 0}^{\infty} (\partial_y^i g) + \left( \frac{x - y}{\alpha} \right) \Bigg|_{y = c} + \cdot + \frac{(y - c)^i}{\alpha^i \cdot i!}. + + Observe that the :math:`(y - c)` term is now scaled to unit magnitude, + as is the argument of :math:`g`. + With :math:`\xi = x / \alpha`, we find + .. math:: + + \begin{eqnarray*} + g (\xi) & = & f (\alpha \xi),\\ + g^{(i)} (\xi) & = & \alpha^i f^{(i)} (\alpha \xi) . + \end{eqnarray*} + + Generically for all kernels, :math:`f^{(i)} (\alpha \xi)` is computable + by taking a sufficient number of symbolic derivatives of :math:`f` and + providing :math:`\alpha \xi = x` as the argument. + Now, for some kernels, like :math:`f (x) = C \log x`, the powers of + :math:`\alpha^i` from the chain rule cancel with the ones from the + argument substituted into the kernel derivatives: + .. math:: + + g^{(i)} (\xi) = \alpha^i f^{(i)} (\alpha \xi) = C' \cdot \alpha^i \cdot + \frac{1}{(\alpha x)^i} \quad (i > 0), + + making them what you might call *scale-invariant*. + This derivative taker returns :math:`g^{(i)}(\xi) = \alpha^i f^{(i)}` + given :math:`f^{(0)}` as *expr* and :math:`\alpha` as :attr:`rscale`. + + .. autoattribute:: orig_expr + .. autoattribute:: var_list + .. autoattribute:: rscale + .. autoattribute:: sac + + .. automethod:: diff + """ + orig_expr: sym.Expr + var_list: Sequence[sym.Symbol] + rscale: sym.Expr = field(default_factory=lambda: sym.sympify(1)) + sac: SymbolicAssignmentCollection | None = None + + cache_by_mi: dict[MultiIndex, sym.Expr] = field(init=False) + + def __post_init__(self): + zero_mi = (0,) * self.dim + object.__setattr__(self, "cache_by_mi", {zero_mi: self.orig_expr}) + + @property + def dim(self): + return len(self.var_list) + + def diff(self, mi: MultiIndex) -> sym.Expr: + """Take the derivative of the expression represented by + :class:`ExprDerivativeTaker`. + :param mi: multi-index representing the derivative + """ + try: + return self.cache_by_mi[mi] + except KeyError: + pass + + current_mi = self.get_closest_cached_mi(mi) + expr = self.cache_by_mi[current_mi] + + for next_deriv, next_mi in self.get_derivative_taking_sequence( + current_mi, mi): + expr = expr.diff(next_deriv) * self.rscale + self.cache_by_mi[next_mi] = expr + + return expr + + def get_derivative_taking_sequence(self, + start_mi: MultiIndex, + end_mi: MultiIndex) -> Iterable[tuple[sym.Symbol, MultiIndex]]: + current_mi = np.array(start_mi, dtype=int) + for idx, (mi_i, vec_i) in enumerate( + zip(sub_mi(end_mi, start_mi), self.var_list, strict=True)): + for _ in range(1, 1 + mi_i): + current_mi[idx] += 1 + yield vec_i, tuple(current_mi) + + def get_closest_cached_mi(self, mi: MultiIndex): + return min((other_mi + for other_mi in self.cache_by_mi + if (np.array(mi) >= np.array(other_mi)).all()), + key=lambda other_mi: sum(sub_mi(mi, other_mi))) + + +# }}} + +# {{{ LaplaceDerivativeTaker + +class LaplaceDerivativeTaker(ExprDerivativeTaker): + """Specialized derivative taker for Laplace potential. + """ + + @property + @memoize_method + def scaled_var_list(self): + return [add_to_sac(self.sac, v/self.rscale) for v in self.var_list] + + @property + @memoize_method + def scaled_r(self): + return add_to_sac(self.sac, + sym.sqrt(sum(v**2 for v in self.scaled_var_list))) + + @override + def diff(self, mi: MultiIndex) -> sym.Expr: + """ + Implements the algorithm described in [Fernando2021] to take cartesian + derivatives of Laplace potential using recurrences. Cost of each derivative + is amortized constant. + .. [Fernando2021]: Fernando, I., Klöckner, A., 2021. Automatic Synthesis of + Low Complexity Translation Operators for the Fast + Multipole Method. In preparation. + """ + # Return zero for negative values. Makes the algorithm readable. + if min(mi) < 0: + return sym.sympify(0) + try: + return self.cache_by_mi[mi] + except KeyError: + pass + + dim = self.dim + if max(mi) == 1: + return ExprDerivativeTaker.diff(self, mi) + d = -1 + for i in range(dim): + if mi[i] >= 2: + d = i + break + assert d >= 0 + expr = 0 + for i in range(dim): + mi_minus_one = list(mi) + mi_minus_one[i] -= 1 + mi_minus_one = tuple(mi_minus_one) + mi_minus_two = list(mi) + mi_minus_two[i] -= 2 + mi_minus_two = tuple(mi_minus_two) + x = self.scaled_var_list[i] + n = mi[i] + if i == d: + if dim == 3: + expr -= (2*n - 1) * x * self.diff(mi_minus_one) + expr -= (n - 1)**2 * self.diff(mi_minus_two) + else: + expr -= 2 * x * (n - 1) * self.diff(mi_minus_one) + expr -= (n - 1) * (n - 2) * self.diff(mi_minus_two) + if n == 2 and sum(mi) == 2: + expr += 1 + else: + expr -= 2 * n * x * self.diff(mi_minus_one) + expr -= n * (n - 1) * self.diff(mi_minus_two) + expr /= self.scaled_r**2 + expr = add_to_sac(self.sac, expr) + self.cache_by_mi[mi] = expr + return expr + + +# }}} + +# {{{ RadialDerivativeTaker + +@dataclass(frozen=True) +class RadialDerivativeTaker(ExprDerivativeTaker): + """Specialized derivative taker for radial expressions. + """ + + cache_by_mi_q: dict[tuple[MultiIndex, int], sym.Expr] = field(init=False) + + def __post_init__(self): + empty_mi = (0,) * len(self.var_list) + object.__setattr__(self, "cache_by_mi_q", {(empty_mi, 0): self.orig_expr}) + + @property + @memoize_method + def r(self): + return sym.sqrt(sum(v**2 for v in self.var_list)) + + @property + def is_radial(self): + rsym = sym.Symbol("_r") + r_expr = self.orig_expr.xreplace({self.r**2: rsym**2}) + return not any(r_expr.has(v) for v in self.var_list) + + @property + @memoize_method + def var_list_multiplied(self): + return [add_to_sac(self.sac, v * self.rscale) for v in self.var_list] + + def diff(self, mi: MultiIndex, q: int = 0) -> sym.Expr: + """ + Implements the algorithm described in [Tausch2003] to take cartesian + derivatives of radial functions using recurrences. Cost of each derivative + is amortized linear in the degree. + .. [Tausch2003]: Tausch, J., 2003. The fast multipole method for arbitrary + Green's functions. + Contemporary Mathematics, 329, pp.307-314. + """ + if not self.is_radial: + assert q == 0 + return ExprDerivativeTaker.diff(self, mi) + + try: + return self.cache_by_mi_q[mi, q] + except KeyError: + pass + + for i in range(self.dim): + if mi[i] == 1: + mi_minus_one = list(mi) + mi_minus_one[i] = 0 + mi_minus_one = tuple(mi_minus_one) + expr = self.var_list_multiplied[i] * self.diff(mi_minus_one, q=q+1) + self.cache_by_mi_q[mi, q] = expr + return expr + + for i in range(self.dim): + if mi[i] >= 2: + mi_minus_one = list(mi) + mi_minus_one[i] -= 1 + mi_minus_one = tuple(mi_minus_one) + mi_minus_two = list(mi) + mi_minus_two[i] -= 2 + mi_minus_two = tuple(mi_minus_two) + expr = (mi[i]-1)*self.diff(mi_minus_two, q=q+1) * self.rscale ** 2 + expr += self.var_list_multiplied[i] * self.diff(mi_minus_one, q=q+1) + expr = add_to_sac(self.sac, expr) + self.cache_by_mi_q[mi, q] = expr + return expr + + assert mi == (0,)*self.dim + assert q > 0 + + prev_expr = self.diff(mi, q=q-1) + # Need to get expr.diff(r)/r, but we can only do expr.diff(x) + # Use expr.diff(x) = expr.diff(r) * x / r + expr = prev_expr.diff(self.var_list[0])/self.var_list[0] + # We need to distribute the division above + expr = expr.expand(deep=False) + self.cache_by_mi_q[mi, q] = expr + return expr + + +# }}} + +# {{{ HelmholtzDerivativeTaker + +class HelmholtzDerivativeTaker(RadialDerivativeTaker): + """Specialized derivative taker for Helmholtz potential. + """ + + def diff(self, mi, q=0): + import sumpy.symbolic as sym + if q < 2 or mi != (0,)*self.dim: + return RadialDerivativeTaker.diff(self, mi, q) + + try: + return self.cache_by_mi_q[mi, q] + except KeyError: + pass + + if self.dim == 2: + # See https://dlmf.nist.gov/10.6.E6 + # and https://dlmf.nist.gov/10.6#E1 + k = self.orig_expr.args[1] / self.r + expr = (-2*(q - 1) * self.diff(mi, q - 1) + - k**2 * self.diff(mi, q - 2)) / self.r**2 + else: + # See reference [Tausch2003] in RadialDerivativeTaker.diff + # Note that there is a typo in the paper where + # -k**2/r is given instead of -k**2/r**2. + k = (self.orig_expr * self.r).args[-1] / sym.I / self.r + expr = (-(2*q - 1) * self.diff(mi, q - 1) + - k**2 * self.diff(mi, q - 2)) / self.r**2 + self.cache_by_mi_q[mi, q] = expr + return expr + + +# }}} + +# {{{ DifferentiatedExprDerivativeTaker + +DerivativeCoeffDict = dict[tuple[int, ...], int | float | complex | sym.Expr] + + +@dataclass(frozen=True) +class DifferentiatedExprDerivativeTaker: + """Implements the :class:`ExprDerivativeTaker` interface + for an expression that is itself a linear combination of + derivatives of a base expression. To take the actual derivatives, + it makes use of an underlying derivative taker *taker*. + + .. attribute:: taker + A :class:`ExprDerivativeTaker` for the base expression. + + .. attribute:: derivative_coeff_dict + A dictionary mapping a derivative multi-index to a coefficient. + The expression represented by this derivative taker is the linear + combination of the derivatives of the expression for the + base expression. + """ + taker: ExprDerivativeTaker + derivative_coeff_dict: DerivativeCoeffDict + + def diff(self, mi: MultiIndex, save_intermediate=lambda x: x) -> sym.Expr: + # By passing `rscale` to the derivative taker we are taking a scaled + # version of the derivative which is `expr.diff(mi)*rscale**sum(mi)` + # which might be implemented efficiently for kernels like Laplace. + # One caveat is that we are taking more derivatives because of + # :attr:`derivative_coeff_dict` which would multiply the + # expression by more `rscale`s than necessary. This is corrected by + # dividing by `rscale`. + max_order = max( + sum(extra_mi) for extra_mi in self.derivative_coeff_dict + ) + + result = sum( + coeff * self.taker.diff(add_mi(mi, extra_mi)) + / self.taker.rscale ** (sum(extra_mi) - max_order) + for extra_mi, coeff in self.derivative_coeff_dict.items()) + + return result * save_intermediate(1 / self.taker.rscale ** max_order) + + +# }}} + +# {{{ Helper functions + +def diff_derivative_coeff_dict( + derivative_coeff_dict: DerivativeCoeffDict, + variable_idx: int, + variables: sp.Matrix) -> DerivativeCoeffDict: + """Differentiate a derivative transformation dictionary given by + *derivative_coeff_dict* using the variable given by **variable_idx** + and return a new derivative transformation dictionary. + """ + from collections import defaultdict + new_derivative_coeff_dict: DerivativeCoeffDict = defaultdict(lambda: 0) + + for mi, coeff in derivative_coeff_dict.items(): + # In the case where we have x * u.diff(x), the result should be + # x.diff(x) + x * u.diff(x, x) + # Calculate the first term by differentiating the coefficients + new_coeff = sym.sympify(coeff).diff(variables[variable_idx]) + new_derivative_coeff_dict[mi] += new_coeff + + # Next calculate the second term by differentiating the derivatives + new_mi = list(mi) + new_mi[variable_idx] += 1 + new_derivative_coeff_dict[tuple(new_mi)] += coeff + + return {derivative: coeff for derivative, coeff in + new_derivative_coeff_dict.items() if coeff != 0} + +# }}} + +# vim: fdm=marker diff --git a/sumpy/distributed.py b/sumpy/distributed.py new file mode 100644 index 000000000..b6f6e110a --- /dev/null +++ b/sumpy/distributed.py @@ -0,0 +1,113 @@ +from __future__ import annotations + + +__copyright__ = "Copyright (C) 2022 Hao Gao" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +from typing import TYPE_CHECKING + +from boxtree.distributed.calculation import DistributedExpansionWranglerMixin + +from pytools import obj_array + +from sumpy.fmm import SumpyExpansionWrangler + + +if TYPE_CHECKING: + from arraycontext import ArrayContext + + +class DistributedSumpyExpansionWrangler( + DistributedExpansionWranglerMixin, SumpyExpansionWrangler): + def __init__( + self, actx: ArrayContext, + comm, tree_indep, local_traversal, global_traversal, + dtype, fmm_level_to_order, communicate_mpoles_via_allreduce=False, + **kwargs): + SumpyExpansionWrangler.__init__( + self, tree_indep, local_traversal, dtype, fmm_level_to_order, + **kwargs) + + self.comm = comm + self.traversal_in_device_memory = True + self.global_traversal = global_traversal + self.communicate_mpoles_via_allreduce = communicate_mpoles_via_allreduce + + def distribute_source_weights(self, + actx: ArrayContext, src_weight_vecs, src_idx_all_ranks): + src_weight_vecs_host = [ + actx.to_numpy(src_weight) for src_weight in src_weight_vecs + ] + + local_src_weight_vecs_host = super().distribute_source_weights( + actx, src_weight_vecs_host, src_idx_all_ranks) + + return [ + actx.from_numpy(local_src_weight) + for local_src_weight in local_src_weight_vecs_host] + + def gather_potential_results(self, + actx: ArrayContext, potentials, tgt_idx_all_ranks): + potentials_host_vec = [ + actx.to_numpy(potentials_dev) for potentials_dev in potentials + ] + + gathered_potentials_host_vec = [ + super().gather_potential_results( + actx, potentials_host, tgt_idx_all_ranks) + for potentials_host in potentials_host_vec] + + if self.is_mpi_root: + return obj_array.new_1d([ + actx.from_numpy(gathered_potentials_host) + for gathered_potentials_host in gathered_potentials_host_vec + ]) + else: + return None + + def reorder_sources(self, source_array): + if self.is_mpi_root: + return source_array[self.global_traversal.tree.user_source_ids] + else: + return source_array + + def reorder_potentials(self, potentials): + if self.is_mpi_root: + import numpy as np + + assert ( + isinstance(potentials, np.ndarray) + and potentials.dtype.char == "O") + + def reorder(x): + return x[self.global_traversal.tree.sorted_target_ids] + + return obj_array.vectorize(reorder, potentials) + else: + return None + + def communicate_mpoles(self, + actx: ArrayContext, mpole_exps, return_stats=False): + mpole_exps_host = actx.to_numpy(mpole_exps) + stats = super().communicate_mpoles(actx, mpole_exps_host, return_stats) + mpole_exps[:] = mpole_exps_host + return stats diff --git a/sumpy/e2e.py b/sumpy/e2e.py index 6f742f727..0d0bdc29b 100644 --- a/sumpy/e2e.py +++ b/sumpy/e2e.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2013 Andreas Kloeckner" __license__ = """ @@ -20,16 +23,28 @@ THE SOFTWARE. """ +import logging +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + import numpy as np +from typing_extensions import override + import loopy as lp +from loopy.version import MOST_RECENT_LANGUAGE_VERSION # ruff:ignore[unused-import] +from pytools import memoize_method + import sumpy.symbolic as sym -import pymbolic +from sumpy.array_context import make_loopy_program +from sumpy.tools import KernelCacheMixin, to_complex_dtype + + +if TYPE_CHECKING: + from arraycontext import ArrayContext + + from sumpy.expansion import ExpansionBase -from loopy.version import MOST_RECENT_LANGUAGE_VERSION -from sumpy.tools import KernelCacheWrapper, to_complex_dtype -from pytools import memoize_method -import logging logger = logging.getLogger(__name__) @@ -42,15 +57,20 @@ .. autoclass:: E2EFromCSR .. autoclass:: E2EFromParent .. autoclass:: E2EFromChildren - """ -# {{{ translation base class +# {{{ E2EBase: base class + +class E2EBase(KernelCacheMixin, ABC): + src_expansion: ExpansionBase + tgt_expansion: ExpansionBase + name: str -class E2EBase(KernelCacheWrapper): - def __init__(self, ctx, src_expansion, tgt_expansion, - name=None, device=None): + def __init__(self, + src_expansion: ExpansionBase, + tgt_expansion: ExpansionBase, + name: str | None = None): """ :arg expansion: a subclass of :class:`sympy.expansion.ExpansionBase` :arg strength_usage: A list of integers indicating which expression @@ -58,44 +78,43 @@ def __init__(self, ctx, src_expansion, tgt_expansion, number of strength arrays that need to be passed. Default: all kernels use the same strength. """ - - if device is None: - device = ctx.devices[0] + from sumpy.kernel import ( + SourceTransformationRemover, + TargetTransformationRemover, + ) + txr = TargetTransformationRemover() + sxr = SourceTransformationRemover() if src_expansion is tgt_expansion: - from sumpy.kernel import (TargetTransformationRemover, - SourceTransformationRemover) - tgt_expansion = src_expansion = src_expansion.with_kernel( - SourceTransformationRemover()( - TargetTransformationRemover()(src_expansion.kernel))) - + tgt_expansion = src_expansion = ( + src_expansion.with_kernel(sxr(txr(src_expansion.kernel)))) else: + src_expansion = ( + src_expansion.with_kernel(sxr(txr(src_expansion.kernel)))) + tgt_expansion = ( + tgt_expansion.with_kernel(sxr(txr(tgt_expansion.kernel)))) - from sumpy.kernel import (TargetTransformationRemover, - SourceTransformationRemover) - src_expansion = src_expansion.with_kernel( - SourceTransformationRemover()( - TargetTransformationRemover()(src_expansion.kernel))) - tgt_expansion = tgt_expansion.with_kernel( - SourceTransformationRemover()( - TargetTransformationRemover()(tgt_expansion.kernel))) - - self.ctx = ctx self.src_expansion = src_expansion self.tgt_expansion = tgt_expansion self.name = name or self.default_name - self.device = device if src_expansion.dim != tgt_expansion.dim: raise ValueError("source and target expansions must have " "same dimensionality") - self.dim = src_expansion.dim + @property + def dim(self): + return self.tgt_expansion.dim + + @property + @abstractmethod + def default_name(self): + pass @memoize_method def get_translation_loopy_insns(self): - from sumpy.symbolic import make_sym_vector - dvec = make_sym_vector("d", self.dim) + import sumpy.symbolic as sym + dvec = sym.make_sym_vector("d", self.dim) src_coeff_exprs = [ sym.Symbol(f"src_coeff{i}") @@ -113,7 +132,7 @@ def get_translation_loopy_insns(self): self.src_expansion, src_coeff_exprs, src_rscale, dvec=dvec, tgt_rscale=tgt_rscale, sac=sac))] - sac.run_global_cse() + sac = sac.run_global_cse() from sumpy.codegen import to_loopy_insns return to_loopy_insns( @@ -124,39 +143,35 @@ def get_translation_loopy_insns(self): ) def get_cache_key(self): - return ( - type(self).__name__, - self.src_expansion, - self.tgt_expansion, - ) + return (type(self).__name__, self.src_expansion, self.tgt_expansion) + + @abstractmethod + @override + def get_kernel(self) -> lp.TranslationUnit: + pass def get_optimized_kernel(self): # FIXME knl = self.get_kernel() - knl = lp.split_iname(knl, "itgt_box", 16, outer_tag="g.0") + return lp.split_iname(knl, "itgt_box", 64, outer_tag="g.0", inner_tag="l.0") - return knl # }}} -# {{{ translation from "compressed sparse row"-like source box lists +# {{{ E2EFromCSR: translation from "compressed sparse row"-like source box lists class E2EFromCSR(E2EBase): """Implements translation from a "compressed sparse row"-like source box list. """ - default_name = "e2e_from_csr" - - def __init__(self, ctx, src_expansion, tgt_expansion, - name=None, device=None): - super().__init__(ctx, src_expansion, tgt_expansion, - name=name, device=device) + @property + def default_name(self): + return "e2e_from_csr" def get_translation_loopy_insns(self): - from sumpy.symbolic import make_sym_vector - dvec = make_sym_vector("d", self.dim) + dvec = sym.make_sym_vector("d", self.dim) src_rscale = sym.Symbol("src_rscale") tgt_rscale = sym.Symbol("tgt_rscale") @@ -173,7 +188,7 @@ def get_translation_loopy_insns(self): self.src_expansion, src_coeff_exprs, src_rscale, dvec, tgt_rscale, sac))] - sac.run_global_cse() + sac = sac.run_global_cse() from sumpy.codegen import to_loopy_insns return to_loopy_insns( @@ -195,12 +210,11 @@ def get_kernel(self): # (same for itgt_box, tgt_ibox) from sumpy.tools import gather_loopy_arguments - loopy_knl = lp.make_kernel( - [ - "{[itgt_box]: 0<=itgt_box tgt_ibox = target_boxes[itgt_box] @@ -214,11 +228,11 @@ def get_kernel(self): <> src_center[idim] = centers[idim, src_ibox] {dup=idim} <> d[idim] = tgt_center[idim] - src_center[idim] \ {dup=idim} - """] + [""" - <> src_coeff{coeffidx} = \ - src_expansions[src_ibox - src_base_ibox, {coeffidx}] \ + """] + [f""" + <> src_coeff{i} = \ + src_expansions[src_ibox - src_base_ibox, {i}] \ {{dep=read_src_ibox}} - """.format(coeffidx=i) for i in range(ncoeff_src)] + [ + """ for i in range(ncoeff_src)] + [ ] + self.get_translation_loopy_insns() + [""" end @@ -229,7 +243,7 @@ def get_kernel(self): """ for coeffidx in range(ncoeff_tgt)] + [""" end """], - [ + kernel_data=[ lp.GlobalArg("centers", None, shape="dim, aligned_nboxes"), lp.ValueArg("src_rscale,tgt_rscale", None), lp.GlobalArg("src_box_starts, src_box_lists", @@ -242,34 +256,30 @@ def get_kernel(self): shape=("nsrc_level_boxes", ncoeff_src), offset=lp.auto), lp.GlobalArg("tgt_expansions", None, shape=("ntgt_level_boxes", ncoeff_tgt), offset=lp.auto), - "..." - ] + gather_loopy_arguments([self.src_expansion, - self.tgt_expansion]), + "...", + *gather_loopy_arguments([self.src_expansion, + self.tgt_expansion]) + ], name=self.name, assumptions="ntgt_boxes>=1", silenced_warnings="write_race(write_expn*)", - default_offset=lp.auto, - fixed_parameters=dict(dim=self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION + fixed_parameters={"dim": self.dim}, ) for knl in [self.src_expansion.kernel, self.tgt_expansion.kernel]: loopy_knl = knl.prepare_loopy_kernel(loopy_knl) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - loopy_knl = lp.set_options(loopy_knl, + return lp.set_options(loopy_knl, enforce_variable_access_ordered="no_check") - return loopy_knl - + @override def get_optimized_kernel(self): # FIXME knl = self.get_kernel() - knl = lp.split_iname(knl, "itgt_box", 16, outer_tag="g.0") - - return knl + return lp.split_iname(knl, "itgt_box", 64, outer_tag="g.0", inner_tag="l.0") - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): """ :arg src_expansions: :arg src_box_starts: @@ -284,36 +294,54 @@ def __call__(self, queue, **kwargs): src_rscale = centers.dtype.type(kwargs.pop("src_rscale")) tgt_rscale = centers.dtype.type(kwargs.pop("tgt_rscale")) - knl = self.get_cached_optimized_kernel() + knl = self.get_cached_kernel() + result = actx.call_loopy( + knl, + centers=centers, + src_rscale=src_rscale, tgt_rscale=tgt_rscale, + **kwargs) + + return result["tgt_expansions"] +# }}} - return knl(queue, - centers=centers, - src_rscale=src_rscale, tgt_rscale=tgt_rscale, - **kwargs) +# {{{ M2LUsingTranslationClassesDependentData class M2LUsingTranslationClassesDependentData(E2EFromCSR): """Implements translation from a "compressed sparse row"-like source box list using M2L translation classes dependent data """ - default_name = "m2l_using_translation_classes_dependent_data" + @property + def default_name(self): + return "m2l_using_translation_classes_dependent_data" def get_translation_loopy_insns(self, result_dtype): - from sumpy.symbolic import make_sym_vector - dvec = make_sym_vector("d", self.dim) + import sumpy.symbolic as sym + dvec = sym.make_sym_vector("d", self.dim) src_rscale = sym.Symbol("src_rscale") tgt_rscale = sym.Symbol("tgt_rscale") - m2l_translation_classes_dependent_ndata = ( - self.tgt_expansion.m2l_translation_classes_dependent_ndata( - self.src_expansion)) - m2l_translation_classes_dependent_data = \ - [sym.Symbol(f"data{i}") - for i in range(m2l_translation_classes_dependent_ndata)] + from sumpy.expansion.local import LocalExpansionBase + from sumpy.expansion.multipole import MultipoleExpansionBase - ncoeff_src = len(self.src_expansion) + assert isinstance(self.tgt_expansion, LocalExpansionBase) + assert isinstance(self.src_expansion, MultipoleExpansionBase) + + m2l_translation = self.tgt_expansion.m2l_translation + m2l_translation_classes_dependent_ndata = ( + m2l_translation.translation_classes_dependent_ndata(self.tgt_expansion, + self.src_expansion)) + m2l_translation_classes_dependent_data = tuple( + sym.Symbol(f"data{i}") + for i in range(m2l_translation_classes_dependent_ndata)) + + if m2l_translation.use_preprocessing: + ncoeff_src = m2l_translation.preprocess_multipole_nexprs( + self.tgt_expansion, self.src_expansion) + else: + ncoeff_src = len(self.src_expansion) src_coeff_exprs = [sym.Symbol(f"src_coeffs{i}") for i in range(ncoeff_src)] @@ -328,7 +356,7 @@ def get_translation_loopy_insns(self, result_dtype): m2l_translation_classes_dependent_data=( m2l_translation_classes_dependent_data)))] - sac.run_global_cse() + sac = sac.run_global_cse() from sumpy.codegen import to_loopy_insns return to_loopy_insns( @@ -346,29 +374,32 @@ def get_inner_loopy_kernel(self, result_dtype): except NotImplementedError: pass - ndata = self.tgt_expansion.m2l_translation_classes_dependent_ndata( - self.src_expansion) - if self.tgt_expansion.use_preprocessing_for_m2l: - ncoeff_src = self.tgt_expansion.m2l_preprocess_multipole_nexprs( - self.src_expansion) - ncoeff_tgt = self.tgt_expansion.m2l_postprocess_local_nexprs( - self.src_expansion) + m2l_translation = self.tgt_expansion.m2l_translation + ndata = m2l_translation.translation_classes_dependent_ndata( + self.tgt_expansion, self.src_expansion) + if m2l_translation.use_preprocessing: + ncoeff_src = m2l_translation.preprocess_multipole_nexprs( + self.tgt_expansion, self.src_expansion) + ncoeff_tgt = m2l_translation.postprocess_local_nexprs( + self.tgt_expansion, self.src_expansion) else: ncoeff_src = len(self.src_expansion) ncoeff_tgt = len(self.tgt_expansion) - domains = [] + import pymbolic as prim + + domains: list[str] = [] insns = self.get_translation_loopy_insns(result_dtype) - coeff = pymbolic.var("coeff") + tgt_coeffs = prim.var("tgt_coeffs") for i in range(ncoeff_tgt): - expr = pymbolic.var(f"tgt_coeff{i}") - insn = lp.Assignment(assignee=coeff[i], - expression=coeff[i] + expr) + expr = prim.var(f"tgt_coeff{i}") + insn = lp.Assignment(assignee=tgt_coeffs[i], + expression=tgt_coeffs[i] + expr) insns.append(insn) return lp.make_function(domains, insns, kernel_data=[ - lp.GlobalArg("coeff", shape=(ncoeff_tgt,), + lp.GlobalArg("tgt_coeffs", shape=(ncoeff_tgt,), is_output=True, is_input=True), lp.GlobalArg("src_coeffs", shape=(ncoeff_src,)), lp.GlobalArg("data", shape=(ndata,)), @@ -380,15 +411,16 @@ def get_inner_loopy_kernel(self, result_dtype): ) def get_kernel(self, result_dtype): + m2l_translation = self.tgt_expansion.m2l_translation m2l_translation_classes_dependent_ndata = \ - self.tgt_expansion.m2l_translation_classes_dependent_ndata( - self.src_expansion) - - if self.tgt_expansion.use_preprocessing_for_m2l: - ncoeff_src = self.tgt_expansion.m2l_preprocess_multipole_nexprs( - self.src_expansion) - ncoeff_tgt = self.tgt_expansion.m2l_postprocess_local_nexprs( - self.src_expansion) + m2l_translation.translation_classes_dependent_ndata( + self.tgt_expansion, self.src_expansion) + + if m2l_translation.use_preprocessing: + ncoeff_src = m2l_translation.preprocess_multipole_nexprs( + self.tgt_expansion, self.src_expansion) + ncoeff_tgt = m2l_translation.postprocess_local_nexprs( + self.tgt_expansion, self.src_expansion) else: ncoeff_src = len(self.src_expansion) ncoeff_tgt = len(self.tgt_expansion) @@ -403,13 +435,12 @@ def get_kernel(self, result_dtype): translation_knl = self.get_inner_loopy_kernel(result_dtype) from sumpy.tools import gather_loopy_arguments - loopy_knl = lp.make_kernel( - [ - "{[itgt_box]: 0<=itgt_box isrc_start = src_box_starts[itgt_box] <> isrc_stop = src_box_starts[itgt_box+1] for icoeff_tgt - <> coeffs[icoeff_tgt] = 0 {id=init_coeffs, dup=icoeff_tgt} + <> tgt_expansion[icoeff_tgt] = 0 \ + {id=init_coeffs, dup=icoeff_tgt} end for isrc_box <> src_ibox = src_box_lists[isrc_box] \ @@ -427,8 +459,8 @@ def get_kernel(self, result_dtype): <> translation_class_rel = \ translation_class - translation_classes_level_start \ {id=translation_offset} - [icoeff_tgt]: coeffs[icoeff_tgt] = e2e( - [icoeff_tgt]: coeffs[icoeff_tgt], + [icoeff_tgt]: tgt_expansion[icoeff_tgt] = e2e( + [icoeff_tgt]: tgt_expansion[icoeff_tgt], [icoeff_src]: src_expansions[src_ibox - src_base_ibox, icoeff_src], [idep]: m2l_translation_classes_dependent_data[ @@ -438,10 +470,11 @@ def get_kernel(self, result_dtype): ) {dep=init_coeffs,id=update_coeffs} end tgt_expansions[tgt_ibox - tgt_base_ibox, icoeff_tgt] = \ - coeffs[icoeff_tgt] {dep=update_coeffs, dup=icoeff_tgt} + tgt_expansion[icoeff_tgt] \ + {dep=update_coeffs, dup=icoeff_tgt,id=write_e2e} end """], - [ + kernel_data=[ lp.GlobalArg("centers", None, shape="dim, aligned_nboxes"), lp.ValueArg("src_rscale,tgt_rscale", None), lp.GlobalArg("src_box_starts, src_box_lists", @@ -466,18 +499,19 @@ def get_kernel(self, result_dtype): offset=lp.auto), lp.ValueArg("ntranslation_classes, ntranslation_classes_lists", np.int32), - ... - ] + gather_loopy_arguments([self.src_expansion, - self.tgt_expansion]), + ..., + *gather_loopy_arguments([self.src_expansion, + self.tgt_expansion]) + ], name=self.name, assumptions="ntgt_boxes>=1", - default_offset=lp.auto, - fixed_parameters=dict(dim=self.dim, - m2l_translation_classes_dependent_ndata=( + fixed_parameters={ + "dim": self.dim, + "m2l_translation_classes_dependent_ndata": ( m2l_translation_classes_dependent_ndata), - ncoeff_tgt=ncoeff_tgt, - ncoeff_src=ncoeff_src), - lang_version=MOST_RECENT_LANGUAGE_VERSION + "ncoeff_tgt": ncoeff_tgt, + "ncoeff_src": ncoeff_src}, + silenced_warnings="write_race(write_e2e*)", ) loopy_knl = lp.merge([translation_knl, loopy_knl]) @@ -494,12 +528,10 @@ def get_kernel(self, result_dtype): def get_optimized_kernel(self, result_dtype): knl = self.get_kernel(result_dtype) - # FIXME - knl = lp.split_iname(knl, "itgt_box", 16, outer_tag="g.0") + return self.tgt_expansion.m2l_translation.optimize_loopy_kernel( + knl, self.tgt_expansion, self.src_expansion) - return knl - - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): """ :arg src_expansions: :arg src_box_starts: @@ -515,72 +547,60 @@ def __call__(self, queue, **kwargs): tgt_rscale = centers.dtype.type(kwargs.pop("tgt_rscale")) src_expansions = kwargs.pop("src_expansions") - knl = self.get_cached_optimized_kernel(result_dtype=src_expansions.dtype) + knl = self.get_cached_kernel(result_dtype=src_expansions.dtype) + result = actx.call_loopy( + knl, + src_expansions=src_expansions, + centers=centers, + src_rscale=src_rscale, tgt_rscale=tgt_rscale, + **kwargs) + + return result["tgt_expansions"] + +# }}} - return knl(queue, - src_expansions=src_expansions, - centers=centers, - src_rscale=src_rscale, tgt_rscale=tgt_rscale, - **kwargs) +# {{{ M2LGenerateTranslationClassesDependentData class M2LGenerateTranslationClassesDependentData(E2EBase): """Implements precomputing the M2L kernel dependent data which are translation classes dependent derivatives. """ - default_name = "m2l_generate_translation_classes_dependent_data" - - def get_translation_loopy_insns(self, result_dtype): - from sumpy.symbolic import make_sym_vector - dvec = make_sym_vector("d", self.dim) - - src_rscale = sym.Symbol("src_rscale") - tgt_rscale = sym.Symbol("tgt_rscale") - - from sumpy.assignment_collection import SymbolicAssignmentCollection - sac = SymbolicAssignmentCollection() - tgt_coeff_names = [ - sac.assign_unique( - f"m2l_translation_classes_dependent_expr{i}", coeff_i) - for i, coeff_i in enumerate( - self.tgt_expansion.m2l_translation_classes_dependent_data( - self.src_expansion, src_rscale, - dvec, tgt_rscale, sac))] - - sac.run_global_cse() - from sumpy.codegen import to_loopy_insns - return to_loopy_insns( - sac.assignments.items(), - vector_names={"d"}, - pymbolic_expr_maps=[self.tgt_expansion.get_code_transformer()], - retain_names=tgt_coeff_names, - complex_dtype=to_complex_dtype(result_dtype), - ) + @property + def default_name(self): + return "m2l_generate_translation_classes_dependent_data" def get_kernel(self, result_dtype): + m2l_translation = self.tgt_expansion.m2l_translation m2l_translation_classes_dependent_ndata = \ - self.tgt_expansion.m2l_translation_classes_dependent_ndata( - self.src_expansion) + m2l_translation.translation_classes_dependent_ndata( + self.tgt_expansion, self.src_expansion) + + translation_classes_data_knl = \ + m2l_translation.loopy_translation_classes_dependent_data( + self.tgt_expansion, self.src_expansion, result_dtype) + from sumpy.tools import gather_loopy_arguments - loopy_knl = lp.make_kernel( - [ - "{[itr_class]: 0<=itr_class d[idim] = m2l_translation_vectors[idim, \ - itr_class + translation_classes_level_start] - - """] + self.get_translation_loopy_insns(result_dtype) + [""" - m2l_translation_classes_dependent_data[itr_class, {idx}] = \ - m2l_translation_classes_dependent_expr{idx} - """.format(idx=i) for i in range( - m2l_translation_classes_dependent_ndata)] + [""" + itr_class + translation_classes_level_start] \ + {id=set_d,dup=idim} + [idata]: m2l_translation_classes_dependent_data[ + itr_class, idata] = \ + m2l_data( + src_rscale, + [idim]: d[idim], + ) {id=update,dep=set_d} end """], - [ + kernel_data=[ lp.ValueArg("src_rscale", None), lp.GlobalArg("m2l_translation_classes_dependent_data", None, shape=("ntranslation_classes", @@ -591,32 +611,36 @@ def get_kernel(self, result_dtype): lp.ValueArg("ntranslation_classes", np.int32), lp.ValueArg("ntranslation_vectors", np.int32), lp.ValueArg("translation_classes_level_start", np.int32), - "..." - ] + gather_loopy_arguments([self.src_expansion, self.tgt_expansion]), + "...", + *gather_loopy_arguments([self.src_expansion, self.tgt_expansion]) + ], name=self.name, assumptions="ntranslation_classes>=1", - default_offset=lp.auto, - fixed_parameters=dict(dim=self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION + fixed_parameters={ + "dim": self.dim, + "m2l_translation_classes_dependent_ndata": ( + m2l_translation_classes_dependent_ndata)}, ) - for knl in [self.src_expansion.kernel, self.tgt_expansion.kernel]: - loopy_knl = knl.prepare_loopy_kernel(loopy_knl) - - loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - loopy_knl = lp.set_options(loopy_knl, - enforce_variable_access_ordered="no_check") + for expr_knl in [self.src_expansion.kernel, self.tgt_expansion.kernel]: + loopy_knl = expr_knl.prepare_loopy_kernel(loopy_knl) - return loopy_knl + loopy_knl = lp.merge([loopy_knl, translation_classes_data_knl]) + loopy_knl = lp.inline_callable_kernel(loopy_knl, "m2l_data") + return lp.set_options(loopy_knl, + enforce_variable_access_ordered="no_check", + # FIXME: Without this, Loopy spends an eternity checking + # scattered writes to global variables to see whether barriers + # need to be inserted. + disable_global_barriers=True) def get_optimized_kernel(self, result_dtype): # FIXME knl = self.get_kernel(result_dtype) - knl = lp.tag_inames(knl, {"itr_class": "g.0"}) + knl = lp.tag_inames(knl, "idim*:unr") + return lp.tag_inames(knl, {"itr_class": "g.0"}) - return knl - - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): """ :arg src_rscale: :arg translation_classes_level_start: @@ -633,14 +657,16 @@ def __call__(self, queue, **kwargs): "m2l_translation_classes_dependent_data") result_dtype = m2l_translation_classes_dependent_data.dtype - knl = self.get_cached_optimized_kernel(result_dtype=result_dtype) + knl = self.get_cached_kernel(result_dtype=result_dtype) + result = actx.call_loopy( + knl, + src_rscale=src_rscale, + m2l_translation_vectors=m2l_translation_vectors, + m2l_translation_classes_dependent_data=( + m2l_translation_classes_dependent_data), + **kwargs) - return knl(queue, - src_rscale=src_rscale, - m2l_translation_vectors=m2l_translation_vectors, - m2l_translation_classes_dependent_data=( - m2l_translation_classes_dependent_data), - **kwargs) + return result["m2l_translation_classes_dependent_data"] # }}} @@ -650,58 +676,42 @@ def __call__(self, queue, **kwargs): class M2LPreprocessMultipole(E2EBase): """Computes the preprocessed multipole expansion for accelerated M2L""" - default_name = "m2l_preprocess_multipole" - - def get_loopy_insns(self, result_dtype): - src_coeff_exprs = [ - sym.Symbol(f"src_coeff{i}") - for i in range(len(self.src_expansion))] - - src_rscale = sym.Symbol("src_rscale") - - from sumpy.assignment_collection import SymbolicAssignmentCollection - sac = SymbolicAssignmentCollection() + @property + def default_name(self): + return "m2l_preprocess_multipole" - preprocessed_src_coeff_names = [ - sac.assign_unique(f"preprocessed_src_coeff{i}", coeff_i) - for i, coeff_i in enumerate( - self.tgt_expansion.m2l_preprocess_multipole_exprs( - self.src_expansion, src_coeff_exprs, - sac=sac, src_rscale=src_rscale))] - - sac.run_global_cse() - - from sumpy.codegen import to_loopy_insns - return to_loopy_insns( - sac.assignments.items(), - vector_names={"d"}, - pymbolic_expr_maps=[self.tgt_expansion.get_code_transformer()], - retain_names=preprocessed_src_coeff_names, - complex_dtype=to_complex_dtype(result_dtype), - ) + @memoize_method + def get_inner_knl_and_optimizations(self, result_dtype): + m2l_translation = self.tgt_expansion.m2l_translation + return m2l_translation.loopy_preprocess_multipole( + self.tgt_expansion, self.src_expansion, result_dtype) def get_kernel(self, result_dtype): + m2l_translation = self.tgt_expansion.m2l_translation nsrc_coeffs = len(self.src_expansion) npreprocessed_src_coeffs = \ - self.tgt_expansion.m2l_preprocess_multipole_nexprs(self.src_expansion) + m2l_translation.preprocess_multipole_nexprs(self.tgt_expansion, + self.src_expansion) + single_box_preprocess_knl, _ = self.get_inner_knl_and_optimizations( + result_dtype) + from sumpy.tools import gather_loopy_arguments - loopy_knl = lp.make_kernel( + loopy_knl = make_loopy_program( [ "{[isrc_box]: 0<=isrc_box src_coeff{idx} = src_expansions[isrc_box, {idx}] - """.format(idx=i) for i in range(nsrc_coeffs)] + [ - ] + self.get_loopy_insns(result_dtype) + [""" - preprocessed_src_expansions[isrc_box, {idx}] = \ - preprocessed_src_coeff{idx} - """.format(idx=i) for i in range( - npreprocessed_src_coeffs)] + [""" + [itgt_coeff]: preprocessed_src_expansions[isrc_box, itgt_coeff] \ + = m2l_preprocess_inner( + src_rscale, + [isrc_coeff]: src_expansions[isrc_box, isrc_coeff], + ) end """], - [ + kernel_data=[ lp.ValueArg("nsrc_boxes", np.int32), lp.ValueArg("src_rscale", None), lp.GlobalArg("src_expansions", None, @@ -709,38 +719,46 @@ def get_kernel(self, result_dtype): lp.GlobalArg("preprocessed_src_expansions", None, shape=("nsrc_boxes", npreprocessed_src_coeffs), offset=lp.auto), - "..." - ] + gather_loopy_arguments([self.src_expansion, self.tgt_expansion]), + "...", + *gather_loopy_arguments([self.src_expansion, self.tgt_expansion]) + ], name=self.name, assumptions="nsrc_boxes>=1", - default_offset=lp.auto, - fixed_parameters=dict(dim=self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION + fixed_parameters={ + "nsrc_coeffs": nsrc_coeffs, + "npreprocessed_src_coeffs": npreprocessed_src_coeffs}, ) for expn in [self.src_expansion.kernel, self.tgt_expansion.kernel]: loopy_knl = expn.prepare_loopy_kernel(loopy_knl) - loopy_knl = lp.set_options(loopy_knl, - enforce_variable_access_ordered="no_check") - return loopy_knl + loopy_knl = lp.merge([loopy_knl, single_box_preprocess_knl]) + return lp.inline_callable_kernel(loopy_knl, "m2l_preprocess_inner") def get_optimized_kernel(self, result_dtype): - # FIXME knl = self.get_kernel(result_dtype) - knl = lp.split_iname(knl, "isrc_box", 16, outer_tag="g.0") + knl = lp.tag_inames(knl, "isrc_box:g.0") + _, optimizations = self.get_inner_knl_and_optimizations(result_dtype) + for optimization in optimizations: + knl = optimization(knl) return knl - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): """ :arg src_expansions :arg preprocessed_src_expansions """ preprocessed_src_expansions = kwargs.pop("preprocessed_src_expansions") result_dtype = preprocessed_src_expansions.dtype - knl = self.get_cached_optimized_kernel(result_dtype=result_dtype) - return knl(queue, - preprocessed_src_expansions=preprocessed_src_expansions, **kwargs) + + knl = self.get_cached_kernel(result_dtype=result_dtype) + result = actx.call_loopy( + knl, + preprocessed_src_expansions=preprocessed_src_expansions, + **kwargs) + + return result["preprocessed_src_expansions"] + # }}} @@ -749,69 +767,44 @@ def __call__(self, queue, **kwargs): class M2LPostprocessLocal(E2EBase): """Postprocesses locals expansions for accelerated M2L""" - default_name = "m2l_postprocess_local" - - def get_loopy_insns(self, result_dtype): - ncoeffs_before_postprocessing = \ - self.tgt_expansion.m2l_postprocess_local_nexprs(self.tgt_expansion) - - tgt_coeff_exprs_before_postprocessing = [ - sym.Symbol(f"tgt_coeff_before_postprocessing{i}") - for i in range(ncoeffs_before_postprocessing)] - - src_rscale = sym.Symbol("src_rscale") - tgt_rscale = sym.Symbol("tgt_rscale") - - from sumpy.assignment_collection import SymbolicAssignmentCollection - sac = SymbolicAssignmentCollection() - - tgt_coeff_exprs = self.tgt_expansion.m2l_postprocess_local_exprs( - self.tgt_expansion, tgt_coeff_exprs_before_postprocessing, - sac=sac, src_rscale=src_rscale, tgt_rscale=tgt_rscale) + @property + def default_name(self): + return "m2l_postprocess_local" - if result_dtype in (np.float32, np.float64): - real_func = sym.Function("real") - tgt_coeff_exprs = [real_func(expr) for expr in - tgt_coeff_exprs] - - tgt_coeff_names = [ - sac.assign_unique(f"tgt_coeff{i}", coeff_i) - for i, coeff_i in enumerate(tgt_coeff_exprs)] - - sac.run_global_cse() - - from sumpy.codegen import to_loopy_insns - return to_loopy_insns( - sac.assignments.items(), - vector_names={"d"}, - pymbolic_expr_maps=[self.tgt_expansion.get_code_transformer()], - retain_names=tgt_coeff_names, - complex_dtype=to_complex_dtype(result_dtype), - ) + @memoize_method + def get_inner_knl_and_optimizations(self, result_dtype): + m2l_translation = self.tgt_expansion.m2l_translation + return m2l_translation.loopy_postprocess_local( + self.tgt_expansion, self.src_expansion, result_dtype) def get_kernel(self, result_dtype): + m2l_translation = self.tgt_expansion.m2l_translation ntgt_coeffs = len(self.tgt_expansion) ntgt_coeffs_before_postprocessing = \ - self.tgt_expansion.m2l_postprocess_local_nexprs(self.tgt_expansion) + m2l_translation.postprocess_local_nexprs(self.tgt_expansion, + self.src_expansion) + + single_box_postprocess_knl, _ = self.get_inner_knl_and_optimizations( + result_dtype) + from sumpy.tools import gather_loopy_arguments - loopy_knl = lp.make_kernel( - [ - "{[itgt_box]: 0<=itgt_box tgt_coeff_before_postprocessing{idx} = \ - tgt_expansions_before_postprocessing[itgt_box, {idx}] - """.format(idx=i) for i in range( - ntgt_coeffs_before_postprocessing)] - + self.get_loopy_insns(result_dtype) + [""" - tgt_expansions[itgt_box, {idx}] = \ - tgt_coeff{idx} - """.format(idx=i) for i in range(ntgt_coeffs)] + [""" + [itgt_coeff]: tgt_expansions[itgt_box, itgt_coeff] = \ + m2l_postprocess_inner( + src_rscale, + tgt_rscale, + [isrc_coeff]: tgt_expansions_before_postprocessing[ \ + itgt_box, isrc_coeff], + ) end """], - [ + kernel_data=[ lp.ValueArg("ntgt_boxes", np.int32), lp.ValueArg("src_rscale", None), lp.ValueArg("tgt_rscale", None), @@ -820,46 +813,60 @@ def get_kernel(self, result_dtype): lp.GlobalArg("tgt_expansions_before_postprocessing", None, shape=("ntgt_boxes", ntgt_coeffs_before_postprocessing), offset=lp.auto), - "..." - ] + gather_loopy_arguments([self.src_expansion, self.tgt_expansion]), + "...", + *gather_loopy_arguments([self.src_expansion, self.tgt_expansion]) + ], name=self.name, assumptions="ntgt_boxes>=1", - default_offset=lp.auto, - fixed_parameters=dict(dim=self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION + fixed_parameters={ + "dim": self.dim, + "nsrc_coeffs": ntgt_coeffs_before_postprocessing, + "ntgt_coeffs": ntgt_coeffs, + }, ) for expn in [self.src_expansion.kernel, self.tgt_expansion.kernel]: loopy_knl = expn.prepare_loopy_kernel(loopy_knl) - loopy_knl = lp.set_options(loopy_knl, + loopy_knl = lp.merge([loopy_knl, single_box_postprocess_knl]) + loopy_knl = lp.inline_callable_kernel(loopy_knl, "m2l_postprocess_inner") + + return lp.set_options(loopy_knl, enforce_variable_access_ordered="no_check") - return loopy_knl def get_optimized_kernel(self, result_dtype): - # FIXME knl = self.get_kernel(result_dtype) - knl = lp.split_iname(knl, "itgt_box", 16, outer_tag="g.0") - return knl + knl = lp.tag_inames(knl, "itgt_box:g.0") + _, optimizations = self.get_inner_knl_and_optimizations(result_dtype) + for optimization in optimizations: + knl = optimization(knl) + return lp.add_inames_for_unused_hw_axes(knl) - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): """ :arg tgt_expansions :arg tgt_expansions_before_postprocessing """ tgt_expansions = kwargs.pop("tgt_expansions") result_dtype = tgt_expansions.dtype - knl = self.get_cached_optimized_kernel(result_dtype=result_dtype) - return knl(queue, - tgt_expansions=tgt_expansions, **kwargs) + + knl = self.get_cached_kernel(result_dtype=result_dtype) + result = actx.call_loopy( + knl, + tgt_expansions=tgt_expansions, + **kwargs) + + return result["tgt_expansions"] # }}} -# {{{ translation from a box's children +# {{{ E2EFromChildren: translation from a box's children class E2EFromChildren(E2EBase): - default_name = "e2e_from_children" + @property + def default_name(self): + return "e2e_from_children" def get_kernel(self): ncoeffs_src = len(self.src_expansion) @@ -879,12 +886,11 @@ def get_kernel(self): for insn in self.get_translation_loopy_insns()] from sumpy.tools import gather_loopy_arguments - loopy_knl = lp.make_kernel( - [ - "{[itgt_box]: 0<=itgt_box tgt_ibox = target_boxes[itgt_box] @@ -901,23 +907,23 @@ def get_kernel(self): <> d[idim] = tgt_center[idim] - src_center[idim] \ {dup=idim} - """] + [""" + """] + [f""" <> src_coeff{i} = \ src_expansions[src_ibox - src_base_ibox, {i}] \ {{id_prefix=read_coeff,dep=read_src_ibox}} - """.format(i=i) for i in range(ncoeffs_src)] + [ - ] + loopy_insns + [""" + """ for i in range(ncoeffs_src)] + [ + ] + loopy_insns + [f""" tgt_expansions[tgt_ibox - tgt_base_ibox, {i}] = \ tgt_expansions[tgt_ibox - tgt_base_ibox, {i}] \ + coeff{i} \ {{id_prefix=write_expn,dep=compute_coeff*, nosync=read_coeff*}} - """.format(i=i) for i in range(ncoeffs_tgt)] + [""" + """ for i in range(ncoeffs_tgt)] + [""" end end end """], - [ + kernel_data=[ lp.GlobalArg("target_boxes", None, shape=lp.auto, offset=lp.auto), lp.GlobalArg("centers", None, shape="dim, aligned_nboxes"), @@ -931,24 +937,23 @@ def get_kernel(self): lp.ValueArg("src_base_ibox,tgt_base_ibox", np.int32), lp.ValueArg("ntgt_level_boxes,nsrc_level_boxes", np.int32), lp.ValueArg("aligned_nboxes", np.int32), - "..." - ] + gather_loopy_arguments([self.src_expansion, self.tgt_expansion]), + "...", + *gather_loopy_arguments([self.src_expansion, self.tgt_expansion]) + ], name=self.name, assumptions="ntgt_boxes>=1", silenced_warnings="write_race(write_expn*)", - fixed_parameters=dict(dim=self.dim, nchildren=2**self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + fixed_parameters={"dim": self.dim, "nchildren": 2**self.dim}, + ) for knl in [self.src_expansion.kernel, self.tgt_expansion.kernel]: loopy_knl = knl.prepare_loopy_kernel(loopy_knl) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - loopy_knl = lp.set_options(loopy_knl, + return lp.set_options(loopy_knl, enforce_variable_access_ordered="no_check") - return loopy_knl - - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): """ :arg src_expansions: :arg src_box_starts: @@ -957,26 +962,30 @@ def __call__(self, queue, **kwargs): :arg tgt_rscale: :arg centers: """ - knl = self.get_cached_optimized_kernel() - centers = kwargs.pop("centers") # "1" may be passed for rscale, which won't have its type # meaningfully inferred. Make the type of rscale explicit. src_rscale = centers.dtype.type(kwargs.pop("src_rscale")) tgt_rscale = centers.dtype.type(kwargs.pop("tgt_rscale")) - return knl(queue, - centers=centers, - src_rscale=src_rscale, tgt_rscale=tgt_rscale, - **kwargs) + knl = self.get_cached_kernel() + result = actx.call_loopy( + knl, + centers=centers, + src_rscale=src_rscale, tgt_rscale=tgt_rscale, + **kwargs) + + return result["tgt_expansions"] # }}} -# {{{ translation from a box's parent +# {{{ E2EFromParent: translation from a box's parent class E2EFromParent(E2EBase): - default_name = "e2e_from_parent" + @property + def default_name(self): + return "e2e_from_parent" def get_kernel(self): ncoeffs_src = len(self.src_expansion) @@ -990,11 +999,10 @@ def get_kernel(self): # (same for itgt_box, tgt_ibox) from sumpy.tools import gather_loopy_arguments - loopy_knl = lp.make_kernel( - [ - "{[itgt_box]: 0<=itgt_box tgt_ibox = target_boxes[itgt_box] @@ -1007,21 +1015,21 @@ def get_kernel(self): <> src_center[idim] = centers[idim, src_ibox] {dup=idim} <> d[idim] = tgt_center[idim] - src_center[idim] {dup=idim} - """] + [""" + """] + [f""" <> src_coeff{i} = \ src_expansions[src_ibox - src_base_ibox, {i}] \ {{id_prefix=read_expn,dep=read_src_ibox}} - """.format(i=i) for i in range(ncoeffs_src)] + [ + """ for i in range(ncoeffs_src)] + [ - ] + self.get_translation_loopy_insns() + [""" + ] + self.get_translation_loopy_insns() + [f""" tgt_expansions[tgt_ibox - tgt_base_ibox, {i}] = \ tgt_expansions[tgt_ibox - tgt_base_ibox, {i}] + coeff{i} \ {{id_prefix=write_expn,nosync=read_expn*}} - """.format(i=i) for i in range(ncoeffs_tgt)] + [""" + """ for i in range(ncoeffs_tgt)] + [""" end """], - [ + kernel_data=[ lp.GlobalArg("target_boxes", None, shape=lp.auto, offset=lp.auto), lp.GlobalArg("centers", None, shape="dim, naligned_boxes"), @@ -1034,23 +1042,23 @@ def get_kernel(self): shape=("ntgt_level_boxes", ncoeffs_tgt), offset=lp.auto), lp.GlobalArg("src_expansions", None, shape=("nsrc_level_boxes", ncoeffs_src), offset=lp.auto), - "..." - ] + gather_loopy_arguments([self.src_expansion, self.tgt_expansion]), - name=self.name, assumptions="ntgt_boxes>=1", + "...", + *gather_loopy_arguments([self.src_expansion, self.tgt_expansion]) + ], + name=self.name, + assumptions="ntgt_boxes>=1", silenced_warnings="write_race(write_expn*)", - fixed_parameters=dict(dim=self.dim, nchildren=2**self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + fixed_parameters={"dim": self.dim, "nchildren": 2**self.dim}, + ) for knl in [self.src_expansion.kernel, self.tgt_expansion.kernel]: loopy_knl = knl.prepare_loopy_kernel(loopy_knl) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - loopy_knl = lp.set_options(loopy_knl, + return lp.set_options(loopy_knl, enforce_variable_access_ordered="no_check") - return loopy_knl - - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): """ :arg src_expansions: :arg src_box_starts: @@ -1059,18 +1067,20 @@ def __call__(self, queue, **kwargs): :arg tgt_rscale: :arg centers: """ - knl = self.get_cached_optimized_kernel() - centers = kwargs.pop("centers") # "1" may be passed for rscale, which won't have its type # meaningfully inferred. Make the type of rscale explicit. src_rscale = centers.dtype.type(kwargs.pop("src_rscale")) tgt_rscale = centers.dtype.type(kwargs.pop("tgt_rscale")) - return knl(queue, - centers=centers, - src_rscale=src_rscale, tgt_rscale=tgt_rscale, - **kwargs) + knl = self.get_cached_kernel() + result = actx.call_loopy( + knl, + centers=centers, + src_rscale=src_rscale, tgt_rscale=tgt_rscale, + **kwargs) + + return result["tgt_expansions"] # }}} diff --git a/sumpy/e2p.py b/sumpy/e2p.py index 421547d36..bd46441a5 100644 --- a/sumpy/e2p.py +++ b/sumpy/e2p.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2013 Andreas Kloeckner" __license__ = """ @@ -20,12 +23,21 @@ THE SOFTWARE. """ +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + import numpy as np + import loopy as lp -import sumpy.symbolic as sym +from loopy.version import MOST_RECENT_LANGUAGE_VERSION # ruff:ignore[unused-import] +from pytools import obj_array -from sumpy.tools import KernelCacheWrapper -from loopy.version import MOST_RECENT_LANGUAGE_VERSION +from sumpy.array_context import make_loopy_program +from sumpy.tools import KernelCacheMixin, gather_loopy_arguments + + +if TYPE_CHECKING: + from arraycontext import ArrayContext __doc__ = """ @@ -40,11 +52,10 @@ """ -# {{{ E2P base class +# {{{ E2PBase: base class -class E2PBase(KernelCacheWrapper): - def __init__(self, ctx, expansion, kernels, - name=None, device=None): +class E2PBase(KernelCacheMixin, ABC): + def __init__(self, expansion, kernels, name=None): """ :arg expansion: a subclass of :class:`sympy.expansion.ExpansionBase` :arg strength_usage: A list of integers indicating which expression @@ -53,97 +64,80 @@ def __init__(self, ctx, expansion, kernels, Default: all kernels use the same strength. """ - if device is None: - device = ctx.devices[0] - - from sumpy.kernel import (SourceTransformationRemover, - TargetTransformationRemover) + from sumpy.kernel import ( + SourceTransformationRemover, + TargetTransformationRemover, + ) sxr = SourceTransformationRemover() txr = TargetTransformationRemover() - expansion = expansion.with_kernel( - sxr(expansion.kernel)) + expansion = expansion.with_kernel(sxr(expansion.kernel)) kernels = [sxr(knl) for knl in kernels] for knl in kernels: assert txr(knl) == expansion.kernel - self.ctx = ctx self.expansion = expansion self.kernels = kernels self.name = name or self.default_name - self.device = device self.dim = expansion.dim - def get_loopy_insns_and_result_names(self): - from sumpy.symbolic import make_sym_vector - bvec = make_sym_vector("b", self.dim) - - import sumpy.symbolic as sp - rscale = sp.Symbol("rscale") + @property + def nresults(self): + return len(self.kernels) - from sumpy.assignment_collection import SymbolicAssignmentCollection - sac = SymbolicAssignmentCollection() + @abstractmethod + def default_name(self): + pass - coeff_exprs = [ - sym.Symbol(f"coeff{i}") - for i in range(len(self.expansion.get_coefficient_identifiers()))] - - result_names = [ - sac.assign_unique(f"result_{i}_p", - self.expansion.evaluate(knl, coeff_exprs, bvec, rscale, sac=sac)) - for i, knl in enumerate(self.kernels) - ] - - sac.run_global_cse() + def get_cache_key(self): + return (type(self).__name__, self.expansion, tuple(self.kernels)) - from sumpy.codegen import to_loopy_insns - loopy_insns = to_loopy_insns( - sac.assignments.items(), - vector_names={"b"}, - pymbolic_expr_maps=[ - knl.get_code_transformer() for knl in self.kernels], - retain_names=result_names, - complex_dtype=np.complex128 # FIXME - ) + def add_loopy_eval_callable( + self, loopy_knl: lp.TranslationUnit) -> lp.TranslationUnit: + inner_knl = self.expansion.loopy_evaluator(self.kernels) + loopy_knl = lp.merge([loopy_knl, inner_knl]) + loopy_knl = lp.inline_callable_kernel(loopy_knl, "e2p") + loopy_knl = lp.remove_unused_inames(loopy_knl) + for kernel in self.kernels: + loopy_knl = kernel.prepare_loopy_kernel(loopy_knl) + return lp.tag_array_axes(loopy_knl, "targets", "sep,C") - return loopy_insns, result_names + def get_loopy_args(self): + return gather_loopy_arguments((self.expansion, *tuple(self.kernels))) def get_kernel_scaling_assignment(self): from sumpy.symbolic import SympyToPymbolicMapper - from sumpy.tools import ScalingAssignmentTag sympy_conv = SympyToPymbolicMapper() - return [lp.Assignment(id=None, + return [lp.Assignment(id="kernel_scaling", assignee="kernel_scaling", expression=sympy_conv( self.expansion.kernel.get_global_scaling_const()), temp_var_type=lp.Optional(None), - tags=frozenset([ScalingAssignmentTag()]), )] - - def get_cache_key(self): - return (type(self).__name__, self.expansion, tuple(self.kernels)) - # }}} -# {{{ E2P to single box (L2P, likely) +# {{{ E2PFromSingleBox: E2P to single box (L2P, likely) class E2PFromSingleBox(E2PBase): - default_name = "e2p_from_single_box" + @property + def default_name(self): + return "e2p_from_single_box" def get_kernel(self): ncoeffs = len(self.expansion) + loopy_args = self.get_loopy_args() - loopy_insns, result_names = self.get_loopy_insns_and_result_names() - - loopy_knl = lp.make_kernel( + loopy_knl = make_loopy_program( [ "{[itgt_box]: 0<=itgt_box tgt_ibox = target_boxes[itgt_box] <> itgt_start = box_target_starts[tgt_ibox] @@ -151,27 +145,32 @@ def get_kernel(self): <> center[idim] = centers[idim, tgt_ibox] {id=fetch_center} - """] + [""" - <> coeff{coeffidx} = \ - src_expansions[tgt_ibox - src_base_ibox, {coeffidx}] - """.format(coeffidx=i) for i in range(ncoeffs)] + [""" + <> coeffs[icoeff] = \ + src_expansions[tgt_ibox - src_base_ibox, icoeff] \ + {id=fetch_coeffs} for itgt - <> b[idim] = targets[idim, itgt] - center[idim] {dup=idim} - - """] + loopy_insns + [""" - - result[{resultidx},itgt] = \ - kernel_scaling * result_{resultidx}_p \ - {{id_prefix=write_result}} - """.format(resultidx=i) for i in range(len(result_names)) - ] + [""" + <> tgt[idim] = targets[idim, itgt] {id=fetch_tgt,dup=idim} + <> result_temp[iknl] = 0 {id=init_result,dup=iknl} + [iknl]: result_temp[iknl] = e2p( + [iknl]: result_temp[iknl], + [icoeff]: coeffs[icoeff], + [idim]: center[idim], + [idim]: tgt[idim], + rscale, + itgt, + ntargets, + targets, + """ + ",".join(arg.name for arg in loopy_args) + """ + ) {dep=fetch_coeffs:fetch_center:init_result:fetch_tgt,\ + id=update_result} + result[iknl, itgt] = result_temp[iknl] * kernel_scaling \ + {id=write_result,dep=update_result} end end """], [ - lp.GlobalArg("targets", None, shape=(self.dim, "ntargets"), - dim_tags="sep,C"), + lp.GlobalArg("targets", None, shape=(self.dim, "ntargets")), lp.GlobalArg("box_target_starts,box_target_counts_nonchild", None, shape=None), lp.GlobalArg("centers", None, shape="dim, naligned_boxes"), @@ -183,32 +182,28 @@ def get_kernel(self): lp.ValueArg("nsrc_level_boxes,naligned_boxes", np.int32), lp.ValueArg("src_base_ibox", np.int32), lp.ValueArg("ntargets", np.int32), - "..." - ] + [arg.loopy_arg for arg in self.expansion.get_args()], + *loopy_args, + ... + ], name=self.name, assumptions="ntgt_boxes>=1", - silenced_warnings="write_race(write_result*)", - default_offset=lp.auto, - fixed_parameters=dict(dim=self.dim, nresults=len(result_names)), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + silenced_warnings="write_race(*_result)", + fixed_parameters={"dim": self.dim, "nresults": len(self.kernels), + "ncoeffs": ncoeffs}) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - for knl in self.kernels: - loopy_knl = knl.prepare_loopy_kernel(loopy_knl) - - return loopy_knl + loopy_knl = lp.tag_inames(loopy_knl, "iknl*:unr") + return self.add_loopy_eval_callable(loopy_knl) def get_optimized_kernel(self): # FIXME knl = self.get_kernel() - knl = lp.tag_inames(knl, dict(itgt_box="g.0")) - knl = self._allow_redundant_execution_of_knl_scaling(knl) - knl = lp.set_options(knl, + knl = lp.tag_inames(knl, {"itgt_box": "g.0"}) + knl = lp.add_inames_to_insn(knl, "itgt_box", "id:kernel_scaling") + return lp.set_options(knl, enforce_variable_access_ordered="no_check") - return knl - - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): """ :arg expansions: :arg target_boxes: @@ -217,71 +212,84 @@ def __call__(self, queue, **kwargs): :arg centers: :arg targets: """ - knl = self.get_cached_optimized_kernel() centers = kwargs.pop("centers") # "1" may be passed for rscale, which won't have its type # meaningfully inferred. Make the type of rscale explicit. rscale = centers.dtype.type(kwargs.pop("rscale")) - return knl(queue, centers=centers, rscale=rscale, **kwargs) + knl = self.get_cached_kernel() + result = actx.call_loopy( + knl, + centers=centers, rscale=rscale, **kwargs) + + return obj_array.new_1d([result[f"result_s{i}"] for i in range(self.nresults)]) # }}} -# {{{ E2P from CSR-like interaction list +# {{{ E2PFromCSR: E2P from CSR-like interaction list class E2PFromCSR(E2PBase): - default_name = "e2p_from_csr" + @property + def default_name(self): + return "e2p_from_csr" def get_kernel(self): ncoeffs = len(self.expansion) + loopy_args = self.get_loopy_args() - loopy_insns, result_names = self.get_loopy_insns_and_result_names() - - loopy_knl = lp.make_kernel( + loopy_knl = make_loopy_program( [ "{[itgt_box]: 0<=itgt_box tgt_ibox = target_boxes[itgt_box] <> itgt_start = box_target_starts[tgt_ibox] <> itgt_end = itgt_start+box_target_counts_nonchild[tgt_ibox] for itgt - <> tgt[idim] = targets[idim,itgt] + <> tgt[idim] = targets[idim,itgt] {id=fetch_tgt,dup=idim} <> isrc_box_start = source_box_starts[itgt_box] <> isrc_box_end = source_box_starts[itgt_box+1] + <> result_temp[iknl] = 0 {id=init_result,dup=iknl} for isrc_box <> src_ibox = source_box_lists[isrc_box] - """] + [""" - <> coeff{coeffidx} = \ - src_expansions[src_ibox - src_base_ibox, {coeffidx}] - """.format(coeffidx=i) for i in range(ncoeffs)] + [""" - - <> center[idim] = centers[idim, src_ibox] {dup=idim} - <> b[idim] = tgt[idim] - center[idim] {dup=idim} - - """] + loopy_insns + [""" + <> coeffs[icoeff] = \ + src_expansions[src_ibox - src_base_ibox, icoeff] \ + {id=fetch_coeffs,dup=icoeff} + <> center[idim] = centers[idim, src_ibox] \ + {dup=idim,id=fetch_center} + [iknl]: result_temp[iknl] = e2p( + [iknl]: result_temp[iknl], + [icoeff]: coeffs[icoeff], + [idim]: center[idim], + [idim]: tgt[idim], + rscale, + itgt, + ntargets, + targets, + """ + ",".join(arg.name for arg in loopy_args) + """ + ) {id=update_result, \ + dep=fetch_coeffs:fetch_center:fetch_tgt:init_result} end - """] + [""" - result[{resultidx}, itgt] = result[{resultidx}, itgt] + \ - kernel_scaling * simul_reduce(sum, isrc_box, - result_{resultidx}_p) {{id_prefix=write_result}} - """.format(resultidx=i) for i in range(len(result_names))] + [""" + result[iknl, itgt] = result[iknl, itgt] + result_temp[iknl] \ + * kernel_scaling \ + {dep=update_result:init_result,id=write_result,dup=iknl} end end """], [ - lp.GlobalArg("targets", None, shape=(self.dim, "ntargets"), - dim_tags="sep,C"), + lp.GlobalArg("targets", None, shape=(self.dim, "ntargets")), lp.GlobalArg("box_target_starts,box_target_counts_nonchild", None, shape=None), lp.GlobalArg("centers", None, shape="dim, aligned_nboxes"), @@ -294,42 +302,45 @@ def get_kernel(self): dim_tags="sep,C"), lp.GlobalArg("source_box_starts, source_box_lists,", None, shape=None, offset=lp.auto), + *loopy_args, "..." - ] + [arg.loopy_arg for arg in self.expansion.get_args()], + ], name=self.name, assumptions="ntgt_boxes>=1", - silenced_warnings="write_race(write_result*)", - default_offset=lp.auto, - fixed_parameters=dict( - dim=self.dim, - nresults=len(result_names)), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + silenced_warnings="write_race(*_result)", + fixed_parameters={ + "ncoeffs": ncoeffs, + "dim": self.dim, + "nresults": len(self.kernels)}) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") + loopy_knl = lp.tag_inames(loopy_knl, "iknl*:unr") loopy_knl = lp.prioritize_loops(loopy_knl, "itgt_box,itgt,isrc_box") - for knl in self.kernels: - loopy_knl = knl.prepare_loopy_kernel(loopy_knl) - - return loopy_knl + loopy_knl = self.add_loopy_eval_callable(loopy_knl) + return lp.tag_array_axes(loopy_knl, "targets", "sep,C") def get_optimized_kernel(self): # FIXME knl = self.get_kernel() - knl = lp.tag_inames(knl, dict(itgt_box="g.0")) - knl = self._allow_redundant_execution_of_knl_scaling(knl) - knl = lp.set_options(knl, + knl = lp.tag_inames(knl, {"itgt_box": "g.0"}) + knl = lp.add_inames_to_insn(knl, "itgt_box", "id:kernel_scaling") + return lp.set_options(knl, enforce_variable_access_ordered="no_check") - return knl - - def __call__(self, queue, **kwargs): - knl = self.get_cached_optimized_kernel() + def __call__(self, actx: ArrayContext, **kwargs): centers = kwargs.pop("centers") # "1" may be passed for rscale, which won't have its type # meaningfully inferred. Make the type of rscale explicit. rscale = centers.dtype.type(kwargs.pop("rscale")) - return knl(queue, centers=centers, rscale=rscale, **kwargs) + knl = self.get_cached_kernel() + result = actx.call_loopy( + knl, + centers=centers, + rscale=rscale, + **kwargs) + + return obj_array.new_1d([result[f"result_s{i}"] for i in range(self.nresults)]) # }}} diff --git a/sumpy/expansion/__init__.py b/sumpy/expansion/__init__.py index 26c0d00a8..b3adc5995 100644 --- a/sumpy/expansion/__init__.py +++ b/sumpy/expansion/__init__.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2012 Andreas Kloeckner" __license__ = """ @@ -19,54 +22,94 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ - import logging +from abc import ABC, abstractmethod +from dataclasses import dataclass, field, replace +from typing import TYPE_CHECKING, Any, ClassVar, Protocol, TypeAlias, overload +from warnings import warn + +from typing_extensions import Self, override + +import pymbolic.primitives as prim from pytools import memoize_method + import sumpy.symbolic as sym +from sumpy.expansion.diff_op import DerivativeIdentifier from sumpy.tools import add_mi -from typing import List, Tuple + + +if TYPE_CHECKING: + from collections.abc import Callable, Hashable, Sequence + + import loopy as lp + from pymbolic.typing import Expression + + from sumpy.assignment_collection import SymbolicAssignmentCollection + from sumpy.expansion.diff_op import MultiIndex + from sumpy.expansion.local import LocalExpansionBase + from sumpy.expansion.multipole import MultipoleExpansionBase + from sumpy.kernel import KernelArgument, ScalarKernel + + +logger = logging.getLogger(__name__) + __doc__ = """ .. autoclass:: ExpansionBase + +Expansion Wranglers +^^^^^^^^^^^^^^^^^^^ + +.. autoclass:: ExpansionTermsWrangler +.. autoclass:: FullExpansionTermsWrangler .. autoclass:: LinearPDEBasedExpansionTermsWrangler Expansion Factories ^^^^^^^^^^^^^^^^^^^ +.. autoclass:: LocalExpansionFactory +.. autoclass:: MultipoleExpansionFactory .. autoclass:: ExpansionFactoryBase .. autoclass:: DefaultExpansionFactory .. autoclass:: VolumeTaylorExpansionFactory """ -logger = logging.getLogger(__name__) - -# {{{ base class +# {{{ expansion base -class ExpansionBase: +@dataclass(frozen=True) +class ExpansionBase(ABC): """ - .. automethod:: with_kernel - .. automethod:: __len__ + .. autoattribute:: kernel + .. autoattribute:: order + .. autoattribute:: use_rscale + .. automethod:: get_coefficient_identifiers .. automethod:: coefficients_from_source - .. automethod:: translate_from + .. automethod:: coefficients_from_source_vec + .. automethod:: loopy_expansion_formation + .. automethod:: evaluate + .. automethod:: loopy_evaluator + + .. automethod:: with_kernel + .. automethod:: copy + + .. automethod:: __len__ .. automethod:: __eq__ .. automethod:: __ne__ """ - init_arg_names = ("kernel", "order", "use_rscale") - - def __init__(self, kernel, order, use_rscale=None): - # Don't be tempted to remove target derivatives here. - # Line Taylor QBX can't do without them, because it can't - # apply those derivatives to the expanded quantity. - self.kernel = kernel - self.order = order + kernel: ScalarKernel + order: int + use_rscale: bool = field(kw_only=True, default=True) - if use_rscale is None: - use_rscale = True + def __post_init__(self) -> None: + if self.use_rscale is None: + warn("use_rscale is None in ExpansionBase. " + "This is deprecated and will stop working in 2026.", + DeprecationWarning, stacklevel=1) - self.use_rscale = use_rscale + object.__setattr__(self, "use_rscale", True) # {{{ propagate kernel interface @@ -74,40 +117,47 @@ def __init__(self, kernel, order, use_rscale=None): # to make it fit into sumpy.qbx.LayerPotential. @property - def dim(self): + def dim(self) -> int: return self.kernel.dim @property - def is_complex_valued(self): + def is_complex_valued(self) -> bool: return self.kernel.is_complex_valued - def get_code_transformer(self): + def get_code_transformer(self) -> Callable[[Expression], Expression]: return self.kernel.get_code_transformer() - def get_global_scaling_const(self): + def get_global_scaling_const(self) -> sym.Expr: return self.kernel.get_global_scaling_const() - def get_args(self): + def get_args(self) -> Sequence[KernelArgument]: return self.kernel.get_args() - def get_source_args(self): + def get_source_args(self) -> Sequence[KernelArgument]: return self.kernel.get_source_args() # }}} - def with_kernel(self, kernel): - return type(self)(kernel, self.order, self.use_rscale) + # {{{ abstract interface - def __len__(self): - return len(self.get_coefficient_identifiers()) + @abstractmethod + def get_storage_index(self, mi: MultiIndex) -> int: + pass - def get_coefficient_identifiers(self): + @abstractmethod + def get_coefficient_identifiers(self) -> Sequence[MultiIndex]: """ - Returns the identifiers of the coefficients that actually get stored. + :returns: the identifiers of the coefficients that actually get stored. """ - raise NotImplementedError - def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): + @abstractmethod + def coefficients_from_source(self, + kernel: ScalarKernel, + avec: sym.Matrix, + bvec: sym.Matrix | None, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: """Form an expansion from a source point. :arg avec: vector from source to center. @@ -119,11 +169,17 @@ def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): :returns: a list of :mod:`sympy` expressions representing the coefficients of the expansion. """ - raise NotImplementedError - def coefficients_from_source_vec(self, kernels, avec, bvec, rscale, weights, - sac=None): + def coefficients_from_source_vec(self, + kernels: Sequence[ScalarKernel], + avec: sym.Matrix, + bvec: sym.Matrix | None, + rscale: sym.Expr, + weights: Sequence[sym.Expr], + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: """Form an expansion with a linear combination of kernels and weights. + *kernels* and *weights* must have the same length. :arg avec: vector from source to center. :arg bvec: vector from center to target. Not usually necessary, @@ -134,90 +190,119 @@ def coefficients_from_source_vec(self, kernels, avec, bvec, rscale, weights, :returns: a list of :mod:`sympy` expressions representing the coefficients of the expansion. """ - result = [0]*len(self) - for knl, weight in zip(kernels, weights): + result: list[sym.Expr] = [sym.sympify(0)]*len(self) + for knl, weight in zip(kernels, weights, strict=True): coeffs = self.coefficients_from_source(knl, avec, bvec, rscale, sac=sac) for i in range(len(result)): result[i] += weight * coeffs[i] return result - def evaluate(self, kernel, coeffs, bvec, rscale, sac=None): + def loopy_expansion_formation(self, + kernels: Sequence[ScalarKernel], + strength_usage: Sequence[int], + nstrengths: int + ) -> lp.TranslationUnit: """ - :return: a :mod:`sympy` expression corresponding + :returns: a :mod:`loopy` kernel that returns the coefficients + for the expansion given by *kernels* with each kernel using + the strength given by *strength_usage*. + """ + from sumpy.expansion.loopy import make_p2e_loopy_kernel + return make_p2e_loopy_kernel(self, kernels, strength_usage, nstrengths) + + @abstractmethod + def evaluate(self, + kernel: ScalarKernel, + coeffs: Sequence[sym.Expr], + bvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> sym.Expr: + """ + :returns: a :mod:`sympy` expression corresponding to the evaluated expansion with the coefficients in *coeffs*. """ - raise NotImplementedError + def loopy_evaluator(self, kernels: Sequence[ScalarKernel]) -> lp.TranslationUnit: + """ + :returns: a :mod:`loopy` kernel that returns the evaluated + target transforms of the potential given by *kernels*. + """ + from sumpy.expansion.loopy import make_e2p_loopy_kernel + return make_e2p_loopy_kernel(self, kernels) - def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale, sac=None): - raise NotImplementedError + # }}} - def update_persistent_hash(self, key_hash, key_builder): - key_hash.update(type(self).__name__.encode("utf8")) - key_builder.rec(key_hash, self.kernel) - key_builder.rec(key_hash, self.order) - key_builder.rec(key_hash, self.use_rscale) + # {{{ copy - def __eq__(self, other): - return ( - type(self) == type(other) - and self.kernel == other.kernel - and self.order == other.order - and self.use_rscale == other.use_rscale) + def with_kernel(self, kernel: ScalarKernel) -> ExpansionBase: + return replace(self, kernel=kernel) - def __ne__(self, other): - return not self.__eq__(other) + def copy(self, **kwargs: Any) -> Self: + return replace(self, **kwargs) - def copy(self, **kwargs): - new_kwargs = { - name: getattr(self, name) - for name in self.init_arg_names} + # }}} - for name in self.init_arg_names: - new_kwargs[name] = kwargs.pop(name, getattr(self, name)) + def __len__(self) -> int: + return len(self.get_coefficient_identifiers()) - if kwargs: - raise TypeError( - "unexpected keyword arguments '{}'".format(", ".join(kwargs))) +# }}} - return type(self)(**new_kwargs) +# {{{ expansion terms wrangler -# }}} +@dataclass(frozen=True) +class ExpansionTermsWrangler(ABC): + """ + .. autoattribute:: order + .. autoattribute:: dim + .. autoattribute:: max_mi + .. automethod:: copy + .. automethod:: get_coefficient_identifiers + .. automethod:: get_full_kernel_derivatives_from_stored + .. automethod:: get_stored_mpole_coefficients_from_full -# {{{ expansion terms wrangler + .. automethod:: get_full_coefficient_identifiers + """ -class ExpansionTermsWrangler: + order: int + dim: int + max_mi: MultiIndex | None - init_arg_names = ("order", "dim", "max_mi") + # {{{ abstract interface - def __init__(self, order, dim, max_mi=None): - self.order = order - self.dim = dim - self.max_mi = max_mi + @abstractmethod + def get_coefficient_identifiers(self) -> Sequence[MultiIndex]: + ... - def get_coefficient_identifiers(self): - raise NotImplementedError + @abstractmethod + def get_full_kernel_derivatives_from_stored(self, + stored_kernel_derivatives: Sequence[sym.Expr], + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: + ... - def get_full_kernel_derivatives_from_stored(self, stored_kernel_derivatives, - rscale, sac=None): - raise NotImplementedError + @abstractmethod + def get_stored_mpole_coefficients_from_full(self, + full_mpole_coefficients: Sequence[sym.Expr], + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> Sequence[sym.Expr]: + ... - def get_stored_mpole_coefficients_from_full(self, full_mpole_coefficients, - rscale, sac=None): - raise NotImplementedError + # }}} @memoize_method - def get_full_coefficient_identifiers(self): + def get_full_coefficient_identifiers(self) -> Sequence[MultiIndex]: """ Returns identifiers for every coefficient in the complete expansion. """ from pytools import ( - generate_nonnegative_integer_tuples_summing_to_at_most - as gnitstam) + generate_nonnegative_integer_tuples_summing_to_at_most as gnitstam, + ) res = sorted(gnitstam(self.order, self.dim), key=sum) @@ -227,21 +312,12 @@ def get_full_coefficient_identifiers(self): return [mi for mi in res if all(mi[i] <= self.max_mi[i] for i in range(self.dim))] - def copy(self, **kwargs): - new_kwargs = { - name: getattr(self, name) - for name in self.init_arg_names} - - for name in self.init_arg_names: - new_kwargs[name] = kwargs.pop(name, getattr(self, name)) + def copy(self, **kwargs: Any) -> Self: + return replace(self, **kwargs) - if kwargs: - raise TypeError( - "unexpected keyword arguments '{}'".format(", ".join(kwargs))) + # {{{ hyperplane helpers - return type(self)(**new_kwargs) - - def _get_mi_hyperpplanes(self) -> List[Tuple[int, int]]: + def _get_mi_hyperplanes(self) -> list[tuple[int, int]]: r""" Coefficient storage is organized into "hyperplanes" in multi-index space. Potentially only a subset of these hyperplanes contain @@ -257,11 +333,12 @@ def _get_mi_hyperpplanes(self) -> List[Tuple[int, int]]: axis `d`. """ d = self.dim - 1 - hyperplanes = [(d, const) for const in range(self.order + 1)] - return hyperplanes + return [(d, const) for const in range(self.order + 1)] @memoize_method - def _split_coeffs_into_hyperplanes(self) -> List[Tuple[int, List[Tuple[int]]]]: + def _split_coeffs_into_hyperplanes( + self + ) -> list[tuple[int, list[MultiIndex]]]: r""" This splits the coefficients into :math:`O(p)` disjoint sets so that for each set, all the identifiers have the form, @@ -286,11 +363,13 @@ def _split_coeffs_into_hyperplanes(self) -> List[Tuple[int, List[Tuple[int]]]]: (2, [(0, 0, 1), (1, 0, 1), (2, 0, 1), (0, 1, 1), (1, 1, 1), (0, 2, 1)]), ] """ - hyperplanes = self._get_mi_hyperpplanes() - res = [] - seen_mis = set() + hyperplanes = self._get_mi_hyperplanes() + + res: list[tuple[int, list[MultiIndex]]] = [] + seen_mis: set[MultiIndex] = set() + for d, const in hyperplanes: - coeffs_in_hyperplane = [] + coeffs_in_hyperplane: list[MultiIndex] = [] for mi in self.get_coefficient_identifiers(): # Check if the multi-index is in this hyperplane and # if it is not in any of the hyperplanes we saw before @@ -299,26 +378,93 @@ def _split_coeffs_into_hyperplanes(self) -> List[Tuple[int, List[Tuple[int]]]]: if mi[d] == const and mi not in seen_mis: coeffs_in_hyperplane.append(mi) seen_mis.add(mi) + res.append((d, coeffs_in_hyperplane)) return res + # }}} + class FullExpansionTermsWrangler(ExpansionTermsWrangler): + @overload + def get_storage_index(self, + mi: MultiIndex, + order: int | None = None) -> int: ... + + @overload + def get_storage_index(self, + mi: tuple[prim.ExpressionNode, ...], + order: int | prim.ExpressionNode | None = None, + ) -> prim.ExpressionNode: ... + + def get_storage_index(self, + mi: MultiIndex | tuple[prim.ExpressionNode, ...], + order: int | prim.ExpressionNode | None = None, + ) -> int | prim.ExpressionNode: + if not order: + order = sum(mi) + + if self.dim == 3: + return (order*(order + 1)*(order + 2))//6 + \ + (order + 2)*mi[2] - (mi[2]*(mi[2] + 1))//2 + mi[1] + elif self.dim == 2: + return (order*(order + 1))//2 + mi[1] + else: + raise NotImplementedError + + @override + def get_coefficient_identifiers(self) -> Sequence[MultiIndex]: + return super().get_full_coefficient_identifiers() + + @override + def get_full_kernel_derivatives_from_stored( + self, + stored_kernel_derivatives: Sequence[sym.Expr], + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> Sequence[sym.Expr]: + return stored_kernel_derivatives - get_coefficient_identifiers = ( - ExpansionTermsWrangler.get_full_coefficient_identifiers) + @override + def get_stored_mpole_coefficients_from_full(self, + full_mpole_coefficients: Sequence[sym.Expr], + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> Sequence[sym.Expr]: + return self.get_full_kernel_derivatives_from_stored( + full_mpole_coefficients, rscale, sac=sac) - def get_full_kernel_derivatives_from_stored(self, stored_kernel_derivatives, - rscale, sac=None): - return stored_kernel_derivatives + @memoize_method + def _get_mi_ordering_key_and_axis_permutation( + self + ) -> tuple[Callable[[MultiIndex | DerivativeIdentifier], tuple[int, ...]], + Sequence[int]]: + """ + Returns a degree lexicographic order as a callable that can be used as a + ``sort`` key on multi-indices and a permutation of the axis ordered + from the slowest varying axis to the fastest varying axis of the + multi-indices when sorted. + """ + axis_permutation = list(reversed(range(self.dim))) + + def mi_key(ident: MultiIndex | DerivativeIdentifier) -> tuple[int, ...]: + if isinstance(ident, DerivativeIdentifier): # ruff:ignore[if-else-block-instead-of-if-exp] + mi = ident.mi + else: + mi = ident + + return (sum(mi), *list(reversed(mi))) - get_stored_mpole_coefficients_from_full = ( - get_full_kernel_derivatives_from_stored) + return mi_key, axis_permutation +# }}} # {{{ sparse matrix-vector multiplication +LinearCombinationCoefficients: TypeAlias = "Sequence[Sequence[tuple[int, sym.Expr]]]" + + class CSEMatVecOperator: """ A class to facilitate a fast matrix vector multiplication with @@ -329,36 +475,42 @@ class CSEMatVecOperator: of values from input vector and a linear combination of values from the output vector. - .. attribute:: from_input_coeffs_by_row - - An object of type ``List[List[Tuple[int, Any]]]``. Each element - in the list represents a row of the matrix using a linear combination - of values from the input vector. Each element has the form - ``(index of input vector, coeff)``. - - Number of rows in the matrix represented is equal to the - length of the `from_input_coeffs_by_row` list. + .. autoattribute:: from_input_coeffs_by_row + .. autoattribute:: from_output_coeffs_by_row + .. autoattribute:: shape + """ - .. attribute:: from_output_coeffs_by_row + shape: tuple[int, int] - An object of type ``List[List[Tuple[int, Any]]]``. Each element - in the list represents a row of the matrix using a linear combination - of values from the output vector. Each element has the form - ``(index of output vector, coeff)``. + from_input_coeffs_by_row: LinearCombinationCoefficients + """Each element in the list represents a row of the matrix using a + linear combination of values from the input vector. Each element has the form + ``(index of input vector, coeff)``. - .. attribute:: shape + Number of rows in the matrix represented is equal to the + length of the `from_input_coeffs_by_row` list.""" - Shape of the matrix as a tuple. - """ + from_output_coeffs_by_row: LinearCombinationCoefficients + """Each element in the list represents a row of the matrix using a linear + combination of values from the output vector. Each element has the form + ``(index of output vector, coeff)``.""" - def __init__(self, from_input_coeffs_by_row, from_output_coeffs_by_row, shape): + def __init__(self, + from_input_coeffs_by_row: LinearCombinationCoefficients, + from_output_coeffs_by_row: LinearCombinationCoefficients, + shape: tuple[int, int]) -> None: self.from_input_coeffs_by_row = from_input_coeffs_by_row self.from_output_coeffs_by_row = from_output_coeffs_by_row self.shape = shape assert len(self.from_input_coeffs_by_row) == shape[0] assert len(self.from_output_coeffs_by_row) == shape[0] - def matvec(self, inp, wrap_intermediate=lambda x: x): + def matvec(self, + inp: Sequence[sym.Expr], + wrap_intermediate: + Callable[[sym.Expr], sym.Expr] + = lambda x: x + ) -> Sequence[sym.Expr]: """ :arg inp: vector for the matrix vector multiplication @@ -367,68 +519,85 @@ def matvec(self, inp, wrap_intermediate=lambda x: x): final expressions in the vector resulting in an expensive matvec. """ assert len(inp) == self.shape[1] - out = [] + + out: list[sym.Expr] = [] for i in range(self.shape[0]): - value = 0 + value = sym.sympify(0) + for input_index, coeff in self.from_input_coeffs_by_row[i]: value += inp[input_index] * coeff + for output_index, coeff in self.from_output_coeffs_by_row[i]: value += out[output_index] * coeff + out.append(wrap_intermediate(value)) + return out - def transpose_matvec(self, inp, wrap_intermediate=lambda x: x): + def transpose_matvec(self, + inp: Sequence[sym.Expr], + wrap_intermediate: + Callable[[sym.Expr], sym.Expr] + = lambda x: x + ) -> Sequence[sym.Expr]: assert len(inp) == self.shape[0] - res = [0]*self.shape[1] + + res: list[sym.Expr] = [sym.sympify(0)]*self.shape[1] expr_all = list(inp) + for i in reversed(range(self.shape[0])): for output_index, coeff in self.from_output_coeffs_by_row[i]: expr_all[output_index] += expr_all[i] * coeff expr_all[output_index] = wrap_intermediate(expr_all[output_index]) + for input_index, coeff in self.from_input_coeffs_by_row[i]: res[input_index] += expr_all[i] * coeff + return res # }}} +# {{{ LinearPDEBasedExpansionTermsWrangler + +@dataclass(frozen=True) class LinearPDEBasedExpansionTermsWrangler(ExpansionTermsWrangler): """ .. automethod:: __init__ """ - init_arg_names = ("order", "dim", "knl", "max_mi") - - def __init__(self, order, dim, knl, max_mi=None): - r""" - :param order: order of the expansion - :param dim: number of dimensions - :param knl: kernel for the PDE - """ - super().__init__(order, dim, max_mi) - self.knl = knl + knl: ScalarKernel - def get_coefficient_identifiers(self): + @override + def get_coefficient_identifiers(self) -> Sequence[MultiIndex]: return self.stored_identifiers - def get_full_kernel_derivatives_from_stored(self, stored_kernel_derivatives, - rscale, sac=None): - + @override + def get_full_kernel_derivatives_from_stored(self, + stored_kernel_derivatives: Sequence[sym.Expr], + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: from sumpy.tools import add_to_sac + projection_matrix = self.get_projection_matrix(rscale) return projection_matrix.matvec(stored_kernel_derivatives, lambda x: add_to_sac(sac, x)) - def get_stored_mpole_coefficients_from_full(self, full_mpole_coefficients, - rscale, sac=None): - + @override + def get_stored_mpole_coefficients_from_full(self, + full_mpole_coefficients: Sequence[sym.Expr], + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> Sequence[sym.Expr]: from sumpy.tools import add_to_sac + projection_matrix = self.get_projection_matrix(rscale) return projection_matrix.transpose_matvec(full_mpole_coefficients, lambda x: add_to_sac(sac, x)) @property - def stored_identifiers(self): + def stored_identifiers(self) -> Sequence[MultiIndex]: stored_identifiers, _ = self.get_stored_ids_and_unscaled_projection_matrix() return stored_identifiers @@ -441,7 +610,10 @@ def stored_identifiers(self): # the axes so that the axis with the on-axis coefficient comes first in the # multi-index tuple. @memoize_method - def _get_mi_ordering_key(self): + def _get_mi_ordering_key_and_axis_permutation( + self + ) -> tuple[Callable[[MultiIndex | DerivativeIdentifier], tuple[int, ...]], + Sequence[int]]: """ A degree lexicographic order with the slowest varying index depending on the PDE is used, returned as a callable that can be used as a @@ -450,6 +622,9 @@ def _get_mi_ordering_key(self): multipole-to-multipole translation to get lower error bounds. The slowest varying index is chosen such that the multipole-to-local translation cost is optimized. + + Also returns a permutation of the axis ordered from the slowest varying + axis to the fastest varying axis of the multi-indices when sorted. """ dim = self.dim deriv_id_to_coeff, = self.knl.get_pde_as_diff_op().eqs @@ -465,19 +640,21 @@ def _get_mi_ordering_key(self): from sumpy.expansion.diff_op import DerivativeIdentifier - def mi_key(ident): - if isinstance(ident, DerivativeIdentifier): + def mi_key(ident: MultiIndex | DerivativeIdentifier) -> tuple[int, ...]: + if isinstance(ident, DerivativeIdentifier): # ruff:ignore[if-else-block-instead-of-if-exp] mi = ident.mi else: mi = ident key = [sum(mi)] - for i in range(dim): - key.append(mi[axis_permutation[i]]) + + key.extend(mi[axis_permutation[i]] for i in range(dim)) + return tuple(key) - return mi_key + return mi_key, axis_permutation - def _get_mi_hyperpplanes(self) -> List[Tuple[int, int]]: + @override + def _get_mi_hyperplanes(self) -> list[tuple[int, int]]: mis = self.get_full_coefficient_identifiers() mi_to_index = {mi: i for i, mi in enumerate(mis)} @@ -487,12 +664,12 @@ def _get_mi_hyperpplanes(self) -> List[Tuple[int, int]]: if not all(ident.mi in mi_to_index for ident in deriv_id_to_coeff): # The order of the expansion is less than the order of the PDE. # Treat as if full expansion. - hyperplanes = super()._get_mi_hyperpplanes() + hyperplanes = super()._get_mi_hyperplanes() else: # Calculate the multi-index that appears last in in the PDE in # the degree lexicographic order given by - # _get_mi_ordering_key. - ordering_key = self._get_mi_ordering_key() + # _get_mi_ordering_key_and_axis_permutation. + ordering_key, _ = self._get_mi_ordering_key_and_axis_permutation() max_mi = max(deriv_id_to_coeff, key=ordering_key).mi hyperplanes = [(d, const) for d in range(self.dim) @@ -500,19 +677,89 @@ def _get_mi_hyperpplanes(self) -> List[Tuple[int, int]]: return hyperplanes - def get_full_coefficient_identifiers(self): + @override + def get_full_coefficient_identifiers(self) -> Sequence[MultiIndex]: identifiers = super().get_full_coefficient_identifiers() - key = self._get_mi_ordering_key() - return list(sorted(identifiers, key=key)) + key, _ = self._get_mi_ordering_key_and_axis_permutation() + return sorted(identifiers, key=key) + + @overload + def get_storage_index(self, + mi: MultiIndex, + order: int | None = None) -> int: ... + + @overload + def get_storage_index(self, + mi: tuple[prim.ExpressionNode, ...], + order: int | prim.ExpressionNode | None = None, + ) -> prim.ExpressionNode: ... + + def get_storage_index(self, + mi: MultiIndex | tuple[prim.ExpressionNode, ...], + order: int | prim.ExpressionNode | None = None, + ) -> int | prim.ExpressionNode: + if order is None: + order = sum(mi) + + ordering_key, axis_permutation = \ + self._get_mi_ordering_key_and_axis_permutation() + deriv_id_to_coeff, = self.knl.get_pde_as_diff_op().eqs + max_mi = max(deriv_id_to_coeff, key=ordering_key).mi + + if all(m != 0 for m in max_mi): + raise NotImplementedError("non-elliptic PDEs") + + c = max_mi[axis_permutation[0]] + + new_mi = list(mi) + new_mi[axis_permutation[0]], new_mi[0] = mi[0], mi[axis_permutation[0]] + mi = tuple(new_mi) + + if self.dim == 3: + if all(isinstance(axis, int) for axis in mi): + if order < c - 1: + return ( + (order*(order + 1)*(order + 2))//6 + + (order + 2)*mi[0] + - (mi[0]*(mi[0] + 1))//2 + + mi[1]) + else: + return ( + (c*(c-1)*(c-2))//6 + + (c * order * (2 + order - c) + mi[0]*(3 - mi[0]+2*order))//2 + + mi[1]) + else: + return prim.If(prim.Comparison(order, "<", c - 1), + (order*(order + 1)*(order + 2))//6 + + (order + 2)*mi[0] + - (mi[0]*(mi[0] + 1))//2 + + mi[1], + (c*(c-1)*(c-2))//6 + + (c * order * (2 + order - c) + mi[0]*(3 - mi[0]+2*order))//2 + + mi[1] + ) + elif self.dim == 2: + if all(isinstance(axis, int) for axis in mi): + if order < c - 1: + return (order*(order + 1))//2 + mi[0] + else: + return (c*(c-1))//2 + c*(order - c + 1) + mi[0] + else: + return prim.If(prim.Comparison(order, "<", c - 1), + (order*(order + 1))//2 + mi[0], + (c*(c-1))//2 + c*(order - c + 1) + mi[0]) + else: + raise NotImplementedError @memoize_method - def get_stored_ids_and_unscaled_projection_matrix(self): + def get_stored_ids_and_unscaled_projection_matrix( + self + ) -> tuple[Sequence[MultiIndex], CSEMatVecOperator]: from pytools import ProcessLogger plog = ProcessLogger(logger, "compute PDE for Taylor coefficients") mis = self.get_full_coefficient_identifiers() - coeff_ident_enumerate_dict = {tuple(mi): i for - (i, mi) in enumerate(mis)} + coeff_ident_enumerate_dict = {tuple(mi): i for (i, mi) in enumerate(mis)} diff_op = self.knl.get_pde_as_diff_op() assert len(diff_op.eqs) == 1 @@ -522,25 +769,30 @@ def get_stored_ids_and_unscaled_projection_matrix(self): # Order of the expansion is less than the order of the PDE. # In that case, the compression matrix is the identity matrix # and there's nothing to project - from_input_coeffs_by_row = [[(i, 1)] for i in range(len(mis))] - from_output_coeffs_by_row = [[] for _ in range(len(mis))] + from_input_coeffs_by_row: LinearCombinationCoefficients = [ + [(i, sym.sympify(1))] for i in range(len(mis))] + from_output_coeffs_by_row: LinearCombinationCoefficients = [ + [] for _ in range(len(mis))] + shape = (len(mis), len(mis)) op = CSEMatVecOperator(from_input_coeffs_by_row, from_output_coeffs_by_row, shape) + plog.done() + return mis, op - ordering_key = self._get_mi_ordering_key() - max_mi = max((ident for ident in mi_to_coeff.keys()), key=ordering_key) + ordering_key, _ = self._get_mi_ordering_key_and_axis_permutation() + max_mi = max((ident for ident in mi_to_coeff), key=ordering_key) max_mi_coeff = mi_to_coeff[max_mi] max_mi_mult = -1/sym.sympify(max_mi_coeff) - def is_stored(mi): + def is_stored(mi: MultiIndex) -> bool: """ A multi_index mi is not stored if mi >= max_mi """ return any(mi[d] < max_mi[d] for d in range(self.dim)) - stored_identifiers = [] + stored_identifiers: list[MultiIndex] = [] from_input_coeffs_by_row = [] from_output_coeffs_by_row = [] @@ -550,30 +802,32 @@ def is_stored(mi): if is_stored(mi): idx = len(stored_identifiers) stored_identifiers.append(mi) - from_input_coeffs_by_row.append([(idx, 1)]) + from_input_coeffs_by_row.append([(idx, sym.sympify(1))]) from_output_coeffs_by_row.append([]) continue diff = [mi[d] - max_mi[d] for d in range(self.dim)] # eg: u_xx + u_yy + u_zz is represented as # [((2, 0, 0), 1), ((0, 2, 0), 1), ((0, 0, 2), 1)] - assignment = [] + assignment: list[tuple[int, sym.Expr]] = [] for other_mi, coeff in mi_to_coeff.items(): j = coeff_ident_enumerate_dict[add_mi(other_mi, diff)] if i == j: # Skip the u_zz part here. continue + # PDE might not have max_mi_coeff = -1, divide by -max_mi_coeff # to get a relation of the form, u_zz = - u_xx - u_yy for Laplace 3D. assignment.append((j, coeff*max_mi_mult)) + from_input_coeffs_by_row.append([]) from_output_coeffs_by_row.append(assignment) plog.done() - logger.debug("number of Taylor coefficients was reduced from {orig} to {red}" - .format(orig=len(self.get_full_coefficient_identifiers()), - red=len(stored_identifiers))) + logger.debug("number of Taylor coefficients was reduced from %d to %d", + len(self.get_full_coefficient_identifiers()), + len(stored_identifiers)) shape = (len(mis), len(stored_identifiers)) op = CSEMatVecOperator(from_input_coeffs_by_row, @@ -581,7 +835,7 @@ def is_stored(mi): return stored_identifiers, op @memoize_method - def get_projection_matrix(self, rscale): + def get_projection_matrix(self, rscale: sym.Expr) -> CSEMatVecOperator: r""" Return a :class:`CSEMatVecOperator` object which exposes a matrix vector multiplication operator for the projection matrix that expresses @@ -608,24 +862,23 @@ def get_projection_matrix(self, rscale): c^{\text{local}}_{\text{full}} = M^T c^{\text{local}}_{\text{stored}}.\\ c^{\text{mpole}}_{\text{stored}} = M c^{\text{mpole}}_{\text{full}}. """ - _, projection_matrix = \ - self.get_stored_ids_and_unscaled_projection_matrix() + _, projection_matrix = self.get_stored_ids_and_unscaled_projection_matrix() full_coeffs = self.get_full_coefficient_identifiers() - projection_with_rscale = [] - for row, assignment in \ - enumerate(projection_matrix.from_output_coeffs_by_row): + projection_with_rscale: LinearCombinationCoefficients = [] + for row, assignment in enumerate(projection_matrix.from_output_coeffs_by_row): # For eg: (u_xxx / rscale**3) = (u_yy / rscale**2) * coeff1 + # (u_xx / rscale**2) * coeff2 # is converted to u_xxx = u_yy * (rscale * coeff1) + # u_xx * (rscale * coeff2) row_rscale = sum(full_coeffs[row]) - from_output_coeffs_with_rscale = [] + from_output_coeffs_with_rscale: Sequence[tuple[int, sym.Expr]] = [] for k, coeff in assignment: diff = row_rscale - sum(full_coeffs[k]) mult = rscale**diff from_output_coeffs_with_rscale.append((k, coeff * mult)) + projection_with_rscale.append(from_output_coeffs_with_rscale) shape = projection_matrix.shape @@ -636,12 +889,20 @@ def get_projection_matrix(self, rscale): # }}} -# {{{ volume taylor +# {{{ volume taylor expansion -class VolumeTaylorExpansionBase: +class VolumeTaylorExpansionMixin(ExpansionBase, ABC): + expansion_terms_wrangler_class: ClassVar[type[ExpansionTermsWrangler]] + expansion_terms_wrangler_cache: ClassVar[dict[Hashable, Any]] = {} + + @property + @abstractmethod + def expansion_terms_wrangler_key(self) -> tuple[Hashable, ...]: + ... @classmethod - def get_or_make_expansion_terms_wrangler(cls, *key): + def get_or_make_expansion_terms_wrangler( + cls, *key: Hashable) -> ExpansionTermsWrangler: """ This stores the expansion terms wrangler at the class attribute level because recreating the expansion terms wrangler implicitly empties its @@ -656,101 +917,104 @@ def get_or_make_expansion_terms_wrangler(cls, *key): return wrangler @property - def expansion_terms_wrangler(self): + def expansion_terms_wrangler(self) -> ExpansionTermsWrangler: return self.get_or_make_expansion_terms_wrangler( *self.expansion_terms_wrangler_key) - def get_coefficient_identifiers(self): + @override + def get_coefficient_identifiers(self) -> Sequence[MultiIndex]: """ Returns the identifiers of the coefficients that actually get stored. """ return self.expansion_terms_wrangler.get_coefficient_identifiers() - def get_full_coefficient_identifiers(self): + def get_full_coefficient_identifiers(self) -> Sequence[MultiIndex]: return self.expansion_terms_wrangler.get_full_coefficient_identifiers() @property @memoize_method - def _storage_loc_dict(self): - return {i: idx for idx, i in - enumerate(self.get_coefficient_identifiers())} - - def get_storage_index(self, i): - return self._storage_loc_dict[i] - - -class VolumeTaylorExpansion(VolumeTaylorExpansionBase): - - expansion_terms_wrangler_class = FullExpansionTermsWrangler - expansion_terms_wrangler_cache = {} + def _storage_loc_dict(self) -> dict[MultiIndex, int]: + return {i: idx for idx, i in enumerate(self.get_coefficient_identifiers())} - # not user-facing, be strict about having to pass use_rscale - def __init__(self, kernel, order, use_rscale): - self.expansion_terms_wrangler_key = (order, kernel.dim) + @override + def get_storage_index(self, mi: MultiIndex) -> int: + return self._storage_loc_dict[mi] -class LinearPDEConformingVolumeTaylorExpansion(VolumeTaylorExpansionBase): +class VolumeTaylorExpansion(VolumeTaylorExpansionMixin, ABC): + expansion_terms_wrangler_class: ClassVar[type[ExpansionTermsWrangler]] \ + = FullExpansionTermsWrangler - expansion_terms_wrangler_class = LinearPDEBasedExpansionTermsWrangler - expansion_terms_wrangler_cache = {} - - # not user-facing, be strict about having to pass use_rscale - def __init__(self, kernel, order, use_rscale): - self.expansion_terms_wrangler_key = (order, kernel.dim, kernel) + @property + @override + def expansion_terms_wrangler_key(self) -> tuple[Hashable, ...]: + return (self.order, self.kernel.dim, None) -class LaplaceConformingVolumeTaylorExpansion( - LinearPDEConformingVolumeTaylorExpansion): +class LinearPDEConformingVolumeTaylorExpansion(VolumeTaylorExpansionMixin, ABC): + expansion_terms_wrangler_class: ClassVar[type[ExpansionTermsWrangler]] = \ + LinearPDEBasedExpansionTermsWrangler - def __init__(self, *args, **kwargs): - from warnings import warn - warn("LaplaceConformingVolumeTaylorExpansion is deprecated. " - "Use LinearPDEConformingVolumeTaylorExpansion instead.", - DeprecationWarning, stacklevel=2) - super().__init__(*args, **kwargs) + @property + @override + def expansion_terms_wrangler_key(self) -> tuple[Hashable, ...]: + return (self.order, self.kernel.dim, None, self.kernel) +# }}} -class HelmholtzConformingVolumeTaylorExpansion( - LinearPDEConformingVolumeTaylorExpansion): - def __init__(self, *args, **kwargs): - from warnings import warn - warn("HelmholtzConformingVolumeTaylorExpansion is deprecated. " - "Use LinearPDEConformingVolumeTaylorExpansion instead.", - DeprecationWarning, stacklevel=2) - super().__init__(*args, **kwargs) +# {{{ expansion factory +class MultipoleExpansionFactory(Protocol): + """ + A protocol, matches :class:`~sumpy.expansion.multipole.MultipoleExpansionBase` + constructors. -class BiharmonicConformingVolumeTaylorExpansion( - LinearPDEConformingVolumeTaylorExpansion): + .. automethod:: __call__ + """ + def __call__(self, + kernel: ScalarKernel, + order: int, + *, use_rscale: bool = True + ) -> MultipoleExpansionBase: + ... - def __init__(self, *args, **kwargs): - from warnings import warn - warn("BiharmonicConformingVolumeTaylorExpansion is deprecated. " - "Use LinearPDEConformingVolumeTaylorExpansion instead.", - DeprecationWarning, stacklevel=2) - super().__init__(*args, **kwargs) -# }}} +class LocalExpansionFactory(Protocol): + """ + A protocol, matches :class:`~sumpy.expansion.local.LocalExpansionBase` constructors. + .. automethod:: __call__ + """ + def __call__(self, + kernel: ScalarKernel, + order: int, + *, use_rscale: bool = True + ) -> LocalExpansionBase: + ... -# {{{ expansion factory -class ExpansionFactoryBase: - """An interface +class ExpansionFactoryBase(ABC): + """ .. automethod:: get_local_expansion_class .. automethod:: get_multipole_expansion_class """ - def get_local_expansion_class(self, base_kernel): - """Returns a subclass of :class:`ExpansionBase` suitable for *base_kernel*. + @abstractmethod + def get_local_expansion_class(self, + base_kernel: ScalarKernel, / + ) -> LocalExpansionFactory: + """ + :returns: a subclass of :class:`ExpansionBase` suitable for *base_kernel*. """ - raise NotImplementedError() - def get_multipole_expansion_class(self, base_kernel): - """Returns a subclass of :class:`ExpansionBase` suitable for *base_kernel*. + @abstractmethod + def get_multipole_expansion_class(self, + base_kernel: ScalarKernel, / + ) -> MultipoleExpansionFactory: + """ + :returns: a subclass of :class:`ExpansionBase` suitable for *base_kernel*. """ - raise NotImplementedError() class VolumeTaylorExpansionFactory(ExpansionFactoryBase): @@ -758,14 +1022,22 @@ class VolumeTaylorExpansionFactory(ExpansionFactoryBase): expansions for each kernel. """ - def get_local_expansion_class(self, base_kernel): - """Returns a subclass of :class:`ExpansionBase` suitable for *base_kernel*. + @override + def get_local_expansion_class( + self, base_kernel: ScalarKernel, / + ) -> type[LocalExpansionBase]: + """ + :returns: a subclass of :class:`ExpansionBase` suitable for *base_kernel*. """ from sumpy.expansion.local import VolumeTaylorLocalExpansion return VolumeTaylorLocalExpansion - def get_multipole_expansion_class(self, base_kernel): - """Returns a subclass of :class:`ExpansionBase` suitable for *base_kernel*. + @override + def get_multipole_expansion_class( + self, base_kernel: ScalarKernel, / + ) -> type[MultipoleExpansionBase]: + """ + :returns: a subclass of :class:`ExpansionBase` suitable for *base_kernel*. """ from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansion return VolumeTaylorMultipoleExpansion @@ -776,43 +1048,34 @@ class DefaultExpansionFactory(ExpansionFactoryBase): expansion for each kernel. """ - def get_local_expansion_class(self, base_kernel): - """Returns a subclass of :class:`ExpansionBase` suitable for *base_kernel*. + @override + def get_local_expansion_class(self, + base_kernel: ScalarKernel, /, + ) -> LocalExpansionFactory: """ - from sumpy.kernel import (HelmholtzKernel, YukawaKernel) - - from sumpy.expansion.local import (H2DLocalExpansion, Y2DLocalExpansion, - LinearPDEConformingVolumeTaylorLocalExpansion, - VolumeTaylorLocalExpansion) - - if (isinstance(base_kernel.get_base_kernel(), HelmholtzKernel) - and base_kernel.dim == 2): - return H2DLocalExpansion - elif (isinstance(base_kernel.get_base_kernel(), YukawaKernel) - and base_kernel.dim == 2): - return Y2DLocalExpansion + :returns: a subclass of :class:`ExpansionBase` suitable for *base_kernel*. + """ + from sumpy.expansion.local import ( + LinearPDEConformingVolumeTaylorLocalExpansion, + VolumeTaylorLocalExpansion, + ) try: base_kernel.get_base_kernel().get_pde_as_diff_op() return LinearPDEConformingVolumeTaylorLocalExpansion except NotImplementedError: return VolumeTaylorLocalExpansion - def get_multipole_expansion_class(self, base_kernel): - """Returns a subclass of :class:`ExpansionBase` suitable for *base_kernel*. + @override + def get_multipole_expansion_class(self, + base_kernel: ScalarKernel, /, + ) -> MultipoleExpansionFactory: """ - from sumpy.kernel import (HelmholtzKernel, YukawaKernel) - - from sumpy.expansion.multipole import (H2DMultipoleExpansion, - Y2DMultipoleExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion, - VolumeTaylorMultipoleExpansion) - - if (isinstance(base_kernel.get_base_kernel(), HelmholtzKernel) - and base_kernel.dim == 2): - return H2DMultipoleExpansion - elif (isinstance(base_kernel.get_base_kernel(), YukawaKernel) - and base_kernel.dim == 2): - return Y2DMultipoleExpansion + :returns: a subclass of :class:`ExpansionBase` suitable for *base_kernel*. + """ + from sumpy.expansion.multipole import ( + LinearPDEConformingVolumeTaylorMultipoleExpansion, + VolumeTaylorMultipoleExpansion, + ) try: base_kernel.get_base_kernel().get_pde_as_diff_op() return LinearPDEConformingVolumeTaylorMultipoleExpansion @@ -822,4 +1085,19 @@ def get_multipole_expansion_class(self, base_kernel): # }}} +__all__ = [ + "CSEMatVecOperator", + "DefaultExpansionFactory", + "ExpansionBase", + "ExpansionFactoryBase", + "ExpansionTermsWrangler", + "FullExpansionTermsWrangler", + "LinearPDEBasedExpansionTermsWrangler", + "LinearPDEConformingVolumeTaylorExpansion", + "VolumeTaylorExpansion", + "VolumeTaylorExpansionFactory", + "VolumeTaylorExpansionMixin", +] + + # vim: fdm=marker diff --git a/sumpy/expansion/diff_op.py b/sumpy/expansion/diff_op.py index 8cb277a11..050a7a601 100644 --- a/sumpy/expansion/diff_op.py +++ b/sumpy/expansion/diff_op.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2019 Isuru Fernando" __license__ = """ @@ -20,18 +23,39 @@ THE SOFTWARE. """ -from collections import namedtuple -from pyrsistent import pmap -from pytools import memoize -from sumpy.tools import add_mi +import logging +from dataclasses import dataclass +from functools import cached_property from itertools import accumulate +from typing import TYPE_CHECKING, TypeAlias + +import numpy as np +from constantdict import constantdict +from typing_extensions import override + +from pytools import memoize + import sumpy.symbolic as sym -import logging -from typing import List + + +if TYPE_CHECKING: + from collections.abc import Mapping, Sequence + + from sympy.polys.agca.modules import FreeModuleElement, SubModulePolyRing + + from pymbolic.typing import Number + logger = logging.getLogger(__name__) __doc__ = """ +.. autodata:: MultiIndex + :no-index: + +.. class:: MultiIndex + + See above. + Differential operator interface ------------------------------- @@ -39,137 +63,235 @@ .. autoclass:: DerivativeIdentifier .. autofunction:: make_identity_diff_op .. autofunction:: as_scalar_pde +.. autofunction:: to_fourier_matrix """ -DerivativeIdentifier = namedtuple("DerivativeIdentifier", ["mi", "vec_idx"]) + +MultiIndex: TypeAlias = tuple[int, ...] + + +@dataclass(frozen=True) +class DerivativeIdentifier: + """ + .. autoattribute:: mi + .. autoattribute: vec_idx + """ + + mi: MultiIndex + """ + Multi-index of the derivative being taken, a tuple with a number of entries + corresponding to the dimension. + """ + + vec_idx: int + """ + In a PDE system of :math:`n` variables, an integer between :math:`0` and :math:`n-1` + indicating which variable is being differentiated. + """ +@dataclass(frozen=True, eq=True) class LinearPDESystemOperator: r""" Represents a constant-coefficient linear differential operator of a - vector-valued function with `dim` spatial variables. It is represented by a - tuple of immutable dictionaries. The dictionary maps a - :class:`DerivativeIdentifier` to the coefficient. This object is immutable. - Optionally supports a time variable as the last variable in the multi-index - of the :class:`DerivativeIdentifier`. + vector-valued function with :attr:`spatial_dim` spatial variables and + additional temporal variables. + + The operator is given by a tuple of immutable dictionaries. The dictionary + maps a :class:`DerivativeIdentifier` to a (time- and space-independent) + coefficient. In the :class:`DerivativeIdentifier`, each multi-index has + :attr:`spatial_dim` indices for the spatial variables and the remaining + ones represent temporal variables. + + The class also supports basic arithmetic, i.e. multiplication and addition + with other operators and constants. + + .. autoattribute:: eqs + .. autoattribute:: spatial_dim + .. autoproperty:: total_dims + + .. autoproperty:: order + .. autoproperty:: nvariables + .. autoproperty:: is_time_dependent + + .. automethod:: to_sym """ - def __init__(self, dim, *eqs): - """ - :arg dim: Number of spatial dimensions of the LinearPDESystemOperator - :arg eqs: A list of dictionaries mapping a :class:`DerivativeIdentifier` - to a coefficient. - """ - self.dim = dim - self.eqs = tuple(eqs) - def __eq__(self, other): - return self.dim == other.dim and self.eqs == other.eqs + spatial_dim: int + """The number of spatial dimensions of the PDE (use :attr:`total_dims` + to include time). + """ + eqs: tuple[Mapping[DerivativeIdentifier, sym.Expr], ...] + """A tuple of all the equations in the system.""" - def __hash__(self): - return hash((self.dim, self.eqs)) + if __debug__: - @property - def order(self): - deg = 0 - for eq in self.eqs: - deg = max(deg, max(sum(ident.mi) for ident in eq.keys())) - return deg + def __post_init__(self) -> None: + # NOTE: this will raise a TypeError if it's not hashable + _ = hash(self) + + # {{{ arithmetic - def __mul__(self, param): - eqs = [] + def __mul__(self, other: Number | sym.Expr) -> LinearPDESystemOperator: + import numbers + if not isinstance(other, (numbers.Number, np.number, sym.Expr)): + return NotImplemented + + eqs: list[Mapping[DerivativeIdentifier, sym.Expr]] = [] for eq in self.eqs: - deriv_ident_to_coeff = {} + deriv_ident_to_coeff: Mapping[DerivativeIdentifier, sym.Expr] = {} for k, v in eq.items(): - deriv_ident_to_coeff[k] = v * param - eqs.append(pmap(deriv_ident_to_coeff)) - return LinearPDESystemOperator(self.dim, *eqs) + deriv_ident_to_coeff[k] = v * other + + eqs.append(constantdict(deriv_ident_to_coeff)) + + return LinearPDESystemOperator(self.spatial_dim, tuple(eqs)) - __rmul__ = __mul__ + def __rmul__(self, param: Number | sym.Expr) -> LinearPDESystemOperator: + return self.__mul__(param) - def __add__(self, other_diff_op): - assert self.dim == other_diff_op.dim - assert len(self.eqs) == len(other_diff_op.eqs) - eqs = [] - for eq, other_eq in zip(self.eqs, other_diff_op.eqs): + def __add__(self, other: LinearPDESystemOperator) -> LinearPDESystemOperator: + if not isinstance(other, LinearPDESystemOperator): + return NotImplemented + + assert self.spatial_dim == other.spatial_dim + assert len(self.eqs) == len(other.eqs) + + eqs: list[Mapping[DerivativeIdentifier, sym.Expr]] = [] + for eq, other_eq in zip(self.eqs, other.eqs, strict=True): res = dict(eq) for k, v in other_eq.items(): if k in res: res[k] += v else: res[k] = v - eqs.append(pmap(res)) - return LinearPDESystemOperator(self.dim, *eqs) + eqs.append(constantdict(res)) + + return LinearPDESystemOperator(self.spatial_dim, tuple(eqs)) - __radd__ = __add__ + def __radd__(self, other: LinearPDESystemOperator) -> LinearPDESystemOperator: + return self.__add__(other) - def __sub__(self, other_diff_op): - return self + (-1)*other_diff_op + def __sub__(self, other: LinearPDESystemOperator) -> LinearPDESystemOperator: + return self + (-1)*other - def __repr__(self): - return f"LinearPDESystemOperator({self.dim}, {repr(self.eqs)})" + def __neg__(self) -> LinearPDESystemOperator: + return (-1) * self - def __getitem__(self, idx): + # }}} + + @override + def __repr__(self) -> str: + return f"LinearPDESystemOperator({self.spatial_dim}, {self.eqs!r})" + + def __getitem__(self, idx: int | slice) -> LinearPDESystemOperator: item = self.eqs.__getitem__(idx) - if not isinstance(item, tuple): - item = (item,) - return LinearPDESystemOperator(self.dim, *item) + eqs = item if isinstance(item, tuple) else (item,) + return LinearPDESystemOperator(self.spatial_dim, eqs) @property - def total_dims(self): - """ - Returns the total number of dimensions including time - """ - return len(self.eqs[0].keys()[0].mi) + def is_time_dependent(self) -> bool: + """Is *True* if the PDE operator has a time component.""" + return self.spatial_dim != self.total_dims + + @cached_property + def order(self) -> int: + """The order of the PDE operator (maximum order of all derivatives).""" + deg = 0 + for eq in self.eqs: + deg = max(deg, max(sum(ident.mi) for ident in eq)) - def to_sym(self, fnames=None): - from sumpy.symbolic import make_sym_vector, Function - x = list(make_sym_vector("x", self.dim)) - x += list(make_sym_vector("t", self.total_dims - self.dim)) + return deg + + @cached_property + def total_dims(self) -> int: + """The total number of dimensions (including time).""" + did = next(iter(self.eqs[0])) + return len(did.mi) + + @cached_property + def nvariables(self) -> int: + """Number of variables in the system.""" + max_vec_idx = max((did.vec_idx for eq in self.eqs for did in eq), default=-1) + return max_vec_idx + 1 + + def to_sym( + self, + fnames: Sequence[str] | None = None, + *, + x_var_name: str = "x", + t_var_name: str = "t", + ) -> list[sym.Expr]: + """Transform the system to a list of :mod:`sympy` expressions. + + :arg fnames: the names of the variables in the system. + (defaults to `["f0", "f1", ....]`) + :arg x_var_name: the name of the spatial variables. + (defaults to `["x0", "x1", ....]`) + :arg t_var_name: the name of the temporal variables. + """ + x: list[sym.Expr] = list(sym.make_sym_vector(x_var_name, self.spatial_dim)) + x.extend(sym.make_sym_vector(t_var_name, self.total_dims - self.spatial_dim)) if fnames is None: noutputs = 0 for eq in self.eqs: - for deriv_ident in eq.keys(): + for deriv_ident in eq: noutputs = max(noutputs, deriv_ident.vec_idx) + fnames = [f"f{i}" for i in range(noutputs+1)] - funcs = [Function(fname)(*x) for fname in fnames] + funcs = [sym.Function(fname)(*x) for fname in fnames] + if len(funcs) < self.nvariables: + raise ValueError( + f"'fnames' does not match system: {len(fnames)} names " + f"(for a system of {self.nvariables} variables)" + ) - res = [] + res: list[sym.Expr] = [] for eq in self.eqs: - sym_eq = 0 + sym_eq: sym.Expr = sym.Integer(0) for deriv_ident, coeff in eq.items(): expr = funcs[deriv_ident.vec_idx] for i, val in enumerate(deriv_ident.mi): for _ in range(val): expr = expr.diff(x[i]) + sym_eq += expr * coeff + res.append(sym_eq) + return res -def convert_module_to_matrix(module, generators): - import sympy +def convert_module_to_matrix( + module: Sequence[FreeModuleElement], + generators: Sequence[sym.Expr] + ) -> sym.Matrix: + import sympy as sp + # poly is a sympy DMP (dense multi-variate polynomial) # type and we convert it to a sympy expression because # sympy matrices with polynomial entries are not supported. # see https://github.com/sympy/sympy/issues/21497 - return sympy.Matrix([[sympy.Poly(poly.to_dict(), *generators, - domain=sympy.EX).as_expr() for poly in ideal.data] for ideal in module]) + return sp.Matrix([ + [sp.Poly(poly.to_dict(), *generators, domain=sp.EX).as_expr() + for poly in ideal.data] + for ideal in module]) @memoize -def _get_all_scalar_pdes(pde: LinearPDESystemOperator) -> \ - List[LinearPDESystemOperator]: - import sympy +def _get_all_scalar_pdes(pde: LinearPDESystemOperator) -> list[LinearPDESystemOperator]: + import sympy as sp from sympy.polys.orderings import grevlex - gens = [sympy.symbols(f"_x{i}") for i in range(pde.dim)] - gens += [sympy.symbols(f"_t{i}") for i in range(pde.total_dims - pde.dim)] + + gens = [sp.Symbol(f"_x{i}") for i in range(pde.spatial_dim)] + gens += [sp.Symbol(f"_t{i}") for i in range(pde.total_dims - pde.spatial_dim)] max_vec_idx = max(deriv_ident.vec_idx for eq in pde.eqs - for deriv_ident in eq.keys()) + for deriv_ident in eq) - pde_system_mat = sympy.zeros(len(pde.eqs), max_vec_idx + 1) + pde_system_mat = sp.zeros(len(pde.eqs), max_vec_idx + 1) for row, eq in enumerate(pde.eqs): for deriv_ident, coeff in eq.items(): deriv_as_poly = 1 @@ -177,12 +299,12 @@ def _get_all_scalar_pdes(pde: LinearPDESystemOperator) -> \ deriv_as_poly *= gens[i]**val pde_system_mat[row, deriv_ident.vec_idx] += coeff * deriv_as_poly - ring = sympy.EX.old_poly_ring(*gens, order=grevlex) - column_ideals = [ring.free_module(1).submodule(*pde_system_mat[:, i].tolist(), - order=grevlex) - for i in range(pde_system_mat.shape[1])] + ring = sp.EX.old_poly_ring(*gens, order=grevlex) + column_ideals = [ + ring.free_module(1).submodule(*pde_system_mat[:, i].tolist(), order=grevlex) + for i in range(pde_system_mat.shape[1]) + ] column_syzygy_modules = [ideal.syzygy_module() for ideal in column_ideals] - ncols = len(column_syzygy_modules) # For each column i, we need to get the intersection of all the syzygy modules @@ -193,12 +315,13 @@ def _get_all_scalar_pdes(pde: LinearPDESystemOperator) -> \ # for each column we calculate the intersection of the left modules and the # right modules. This requires only $3*(n-2)$ work. - def intersect(a, b): + def intersect(a: SubModulePolyRing, b: SubModulePolyRing) -> SubModulePolyRing: return a.intersect(b) - left_intersections = list(accumulate(column_syzygy_modules, func=intersect)) - right_intersections = list(reversed(list(accumulate(reversed( - column_syzygy_modules), func=intersect)))) + left_intersections = list( + accumulate(column_syzygy_modules, func=intersect)) + right_intersections = list(reversed(list( + accumulate(reversed(column_syzygy_modules), func=intersect)))) # At the end, calculate the intersection of the left modules and right modules # and calculate a groebner basis for it. @@ -212,27 +335,31 @@ def intersect(a, b): # column. scalar_pdes_vec = [ (convert_module_to_matrix(module_intersections[i]._groebner_vec(), - gens) * pde_system_mat)[:, i] + gens) * pde_system_mat)[:, i] for i in range(ncols) ] - results = [] + results: list[LinearPDESystemOperator] = [] for col in range(ncols): - scalar_pde_polys = [sympy.Poly(pde, *gens, domain=sympy.EX) for - pde in scalar_pdes_vec[col]] + scalar_pde_polys = [sp.Poly(pde, *gens, domain=sp.EX) + for pde in scalar_pdes_vec[col]] scalar_pdes = [pde for pde in scalar_pde_polys if pde.degree() > 0] scalar_pde = min(scalar_pdes, key=lambda x: x.degree()).monic() + pde_dict = { - DerivativeIdentifier(mi, 0): sym.sympify(coeff.as_expr().simplify()) for - (mi, coeff) in zip(scalar_pde.monoms(), scalar_pde.coeffs()) + DerivativeIdentifier(mi, 0): sym.sympify(coeff.as_expr().simplify()) + for (mi, coeff) in zip(scalar_pde.monoms(), + scalar_pde.coeffs(), strict=True) } - results.append(LinearPDESystemOperator(pde.dim, pmap(pde_dict))) + results.append(LinearPDESystemOperator(pde.spatial_dim, + (constantdict(pde_dict),))) return results -def as_scalar_pde(pde: LinearPDESystemOperator, comp_idx: int) \ - -> LinearPDESystemOperator: +def as_scalar_pde( + pde: LinearPDESystemOperator, comp_idx: int, + ) -> LinearPDESystemOperator: r""" Returns a scalar PDE that is satisfied by the *comp_idx* component of *pde*. @@ -245,14 +372,16 @@ def as_scalar_pde(pde: LinearPDESystemOperator, comp_idx: int) \ eg: .. math:: - \frac{\partial^2 u}{\partial x^2} + \ + + \frac{\partial^2 u}{\partial x^2} + 2 \frac{\partial^2 v}{\partial x y} = 0 \\ - 3 \frac{\partial^2 u}{\partial y^2} + \ + 3 \frac{\partial^2 u}{\partial y^2} + \frac{\partial^2 v}{\partial x^2} = 0 - is converted into, + is converted into .. math:: + \begin{bmatrix} x^2 & 2xy \\ 2y^2 & x^2 @@ -290,22 +419,60 @@ def as_scalar_pde(pde: LinearPDESystemOperator, comp_idx: int) \ :arg comp_idx: the index of the component of the PDE solution vector for which a scalar PDE is requested. """ - indices = set() + indices: set[int] = set() for eq in pde.eqs: - for deriv_ident in eq.keys(): + for deriv_ident in eq: indices.add(deriv_ident.vec_idx) # this is already a scalar pde - if len(indices) == 1 and list(indices)[0] == comp_idx: + if len(indices) == 1 and next(iter(indices)) == comp_idx: return pde return _get_all_scalar_pdes(pde)[comp_idx] -def laplacian(diff_op): - dim = diff_op.dim - empty = [pmap()] * len(diff_op.eqs) - res = LinearPDESystemOperator(dim, *empty) +def to_fourier_matrix( + pde: LinearPDESystemOperator, + ks: sym.Matrix, + ) -> sym.Matrix: + r"""Return the Fourier (symbol) matrix of a constant-coefficient PDE system. + + Each spatial derivative :math:`\partial / \partial x_j` is replaced by + multiplication by :math:`i\,k_j`. The result is a + :obj:`sympy.matrices.dense.Matrix` whose ``(row, col)`` entry is the + polynomial in the frequency variables contributed by equation *row* acting + on component *col*. + + :returns: a matrix of size ``(len(pde.eqs), nvariables)``. + """ + if pde.is_time_dependent: + raise ValueError("cannot compute Fourier symbol for time-dependent PDEs") + + ncols = pde.nvariables + + mat = [] + for eq in pde.eqs: + row = [sym.Integer(0)] * ncols + + for deriv_ident, coeff in eq.items(): + factor: sym.Expr = sym.Integer(1) + for j, power in enumerate(deriv_ident.mi): + factor *= (sym.I * ks[j]) ** power + + row[deriv_ident.vec_idx] += coeff * factor + + assert len(row) == ncols + mat.append(row) + + return sym.Matrix(mat) + + +def laplacian(diff_op: LinearPDESystemOperator) -> LinearPDESystemOperator: + dim = diff_op.spatial_dim + empty: tuple[Mapping[DerivativeIdentifier, sym.Expr], ...] = ( + (constantdict(),) * len(diff_op.eqs)) + + res = LinearPDESystemOperator(dim, empty) for j in range(dim): mi = [0]*diff_op.total_dims mi[j] = 2 @@ -313,81 +480,113 @@ def laplacian(diff_op): return res -def diff(diff_op, mi): - eqs = [] +def diff( + diff_op: LinearPDESystemOperator, mi: tuple[int, ...] + ) -> LinearPDESystemOperator: + from sumpy.tools import add_mi + + eqs: list[Mapping[DerivativeIdentifier, sym.Expr]] = [] for eq in diff_op.eqs: - res = {} + res: Mapping[DerivativeIdentifier, sym.Expr] = {} for deriv_ident, v in eq.items(): new_mi = add_mi(deriv_ident.mi, mi) res[DerivativeIdentifier(new_mi, deriv_ident.vec_idx)] = v - eqs.append(pmap(res)) - return LinearPDESystemOperator(diff_op.dim, *eqs) + eqs.append(constantdict(res)) + + return LinearPDESystemOperator(diff_op.spatial_dim, tuple(eqs)) -def divergence(diff_op): - assert len(diff_op.eqs) == diff_op.dim - res = LinearPDESystemOperator(diff_op.dim, pmap()) - for i in range(diff_op.dim): + +def divergence(diff_op: LinearPDESystemOperator) -> LinearPDESystemOperator: + if len(diff_op.eqs) != diff_op.spatial_dim: + raise ValueError( + "number of equations does not match system dimension: " + f"got {len(diff_op.eqs)} equations for {diff_op.spatial_dim}d system") + + res = LinearPDESystemOperator(diff_op.spatial_dim, (constantdict(),)) + for i in range(diff_op.spatial_dim): mi = [0]*diff_op.total_dims mi[i] = 1 res += diff(diff_op[i], tuple(mi)) + return res -def gradient(diff_op): - assert len(diff_op.eqs) == 1 - eqs = [] - dim = diff_op.dim +def gradient(diff_op: LinearPDESystemOperator) -> LinearPDESystemOperator: + if len(diff_op.eqs) != 1: + raise ValueError( + f"can only take gradient of scalar system: got {len(diff_op.eqs)}d") + + eqs: list[Mapping[DerivativeIdentifier, sym.Expr]] = [] + dim = diff_op.spatial_dim for i in range(dim): mi = [0]*diff_op.total_dims mi[i] = 1 eqs.append(diff(diff_op, tuple(mi)).eqs[0]) - return LinearPDESystemOperator(dim, *eqs) + return LinearPDESystemOperator(dim, tuple(eqs)) + + +def curl(diff_op: LinearPDESystemOperator) -> LinearPDESystemOperator: + if len(diff_op.eqs) != diff_op.spatial_dim: + raise ValueError( + "number of equations does not match system dimension: " + f"got {len(diff_op.eqs)} equations for {diff_op.spatial_dim}d system") -def curl(pde): - assert len(pde.eqs) == 3 - assert pde.dim == 3 - eqs = [] - mis = [] + if diff_op.spatial_dim != 3: + raise ValueError(f"can only take curl of 3d system: got {diff_op.spatial_dim}d") + + eqs: list[Mapping[DerivativeIdentifier, sym.Expr]] = [] + mis: list[MultiIndex] = [] for i in range(3): - mi = [0]*pde.total_dims + mi = [0]*diff_op.total_dims mi[i] = 1 mis.append(tuple(mi)) for i in range(3): - new_pde = diff(pde[(i+2) % 3], mis[(i+1) % 3]) - \ - diff(pde[(i+1) % 3], mis[(i+2) % 3]) + new_pde = ( + diff(diff_op[(i+2) % 3], mis[(i+1) % 3]) + - diff(diff_op[(i+1) % 3], mis[(i+2) % 3])) eqs.append(new_pde.eqs[0]) - return LinearPDESystemOperator(pde.dim, *eqs) + return LinearPDESystemOperator(diff_op.spatial_dim, tuple(eqs)) + +def concat(*ops: LinearPDESystemOperator) -> LinearPDESystemOperator: + if not ops: + raise TypeError("concat() takes at least 1 positional argument (0 given)") + + if len(ops) == 1: + return ops[0] + + dim = ops[0].spatial_dim + if not all(op.spatial_dim == dim for op in ops): + raise ValueError(f"operators must have the same dimension (expected {dim}d)") -def concat(*ops): - ops = list(ops) - assert len(ops) >= 1 - dim = ops[0].dim - for op in ops: - assert op.dim == dim eqs = list(ops[0].eqs) for op in ops[1:]: eqs.extend(list(op.eqs)) - return LinearPDESystemOperator(dim, *eqs) + return LinearPDESystemOperator(dim, tuple(eqs)) -def make_identity_diff_op(ninput, noutput=1, time_dependent=False): - """ - Returns the identity as a linear PDE system operator. - if *include_time* is true, then the last dimension of the - multi-index is time. - :arg ninput: number of spatial variables of the function - :arg noutput: number of output values of function - :arg time_dependent: include time as a dimension +def make_identity_diff_op( + ninput: int, noutput: int = 1, *, + time_dependent: bool = False + ) -> LinearPDESystemOperator: """ - if time_dependent: + :arg ninput: number of spatial variables of the function. + :arg noutput: number of output values of function. + :arg time_dependent: include time as a dimension. + :returns: the identity as a linear PDE system operator. If *time_dependent* + is *True*, then the last dimension of the multi-index is time. + """ + + if time_dependent: # ruff:ignore[if-else-block-instead-of-if-exp] mi = tuple([0]*(ninput + 1)) else: mi = tuple([0]*ninput) - eqs = [pmap({DerivativeIdentifier(mi, i): 1}) for i in range(noutput)] - return LinearPDESystemOperator(ninput, *eqs) + + return LinearPDESystemOperator(ninput, tuple( + constantdict({DerivativeIdentifier(mi, i): sym.sympify(1)}) + for i in range(noutput))) diff --git a/sumpy/expansion/level_to_order.py b/sumpy/expansion/level_to_order.py index 5d00c5b01..791e2f675 100644 --- a/sumpy/expansion/level_to_order.py +++ b/sumpy/expansion/level_to_order.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2016 Matt Wala" __license__ = """ @@ -21,68 +24,99 @@ """ __doc__ = """ +.. autoclass:: TreeLike + :members: + :undoc-members: + .. autoclass:: FMMLibExpansionOrderFinder .. autoclass:: SimpleExpansionOrderFinder """ import math +from dataclasses import dataclass +from typing import TYPE_CHECKING, Protocol, cast import numpy as np +if TYPE_CHECKING: + from collections.abc import Sequence + + import sumpy.symbolic as sym + from sumpy.kernel import ScalarKernel + + +class TreeLike(Protocol): + dimensions: int + root_extent: float + stick_out_factor: float + + +@dataclass(frozen=True) class FMMLibExpansionOrderFinder: r"""Return expansion orders that meet the tolerance for a given level using routines wrapped from ``pyfmmlib``. - .. automethod:: __init__ + .. autoattribute:: tol + .. autoattribute:: extra_order + .. automethod:: __call__ """ - def __init__(self, tol, extra_order=0): - """ - :arg tol: error tolerance - :arg extra_order: order increase to accommodate, say, the taking of - derivatives of the FMM expansions. - """ - self.tol = tol - self.extra_order = extra_order - - def __call__(self, kernel, kernel_args, tree, level): - import pyfmmlib + tol: float + """Error tolerance.""" + extra_order: int = 0 + """Order increase to accommodate, say, the taking of derivatives of the FMM + expansion. + """ - from sumpy.kernel import LaplaceKernel, HelmholtzKernel + def __call__(self, + kernel: ScalarKernel, + kernel_args: dict[str, sym.Expr] | Sequence[tuple[str, sym.Expr]], + tree: TreeLike, + level: int) -> int: + from pyfmmlib import h2dterms, h3dterms, l2dterms, l3dterms - assert isinstance(kernel, (LaplaceKernel, HelmholtzKernel)) - assert tree.dimensions in (2, 3) + from sumpy.kernel import HelmholtzKernel, LaplaceKernel if isinstance(kernel, LaplaceKernel): if tree.dimensions == 2: - nterms, ier = pyfmmlib.l2dterms(self.tol) + nterms, ier = l2dterms(self.tol) if ier: raise RuntimeError(f"l2dterms returned error code '{ier}'") elif tree.dimensions == 3: - nterms, ier = pyfmmlib.l3dterms(self.tol) + nterms, ier = l3dterms(self.tol) if ier: raise RuntimeError(f"l3dterms returned error code '{ier}'") + else: + raise ValueError(f"unsupported dimension: {tree.dimensions}") + elif isinstance(kernel, HelmholtzKernel): helmholtz_k = dict(kernel_args)[kernel.helmholtz_k_name] - size = tree.root_extent / 2 ** level + size = cast("int", tree.root_extent / 2 ** level) if tree.dimensions == 2: - nterms, ier = pyfmmlib.h2dterms(size, helmholtz_k, self.tol) + nterms, ier = h2dterms(size, helmholtz_k, self.tol) if ier: raise RuntimeError(f"h2dterms returned error code '{ier}'") elif tree.dimensions == 3: - nterms, ier = pyfmmlib.h3dterms(size, helmholtz_k, self.tol) + nterms, ier = h3dterms(size, helmholtz_k, self.tol) if ier: raise RuntimeError(f"h3dterms returned error code '{ier}'") + else: + raise ValueError(f"unsupported dimension: {tree.dimensions}") + + else: + raise TypeError(f"unsupported kernel: '{type(kernel).__name__}'") + return nterms + self.extra_order +@dataclass(frozen=True) class SimpleExpansionOrderFinder: r""" This models the Laplace truncation error as: @@ -95,42 +129,53 @@ class SimpleExpansionOrderFinder: .. math:: - C_{\text{helm}} \frac 1{p!} - \left(C_{\text{helmscale}} \cdot \frac{hk}{2\pi}\right)^{p+1}, + C_{\text{helm}} \frac{1}{p!} + \left(C_{\text{helmscale}} \cdot \frac{hk}{2\pi}\right)^{p+1}, where :math:`d` is the number of dimensions, :math:`p` is the expansion order, :math:`h` is the box size, and :math:`k` is the wave number. - .. automethod:: __init__ + .. autoattribute:: tol + .. autoattribute:: extra_order + .. autoattribute:: err_const_laplace + .. autoattribute:: err_const_helmholtz + .. autoattribute:: scaling_const_helmholtz + .. automethod:: __call__ """ - def __init__(self, tol, err_const_laplace=0.01, err_const_helmholtz=100, - scaling_const_helmholtz=4, - extra_order=1): - """ - :arg extra_order: order increase to accommodate, say, the taking of - derivatives of the FMM expansions. - """ - self.tol = tol - - self.err_const_laplace = err_const_laplace - self.err_const_helmholtz = err_const_helmholtz - self.scaling_const_helmholtz = scaling_const_helmholtz + tol: float + """Error tolerance.""" + + err_const_laplace: float = 0.01 + """Constant :math:`C_{\text{lap}}` used in the Laplace truncation error.""" + err_const_helmholtz: float = 100.0 + """Constant :math:`C_{\text{helm}}` used in the Helmholtz truncation error.""" + scaling_const_helmholtz: float = 4.0 + """Constant :math:`C_{\text{helmscale}}` used in the Helmholtz truncation error. + This can be used to tweak the scaling with respect to the box size or wave + number. + """ - self.extra_order = extra_order + extra_order: int = 1 + """Order increase to accommodate, say, the taking of derivatives of the FMM + expansion. + """ - def __call__(self, kernel, kernel_args, tree, level): - from sumpy.kernel import LaplaceKernel, HelmholtzKernel + def __call__(self, + kernel: ScalarKernel, + kernel_args: dict[str, sym.Expr] | Sequence[tuple[str, sym.Expr]], + tree: TreeLike, + level: int) -> int: + from sumpy.kernel import HelmholtzKernel, LaplaceKernel - assert isinstance(kernel, (LaplaceKernel, HelmholtzKernel)) + assert isinstance(kernel, LaplaceKernel | HelmholtzKernel) - laplace_order = int(np.ceil( - (np.log(self.tol) - np.log(self.err_const_laplace)) - / # noqa: W504 - np.log( - np.sqrt(tree.dimensions)/3 - ) - 1)) + laplace_order = math.ceil( + (math.log(self.tol) - math.log(self.err_const_laplace)) + / + math.log(math.sqrt(tree.dimensions) / 3.0) + - 1) if isinstance(kernel, HelmholtzKernel): helmholtz_k = dict(kernel_args)[kernel.helmholtz_k_name] diff --git a/sumpy/expansion/local.py b/sumpy/expansion/local.py index 72d91c01d..ce59f5521 100644 --- a/sumpy/expansion/local.py +++ b/sumpy/expansion/local.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2012 Andreas Kloeckner" __license__ = """ @@ -20,160 +23,76 @@ THE SOFTWARE. """ +import logging import math -from typing import Tuple, Any +from abc import ABC, abstractmethod +from dataclasses import dataclass, field +from typing import TYPE_CHECKING, cast + +from typing_extensions import override -import pymbolic -import loopy as lp from pytools import single_valued import sumpy.symbolic as sym from sumpy.expansion import ( - ExpansionBase, - VolumeTaylorExpansion, - LinearPDEConformingVolumeTaylorExpansion) -from sumpy.tools import ( - add_to_sac, fft, - mi_increment_axis, matvec_toeplitz_upper_triangular) + ExpansionBase, + LinearPDEConformingVolumeTaylorExpansion, + VolumeTaylorExpansion, + VolumeTaylorExpansionMixin, +) +from sumpy.expansion.multipole import H2DMultipoleExpansion, Y2DMultipoleExpansion +from sumpy.tools import add_to_sac, mi_increment_axis + + +if TYPE_CHECKING: + from collections.abc import Sequence + + import loopy as lp + + from sumpy.assignment_collection import SymbolicAssignmentCollection + from sumpy.expansion.diff_op import MultiIndex + from sumpy.expansion.m2l import M2LTranslationBase, TranslationClassesDepData + from sumpy.expansion.multipole import ( + HankelBased2DMultipoleExpansion, + MultipoleExpansionBase, + ) + from sumpy.kernel import ScalarKernel + -import logging logger = logging.getLogger(__name__) __doc__ = """ - .. autoclass:: LocalExpansionBase .. autoclass:: VolumeTaylorLocalExpansion .. autoclass:: H2DLocalExpansion .. autoclass:: Y2DLocalExpansion .. autoclass:: LineTaylorLocalExpansion - """ -class LocalExpansionBase(ExpansionBase): +@dataclass(frozen=True) +class LocalExpansionBase(ExpansionBase, ABC): """Base class for local expansions. - .. attribute:: kernel - .. attribute:: order - .. attribute:: use_rscale - .. attribute:: use_fft_for_m2l - .. attribute:: use_preprocessing_for_m2l - - .. automethod:: m2l_translation_classes_dependent_data - .. automethod:: m2l_translation_classes_dependent_ndata - .. automethod:: m2l_preprocess_multipole_exprs - .. automethod:: m2l_preprocess_multipole_nexprs - .. automethod:: m2l_postprocess_local_exprs - .. automethod:: m2l_postprocess_local_nexprs .. automethod:: translate_from """ - init_arg_names = ("kernel", "order", "use_rscale", "use_fft_for_m2l", - "use_preprocessing_for_m2l") - - def __init__(self, kernel, order, use_rscale=None, - use_fft_for_m2l=False, use_preprocessing_for_m2l=None): - super().__init__(kernel, order, use_rscale) - self.use_fft_for_m2l = use_fft_for_m2l - if use_preprocessing_for_m2l is None: - self.use_preprocessing_for_m2l = use_fft_for_m2l - else: - self.use_preprocessing_for_m2l = use_preprocessing_for_m2l - - def with_kernel(self, kernel): - return type(self)(kernel, self.order, self.use_rscale, - use_fft_for_m2l=self.use_fft_for_m2l, - use_preprocessing_for_m2l=self.use_preprocessing_for_m2l) - - def update_persistent_hash(self, key_hash, key_builder): - super().update_persistent_hash(key_hash, key_builder) - key_builder.rec(key_hash, self.use_fft_for_m2l) - key_builder.rec(key_hash, self.use_preprocessing_for_m2l) - - def __eq__(self, other): - return ( - type(self) == type(other) - and self.kernel == other.kernel - and self.order == other.order - and self.use_rscale == other.use_rscale - and self.use_fft_for_m2l == other.use_fft_for_m2l - and self.use_preprocessing_for_m2l == other.use_preprocessing_for_m2l - ) - - def m2l_translation_classes_dependent_data(self, src_expansion, src_rscale, - dvec, tgt_rscale, sac) -> Tuple[Any]: - """Return an iterable of expressions that needs to be precomputed - for multipole-to-local translations that depend only on the - distance between the multipole center and the local center which - is given as *dvec*. - - Since there are only a finite number of different values for the - distance between per level, these can be precomputed for the tree. - In :mod:`boxtree`, these distances are referred to as translation - classes. - """ - return tuple() - - def m2l_translation_classes_dependent_ndata(self, src_expansion): - """Return the number of expressions returned by - :func:`~sumpy.expansion.local.LocalExpansionBase.m2l_translation_classes_dependent_data`. - This method exists because calculating the number of expressions using - the above method might be costly and - :func:`~sumpy.expansion.local.LocalExpansionBase.m2l_translation_classes_dependent_data` - cannot be memoized due to it having side effects through the argument - *sac*. - """ - return 0 - - def m2l_preprocess_multipole_exprs(self, src_expansion, src_coeff_exprs, sac, - src_rscale): - """Return the preprocessed multipole expansion for an optimized M2L. - Preprocessing happens once per source box before M2L translation is done. - - When FFT is turned on, the input expressions are transformed into Fourier - space. These expressions are used in a separate :mod:`loopy` kernel - to avoid having to transform for each target and source box pair. - When FFT is turned off, the expressions are equal to the multipole - expansion coefficients with zeros added - to make the M2L computation a circulant matvec. - """ - raise NotImplementedError - - def m2l_preprocess_multipole_nexprs(self, src_expansion): - """Return the number of expressions returned by - :func:`~sumpy.expansion.local.LocalExpansionBase.m2l_preprocess_multipole_exprs`. - This method exists because calculating the number of expressions using - the above method might be costly and it cannot be memoized due to it having - side effects through the argument *sac*. - """ - # For all use-cases we have right now, this is equal to the number of - # translation classes dependent exprs. Use that as a default. - return self.m2l_translation_classes_dependent_ndata(src_expansion) - - def m2l_postprocess_local_exprs(self, src_expansion, m2l_result, src_rscale, - tgt_rscale, sac): - """Return postprocessed local expansion for an optimized M2L. - Postprocessing happens once per target box just after the M2L translation - is done and before storing the expansion coefficients for the local - expansion. - - When FFT is turned on, the output expressions are transformed from Fourier - space back to the original space. - """ - raise NotImplementedError - - def m2l_postprocess_local_nexprs(self, src_expansion): - """Return the number of expressions given as input to - :func:`~sumpy.expansion.local.LocalExpansionBase.m2l_postprocess_local_exprs`. - This method exists because calculating the number of expressions using - the above method might be costly and it cannot be memoized due to it - having side effects through the argument *sac*. - """ - # For all use-cases we have right now, this is equal to the number of - # translation classes dependent exprs. Use that as a default. - return self.m2l_translation_classes_dependent_ndata(src_expansion) - def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale, sac=None, m2l_translation_classes_dependent_data=None): + @property + @abstractmethod + def m2l_translation(self) -> M2LTranslationBase: + ... + + @abstractmethod + def translate_from(self, + src_expansion: LocalExpansionBase | MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + m2l_translation_classes_dependent_data: ( + TranslationClassesDepData | None) = None + ) -> Sequence[sym.Expr]: """Translate from a multipole or local expansion to a local expansion :arg src_expansion: The source expansion to translate from. @@ -188,22 +107,36 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, to collect common subexpressions or None. :arg m2l_translation_classes_dependent_data: An iterable of symbolic expressions representing the expressions returned by - :func:`~sumpy.expansion.local.LocalExpansionBase.m2l_translation_classes_dependent_data`. + :func:`~sumpy.expansion.m2l.M2LTranslationBase.translation_classes_dependent_data`. """ - raise NotImplementedError # {{{ line taylor -class LineTaylorLocalExpansion(LocalExpansionBase): - - def get_storage_index(self, k): - return k - - def get_coefficient_identifiers(self): - return list(range(self.order+1)) - - def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): +class LineTaylorLocalExpansion(LocalExpansionBase, ABC): + @property + @override + def m2l_translation(self) -> M2LTranslationBase: + # FIXME: Um... + raise NotImplementedError() + + @override + def get_storage_index(self, mi: MultiIndex) -> int: + ind, = mi + return ind + + @override + def get_coefficient_identifiers(self) -> Sequence[MultiIndex]: + return [(i,) for i in range(self.order+1)] + + @override + def coefficients_from_source(self, + kernel: ScalarKernel, + avec: sym.Matrix, + bvec: sym.Matrix | None, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: # no point in heeding rscale here--just ignore it if bvec is None: raise RuntimeError("cannot use line-Taylor expansions in a setting " @@ -211,19 +144,18 @@ def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): "formation") tau = sym.Symbol("tau") - - avec_line = avec + tau*bvec - + avec_line = cast("sym.Matrix", avec + tau*bvec) line_kernel = kernel.get_expression(avec_line) from sumpy.symbolic import USE_SYMENGINE if USE_SYMENGINE: - from sumpy.tools import ExprDerivativeTaker - deriv_taker = ExprDerivativeTaker(line_kernel, (tau,), sac=sac, rscale=1) + from sumpy.derivative_taker import ExprDerivativeTaker + deriv_taker = ExprDerivativeTaker(line_kernel, (tau,), sac=sac, + rscale=sym.sympify(1)) - return [kernel.postprocess_at_source( - deriv_taker.diff(i), avec).subs(tau, 0) + return [kernel.postprocess_at_source(deriv_taker.diff(i), avec) + .subs(tau, 0) for i in self.get_coefficient_identifiers()] else: # Workaround for sympy. The automatic distribution after @@ -233,29 +165,72 @@ def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): # # See also https://gitlab.tiker.net/inducer/pytential/merge_requests/12 - return [kernel.postprocess_at_source( - line_kernel.diff(tau, i), avec) + return [kernel.postprocess_at_source(line_kernel.diff(tau, i), avec) .subs(tau, 0) - for i in self.get_coefficient_identifiers()] - - def evaluate(self, tgt_kernel, coeffs, bvec, rscale, sac=None): + for i, in self.get_coefficient_identifiers()] + + @override + def evaluate(self, + kernel: ScalarKernel, + coeffs: Sequence[sym.Expr], + bvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None) -> sym.Expr: # no point in heeding rscale here--just ignore it + + # NOTE: We can't meaningfully apply target derivatives here. + # Instead, this is handled in LayerPotentialBase._evaluate. return sym.Add(*( - coeffs[self.get_storage_index(i)] / math.factorial(i) - for i in self.get_coefficient_identifiers())) + coeffs[self.get_storage_index(i)] / math.factorial(i[0]) + for i in self.get_coefficient_identifiers())) + + @override + def translate_from(self, + src_expansion: LocalExpansionBase | MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + m2l_translation_classes_dependent_data: ( + TranslationClassesDepData | None) = None + ) -> Sequence[sym.Expr]: + raise NotImplementedError # }}} # {{{ volume taylor -class VolumeTaylorLocalExpansionBase(LocalExpansionBase): +@dataclass(frozen=True) +class VolumeTaylorLocalExpansionBase(VolumeTaylorExpansionMixin, + LocalExpansionBase, ABC): """ Coefficients represent derivative values of the kernel. """ - def coefficients_from_source_vec(self, kernels, avec, bvec, rscale, weights, - sac=None): + m2l_translation_override: M2LTranslationBase | None = \ + field(kw_only=True, default=None) + + @property + @override + def m2l_translation(self) -> M2LTranslationBase: + if self.m2l_translation_override is not None: + return self.m2l_translation_override + else: + from sumpy.expansion.m2l import DefaultM2LTranslationClassFactory + factory = DefaultM2LTranslationClassFactory() + return factory.get_m2l_translation_class(self.kernel, self.__class__)() + + @override + def coefficients_from_source_vec(self, + kernels: Sequence[ScalarKernel], + avec: sym.Matrix, + bvec: sym.Matrix | None, + rscale: sym.Expr, + weights: Sequence[sym.Expr], + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: """Form an expansion with a linear combination of kernels and weights. Since all of the kernels share a base kernel, this method uses one derivative taker with one SymbolicAssignmentCollection object @@ -271,23 +246,23 @@ def coefficients_from_source_vec(self, kernels, avec, bvec, rscale, weights, the coefficients of the expansion. """ if not self.use_rscale: - rscale = 1 + rscale = sym.sympify(1) base_kernel = single_valued(knl.get_base_kernel() for knl in kernels) base_taker = base_kernel.get_derivative_taker(avec, rscale, sac) - result = [0]*len(self) + result: list[sym.Expr] = [sym.sympify(0)]*len(self) - for knl, weight in zip(kernels, weights): + for knl, weight in zip(kernels, weights, strict=True): taker = knl.postprocess_at_source(base_taker, avec) # Following is a hack to make sure cse works. if 1: - def save_temp(x): - return add_to_sac(sac, weight * x) + def save_temp(x: sym.Expr) -> sym.Expr: + return add_to_sac(sac, weight * x) # ruff:ignore[function-uses-loop-variable] for i, mi in enumerate(self.get_coefficient_identifiers()): result[i] += taker.diff(mi, save_temp) else: - def save_temp(x): + def save_temp(x: sym.Expr) -> sym.Expr: return add_to_sac(sac, x) for i, mi in enumerate(self.get_coefficient_identifiers()): @@ -295,222 +270,56 @@ def save_temp(x): return result - def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): + @override + def coefficients_from_source(self, + kernel: ScalarKernel, + avec: sym.Matrix, + bvec: sym.Matrix | None, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: return self.coefficients_from_source_vec((kernel,), avec, bvec, - rscale=rscale, weights=(1,), sac=sac) - - def evaluate(self, kernel, coeffs, bvec, rscale, sac=None): - if not self.use_rscale: - rscale = 1 + rscale=rscale, weights=(sym.sympify(1),), sac=sac) + + @override + def evaluate(self, + kernel: ScalarKernel, + coeffs: Sequence[sym.Expr], + bvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> sym.Expr: + rscale = sym.sympify(1 if not self.use_rscale else rscale) evaluated_coeffs = ( self.expansion_terms_wrangler.get_full_kernel_derivatives_from_stored( coeffs, rscale, sac=sac)) - bvec_scaled = [b*rscale**-1 for b in bvec] - from sumpy.tools import mi_power, mi_factorial + bvec_scaled = [cast("sym.Expr", b*rscale**-1) for b in bvec] + from sumpy.tools import mi_factorial, mi_power - result = sum( + result = sym.sympify(sum( coeff * mi_power(bvec_scaled, mi, evaluate=False) / mi_factorial(mi) for coeff, mi in zip( - evaluated_coeffs, self.get_full_coefficient_identifiers())) + evaluated_coeffs, self.get_full_coefficient_identifiers(), + strict=True))) return kernel.postprocess_at_target(result, bvec) - def m2l_translation_classes_dependent_ndata(self, src_expansion): - """Returns number of expressions in M2L global precomputation step. - """ - mis_with_dummy_rows, _, _ = \ - self._m2l_translation_classes_dependent_data_mis(src_expansion) - - return len(mis_with_dummy_rows) - - def _m2l_translation_classes_dependent_data_mis(self, src_expansion): - """We would like to compute the M2L by way of a circulant matrix below. - To get the matrix representing the M2L into circulant form, a certain - numbering of rows and columns (as identified by multi-indices) is - required. This routine returns that numbering. - - .. note:: - - The set of multi-indices returned may be a superset of the - coefficients used by the expansion. On the input end, those - coefficients are taken as zero. On output, they are simply - dropped from the computed result. - - This method returns the multi-indices representing the rows - of the circulant matrix, the multi-indices representing the rows - of the M2L translation matrix and the maximum multi-index of the - latter. - """ - from pytools import generate_nonnegative_integer_tuples_below as gnitb - from sumpy.tools import add_mi - - # max_mi is the multi-index which is the sum of the - # element-wise maximum of source multi-indices and the - # element-wise maximum of target multi-indices. - max_mi = [0]*self.dim - for i in range(self.dim): - max_mi[i] = max(mi[i] for mi in - src_expansion.get_coefficient_identifiers()) - max_mi[i] += max(mi[i] for mi in - self.get_coefficient_identifiers()) - - # These are the multi-indices representing the rows - # in the circulant matrix. Note that to get the circulant - # matrix structure some multi-indices that are not in the - # M2L translation matrix are added. - # This corresponds to adding O(p^(d-1)) - # additional rows and columns in the case of some PDEs - # like Laplace and O(p^d) in other cases. - circulant_matrix_mis = list(gnitb([m + 1 for m in max_mi])) - - # These are the multi-indices representing the rows - # in the M2L translation matrix without the additional - # multi-indices in the circulant matrix - needed_vector_terms = set() - # For eg: 2D full Taylor Laplace, we only need kernel derivatives - # (n1+n2, m1+m2), n1+m1<=p, n2+m2<=p - for tgt_deriv in self.get_coefficient_identifiers(): - for src_deriv in src_expansion.get_coefficient_identifiers(): - needed = add_mi(src_deriv, tgt_deriv) - if needed not in needed_vector_terms: - needed_vector_terms.add(needed) - - return circulant_matrix_mis, tuple(needed_vector_terms), max_mi - - def m2l_translation_classes_dependent_data(self, src_expansion, src_rscale, - dvec, tgt_rscale, sac): - - # We know the general form of the multipole expansion is: - # - # coeff0 * diff(kernel(src - c1), mi0) + - # coeff1 * diff(kernel(src - c1), mi1) + ... - # - # To get the local expansion coefficients, we take derivatives of - # the multipole expansion. For eg: the coefficient w.r.t mir is - # - # coeff0 * diff(kernel(c2 - c1), mi0 + mir) + - # coeff1 * diff(kernel(c2 - c1), mi1 + mir) + ... - # - # The derivatives above depends only on `c2 - c1` and can be precomputed - # globally as there are only a finite number of values for `c2 - c1` for - # m2l. - - if not self.use_rscale: - src_rscale = 1 - - circulant_matrix_mis, needed_vector_terms, max_mi = \ - self._m2l_translation_classes_dependent_data_mis(src_expansion) - - circulant_matrix_ident_to_index = {ident: i for i, ident in - enumerate(circulant_matrix_mis)} - - # Create a expansion terms wrangler for derivatives up to order - # (tgt order)+(src order) including a corresponding reduction matrix - # For eg: 2D full Taylor Laplace, this is (n, m), - # n+m<=2*p, n<=2*p, m<=2*p - srcplusderiv_terms_wrangler = \ - src_expansion.expansion_terms_wrangler.copy( - order=self.order + src_expansion.order, max_mi=tuple(max_mi)) - srcplusderiv_full_coeff_ids = \ - srcplusderiv_terms_wrangler.get_full_coefficient_identifiers() - srcplusderiv_ident_to_index = {ident: i for i, ident in - enumerate(srcplusderiv_full_coeff_ids)} - - # The vector has the kernel derivatives and depends only on the distance - # between the two centers - taker = src_expansion.kernel.get_derivative_taker(dvec, src_rscale, sac) - vector_stored = [] - # Calculate the kernel derivatives for the compressed set - for term in \ - srcplusderiv_terms_wrangler.get_coefficient_identifiers(): - kernel_deriv = taker.diff(term) - vector_stored.append(kernel_deriv) - # Calculate the kernel derivatives for the full set - vector_full = \ - srcplusderiv_terms_wrangler.get_full_kernel_derivatives_from_stored( - vector_stored, src_rscale) - - for term in srcplusderiv_full_coeff_ids: - assert term in needed_vector_terms - - vector = [0]*len(needed_vector_terms) - for i, term in enumerate(needed_vector_terms): - vector[i] = add_to_sac(sac, - vector_full[srcplusderiv_ident_to_index[term]]) - - # Add zero values needed to make the translation matrix circulant - derivatives_full = [0]*len(circulant_matrix_mis) - for expr, mi in zip(vector, needed_vector_terms): - derivatives_full[circulant_matrix_ident_to_index[mi]] = expr - - if self.use_fft_for_m2l: - # Note that the matrix we have now is a mirror image of a - # circulant matrix. We reverse the first column to get the - # first column for the circulant matrix and then finally - # use the FFT for convolution represented by the circulant - # matrix. - return fft(list(reversed(derivatives_full)), sac=sac) - - return derivatives_full - - def m2l_preprocess_multipole_exprs(self, src_expansion, src_coeff_exprs, sac, - src_rscale): - circulant_matrix_mis, needed_vector_terms, max_mi = \ - self._m2l_translation_classes_dependent_data_mis(src_expansion) - circulant_matrix_ident_to_index = {ident: i for i, ident in - enumerate(circulant_matrix_mis)} - - # Calculate the input vector for the circulant matrix - input_vector = [0] * len(circulant_matrix_mis) - for coeff, term in zip( - src_coeff_exprs, - src_expansion.get_coefficient_identifiers()): - input_vector[circulant_matrix_ident_to_index[term]] = \ - add_to_sac(sac, coeff) - - if self.use_fft_for_m2l: - return fft(input_vector, sac=sac) - else: - return input_vector - - def m2l_preprocess_multipole_nexprs(self, src_expansion): - circulant_matrix_mis, _, _ = \ - self._m2l_translation_classes_dependent_data_mis(src_expansion) - return len(circulant_matrix_mis) - - def m2l_postprocess_local_exprs(self, src_expansion, m2l_result, src_rscale, - tgt_rscale, sac): - circulant_matrix_mis, needed_vector_terms, max_mi = \ - self._m2l_translation_classes_dependent_data_mis(src_expansion) - circulant_matrix_ident_to_index = {ident: i for i, ident in - enumerate(circulant_matrix_mis)} - - if self.use_fft_for_m2l: - n = len(circulant_matrix_mis) - m2l_result = fft(m2l_result, inverse=True, sac=sac) - # since we reversed the M2L matrix, we reverse the result - # to get the correct result - m2l_result = list(reversed(m2l_result[:n])) - - # Filter out the dummy rows and scale them for target - rscale_ratio = add_to_sac(sac, tgt_rscale/src_rscale) - result = [ - m2l_result[circulant_matrix_ident_to_index[term]] - * rscale_ratio**sum(term) - for term in self.get_coefficient_identifiers()] - - return result - - def m2l_postprocess_local_nexprs(self, src_expansion): - return self.m2l_translation_classes_dependent_ndata(src_expansion) - - def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale, sac=None, _fast_version=True, - m2l_translation_classes_dependent_data=None): + @override + def translate_from(self, + src_expansion: LocalExpansionBase | MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + m2l_translation_classes_dependent_data: ( + TranslationClassesDepData | None) = None, + _fast_version: bool = True, + ) -> Sequence[sym.Expr]: logger.info("building translation operator for %s: %s(%d) -> %s(%d): start", src_expansion.kernel, type(src_expansion).__name__, @@ -519,68 +328,48 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, self.order) if not self.use_rscale: - src_rscale = 1 - tgt_rscale = 1 + src_rscale = sym.sympify(1) + tgt_rscale = sym.sympify(1) from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansionBase # {{{ M2L if isinstance(src_expansion, VolumeTaylorMultipoleExpansionBase): - circulant_matrix_mis, needed_vector_terms, max_mi = \ - self._m2l_translation_classes_dependent_data_mis(src_expansion) - - if not m2l_translation_classes_dependent_data: - derivatives = self.m2l_translation_classes_dependent_data( - src_expansion, src_rscale, dvec, tgt_rscale, sac) - else: - derivatives = m2l_translation_classes_dependent_data - - if self.use_fft_for_m2l: - assert len(src_coeff_exprs) == len(derivatives) - result = [a*b for a, b in zip(derivatives, src_coeff_exprs)] - else: - if not self.use_preprocessing_for_m2l: - src_coeff_exprs = self.m2l_preprocess_multipole_exprs( - src_expansion, src_coeff_exprs, sac, src_rscale) - - # Returns a big symbolic sum of matrix entries - # (FIXME? Though this is just the correctness-checking - # fallback for the FFT anyhow) - result = matvec_toeplitz_upper_triangular(src_coeff_exprs, - derivatives) - - if not self.use_preprocessing_for_m2l: - result = self.m2l_postprocess_local_exprs(src_expansion, - result, src_rscale, tgt_rscale, sac) + m2l_result = self.m2l_translation.translate(self, src_expansion, + src_coeff_exprs, src_rscale, dvec, tgt_rscale, sac, + m2l_translation_classes_dependent_data) logger.info("building translation operator: done") - return result + return m2l_result # }}} + assert isinstance(src_expansion, VolumeTaylorLocalExpansionBase) + # {{{ L2L # not coming from a Taylor multipole: expand via derivatives - rscale_ratio = add_to_sac(sac, tgt_rscale/src_rscale) + # FIXME: this shouldn't need to be sympified, but many places still + # pass in floats. removing it fails `test_m2m_and_l2l_exprs_simpler` + rscale_ratio = add_to_sac(sac, sym.sympify(tgt_rscale/src_rscale)) src_wrangler = src_expansion.expansion_terms_wrangler src_coeffs = ( src_wrangler.get_full_kernel_derivatives_from_stored( src_coeff_exprs, src_rscale, sac=sac)) + src_mis = \ src_expansion.expansion_terms_wrangler.get_full_coefficient_identifiers() - src_mi_to_index = {mi: i for i, mi in enumerate(src_mis)} - tgt_mis = \ - self.expansion_terms_wrangler.get_coefficient_identifiers() + tgt_mis = self.expansion_terms_wrangler.get_coefficient_identifiers() tgt_mi_to_index = {mi: i for i, mi in enumerate(tgt_mis)} tgt_split = self.expansion_terms_wrangler._split_coeffs_into_hyperplanes() p = max(sum(mi) for mi in src_mis) - result = [0] * len(tgt_mis) + result: list[sym.Expr] = [sym.sympify(0)] * len(tgt_mis) # Local expansion around the old center gives us that, # @@ -635,13 +424,16 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, for axis in {d for d, _ in tgt_split}: # Use the axis as the first dimension to vary so that the below # algorithm is O(p^{d+1}) for full and O(p^{d}) for compressed - dims = [axis] + list(range(axis)) + \ - list(range(axis+1, self.dim)) + dims = [axis, *list(range(axis)), *list(range(axis + 1, self.dim))] + # Start with source coefficients. Gets updated after each axis. - cur_dim_input_coeffs = src_coeffs + cur_dim_input_coeffs = list(src_coeffs) + cur_dim_output_coeffs: list[sym.Expr] = [sym.sympify(-1)] * len(src_mis) + # O(1) iterations for d in dims: - cur_dim_output_coeffs = [0] * len(src_mis) + cur_dim_output_coeffs = [sym.sympify(0)] * len(src_mis) + # Only O(p^{d-1}) operations are used in compressed # O(p^d) operations are used in full for out_i, out_mi in enumerate(src_mis): @@ -649,9 +441,13 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, for q in range(p+1-sum(out_mi)): src_mi = mi_increment_axis(out_mi, d, q) if src_mi in src_mi_to_index: - cur_dim_output_coeffs[out_i] += (dvec[d]/src_rscale)**q \ - * cur_dim_input_coeffs[src_mi_to_index[src_mi]] \ - / math.factorial(q) + dvec_d = cast("sym.Expr", dvec[d]) + + cur_dim_output_coeffs[out_i] += ( + (dvec_d/src_rscale)**q + * cur_dim_input_coeffs[src_mi_to_index[src_mi]] + / math.factorial(q)) + # Y at the end of the iteration becomes the source coefficients # for the next iteration cur_dim_input_coeffs = cur_dim_output_coeffs @@ -660,14 +456,15 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, # In L2L, source level usually has same or higher order than target # level. If not, extra coeffs in target level are zero filled. if mi not in src_mi_to_index: - result[tgt_mi_to_index[mi]] = 0 + result[tgt_mi_to_index[mi]] = sym.sympify(0) else: # Add to result after scaling - result[tgt_mi_to_index[mi]] += \ - cur_dim_output_coeffs[src_mi_to_index[mi]] \ - * rscale_ratio ** sum(mi) + result[tgt_mi_to_index[mi]] += ( + cur_dim_output_coeffs[src_mi_to_index[mi]] + * rscale_ratio ** sum(mi)) # {{{ simpler, functionally equivalent code + if not _fast_version: # Rscale/operand magnitude is fairly sensitive to the order of # operations--which is something we don't have fantastic control @@ -676,157 +473,99 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, # to compensate for differentiating which is done at the end. # This moves the two cancelling "rscales" closer to each other at # the end in the hope of helping rscale magnitude. - from sumpy.tools import ExprDerivativeTaker - dvec_scaled = [d*src_rscale for d in dvec] + from sumpy.derivative_taker import ExprDerivativeTaker + + dvec_scaled = sym.Matrix([d*src_rscale for d in dvec]) expr = src_expansion.evaluate(src_expansion.kernel, src_coeff_exprs, dvec_scaled, rscale=src_rscale, sac=sac) + replace_dict = {d: d/src_rscale for d in dvec} taker = ExprDerivativeTaker(expr, dvec) rscale_ratio = sym.UnevaluatedExpr(tgt_rscale/src_rscale) + result = [ (taker.diff(mi).xreplace(replace_dict) * rscale_ratio**sum(mi)) for mi in self.get_coefficient_identifiers()] + # }}} - # }}} + logger.info("building translation operator: done") return result - def loopy_translate_from(self, src_expansion): + def loopy_translate_from(self, src_expansion: ExpansionBase) -> lp.TranslationUnit: from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansionBase if isinstance(src_expansion, VolumeTaylorMultipoleExpansionBase): - if self.use_preprocessing_for_m2l: - ncoeff_src = self.m2l_preprocess_multipole_nexprs(src_expansion) - ncoeff_tgt = self.m2l_postprocess_local_nexprs(src_expansion) - icoeff_src = pymbolic.var("icoeff_src") - icoeff_tgt = pymbolic.var("icoeff_tgt") - domains = [f"{{[icoeff_tgt]: 0<=icoeff_tgt<{ncoeff_tgt} }}"] - - coeff = pymbolic.var("coeff") - src_coeffs = pymbolic.var("src_coeffs") - m2l_translation_classes_dependent_data = pymbolic.var("data") - - if self.use_fft_for_m2l: - expr = src_coeffs[icoeff_tgt] \ - * m2l_translation_classes_dependent_data[icoeff_tgt] - else: - toeplitz_first_row = src_coeffs[icoeff_src-icoeff_tgt] - vector = m2l_translation_classes_dependent_data[icoeff_src] - expr = toeplitz_first_row * vector - domains.append( - f"{{[icoeff_src]: icoeff_tgt<=icoeff_src<{ncoeff_src} }}") - - expr = src_coeffs[icoeff_tgt] \ - * m2l_translation_classes_dependent_data[icoeff_tgt] - - insns = [ - lp.Assignment( - assignee=coeff[icoeff_tgt], - expression=coeff[icoeff_tgt] + expr), - ] - return lp.make_function(domains, insns, - kernel_data=[ - lp.GlobalArg("coeff, src_coeffs, data", - shape=lp.auto), - lp.ValueArg("src_rscale, tgt_rscale"), - ...], - name="e2e", - lang_version=lp.MOST_RECENT_LANGUAGE_VERSION, - ) + return self.m2l_translation.loopy_translate(self, src_expansion) + raise NotImplementedError( - f"A direct loopy kernel for translation from " + f"a direct loopy kernel for translation from " f"{src_expansion} to {self} is not implemented.") class VolumeTaylorLocalExpansion( VolumeTaylorExpansion, VolumeTaylorLocalExpansionBase): - - def __init__(self, kernel, order, use_rscale=None, - use_fft_for_m2l=False, use_preprocessing_for_m2l=None): - VolumeTaylorLocalExpansionBase.__init__(self, kernel, order, use_rscale, - use_fft_for_m2l, use_preprocessing_for_m2l=None) - VolumeTaylorExpansion.__init__(self, kernel, order, use_rscale) + pass class LinearPDEConformingVolumeTaylorLocalExpansion( LinearPDEConformingVolumeTaylorExpansion, VolumeTaylorLocalExpansionBase): - - def __init__(self, kernel, order, use_rscale=None, - use_fft_for_m2l=False, use_preprocessing_for_m2l=None): - VolumeTaylorLocalExpansionBase.__init__(self, kernel, order, use_rscale, - use_fft_for_m2l, use_preprocessing_for_m2l) - LinearPDEConformingVolumeTaylorExpansion.__init__( - self, kernel, order, use_rscale) - - -class LaplaceConformingVolumeTaylorLocalExpansion( - LinearPDEConformingVolumeTaylorLocalExpansion): - - def __init__(self, *args, **kwargs): - from warnings import warn - warn("LaplaceConformingVolumeTaylorLocalExpansion is deprecated. " - "Use LinearPDEConformingVolumeTaylorLocalExpansion instead.", - DeprecationWarning, stacklevel=2) - super().__init__(*args, **kwargs) - - -class HelmholtzConformingVolumeTaylorLocalExpansion( - LinearPDEConformingVolumeTaylorLocalExpansion): - - def __init__(self, *args, **kwargs): - from warnings import warn - warn("HelmholtzConformingVolumeTaylorLocalExpansion is deprecated. " - "Use LinearPDEConformingVolumeTaylorLocalExpansion instead.", - DeprecationWarning, stacklevel=2) - super().__init__(*args, **kwargs) - - -class BiharmonicConformingVolumeTaylorLocalExpansion( - LinearPDEConformingVolumeTaylorLocalExpansion): - - def __init__(self, *args, **kwargs): - from warnings import warn - warn("BiharmonicConformingVolumeTaylorLocalExpansion is deprecated. " - "Use LinearPDEConformingVolumeTaylorLocalExpansion instead.", - DeprecationWarning, stacklevel=2) - super().__init__(*args, **kwargs) + pass # }}} # {{{ 2D Bessel-based-expansion -class _FourierBesselLocalExpansion(LocalExpansionBase): - def __init__(self, kernel, order, use_rscale=None, - use_fft_for_m2l=False, use_preprocessing_for_m2l=None): - - if use_fft_for_m2l: - # FIXME: expansion with FFT is correct symbolically and can be verified - # with sympy. However there are numerical issues that we have to deal - # with. Greengard and Rokhlin 1988 attributes this to numerical - # instability but gives rscale as a possible solution. Sumpy's rscale - # choice is slightly different from Greengard and Rokhlin and that - # might be the reason for this numerical issue. - raise ValueError("Bessel based expansions with FFT is not fully " - "supported yet.") - - super().__init__(kernel, order, use_rscale, - use_fft_for_m2l=use_fft_for_m2l, - use_preprocessing_for_m2l=use_preprocessing_for_m2l) +@dataclass(frozen=True) +class FourierBesselLocalExpansionMixin(LocalExpansionBase, ABC): - def get_storage_index(self, k): - return self.order+k + m2l_translation_override: M2LTranslationBase | None = ( + field(kw_only=True, default=None)) - def get_coefficient_identifiers(self): - return list(range(-self.order, self.order+1)) + @property + @abstractmethod + def mpole_expn_class(self) -> type[HankelBased2DMultipoleExpansion]: + ... - def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): + @property + @override + def m2l_translation(self) -> M2LTranslationBase: + if self.m2l_translation_override is not None: + return self.m2l_translation_override + else: + from sumpy.expansion.m2l import DefaultM2LTranslationClassFactory + + factory = DefaultM2LTranslationClassFactory() + return factory.get_m2l_translation_class(self.kernel, self.__class__)() + + @abstractmethod + def get_bessel_arg_scaling(self) -> sym.Expr: + ... + + @override + def get_storage_index(self, mi: MultiIndex) -> int: + ind, = mi + return self.order+ind + + @override + def get_coefficient_identifiers(self) -> Sequence[MultiIndex]: + return [(i,) for i in range(-self.order, self.order+1)] + + @override + def coefficients_from_source(self, + kernel: ScalarKernel, + avec: sym.Matrix, + bvec: sym.Matrix | None, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: if not self.use_rscale: - rscale = 1 + rscale = sym.sympify(1) - from sumpy.symbolic import sym_real_norm_2, Hankel1 + from sumpy.symbolic import Hankel1, sym_real_norm_2 arg_scale = self.get_bessel_arg_scaling() @@ -837,249 +576,132 @@ def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): Hankel1(c, arg_scale * avec_len, 0) * rscale ** abs(c) * sym.exp(sym.I * c * source_angle_rel_center), avec) - for c in self.get_coefficient_identifiers()] - - def evaluate(self, kernel, coeffs, bvec, rscale, sac=None): + for c, in self.get_coefficient_identifiers()] + + @override + def evaluate(self, + kernel: ScalarKernel, + coeffs: Sequence[sym.Expr], + bvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> sym.Expr: if not self.use_rscale: - rscale = 1 + rscale = sym.sympify(1) - from sumpy.symbolic import sym_real_norm_2, BesselJ + from sumpy.symbolic import BesselJ, sym_real_norm_2 bvec_len = sym_real_norm_2(bvec) target_angle_rel_center = sym.atan2(bvec[1], bvec[0]) arg_scale = self.get_bessel_arg_scaling() - return sum(coeffs[self.get_storage_index(c)] + return sym.sympify(sum(coeffs[self.get_storage_index((c,))] * kernel.postprocess_at_target( BesselJ(c, arg_scale * bvec_len, 0) / rscale ** abs(c) * sym.exp(sym.I * c * -target_angle_rel_center), bvec) - for c in self.get_coefficient_identifiers()) - - def m2l_translation_classes_dependent_ndata(self, src_expansion): - nexpr = 2 * self.order + 2 * src_expansion.order + 1 - return nexpr - - def m2l_translation_classes_dependent_data(self, src_expansion, src_rscale, - dvec, tgt_rscale, sac): - - from sumpy.symbolic import sym_real_norm_2, Hankel1 - from sumpy.tools import fft - - dvec_len = sym_real_norm_2(dvec) - new_center_angle_rel_old_center = sym.atan2(dvec[1], dvec[0]) - arg_scale = self.get_bessel_arg_scaling() - # [-(src_order+tgt_order), ..., 0, ..., (src_order + tgt_order)] - m2l_translation_classes_dependent_data = \ - [0] * (2*self.order + 2 * src_expansion.order + 1) - - # The M2L is a mirror image of a Toeplitz matvec with Hankel function - # evaluations. https://dlmf.nist.gov/10.23.F1 - # This loop computes the first row and the last column vector sufficient - # to specify the matrix entries. - for j in self.get_coefficient_identifiers(): - idx_j = self.get_storage_index(j) - for m in src_expansion.get_coefficient_identifiers(): - idx_m = src_expansion.get_storage_index(m) - m2l_translation_classes_dependent_data[idx_j + idx_m] = ( - Hankel1(m + j, arg_scale * dvec_len, 0) - * sym.exp(sym.I * (m + j) * new_center_angle_rel_old_center)) - - if self.use_fft_for_m2l: - order = src_expansion.order - # For this expansion, we have a mirror image of a Toeplitz matrix. - # First, we have to take the mirror image of the M2L matrix. - # - # After that the Toeplitz matrix has to be embedded in a circulant - # matrix. In this cicrcular matrix the first part of the first - # column is the first column of the Toeplitz matrix which is - # the last column of the M2L matrix. The second part is the - # reverse of the first row of the Toeplitz matrix which - # is the reverse of the first row of the M2L matrix. - first_row_m2l, last_column_m2l = \ - m2l_translation_classes_dependent_data[:2*order], \ - m2l_translation_classes_dependent_data[2*order:] - first_column_toeplitz = last_column_m2l - first_row_toeplitz = list(reversed(first_row_m2l)) - - first_column_circulant = list(first_column_toeplitz) + \ - list(reversed(first_row_toeplitz)) - return fft(first_column_circulant, sac) - else: - return m2l_translation_classes_dependent_data - - def m2l_preprocess_multipole_exprs(self, src_expansion, src_coeff_exprs, sac, - src_rscale): - - from sumpy.tools import fft - src_coeff_exprs = list(src_coeff_exprs) - for m in src_expansion.get_coefficient_identifiers(): - src_coeff_exprs[src_expansion.get_storage_index(m)] *= src_rscale**abs(m) - - if self.use_fft_for_m2l: - src_coeff_exprs = list(reversed(src_coeff_exprs)) - src_coeff_exprs += [0] * (len(src_coeff_exprs) - 1) - return fft(src_coeff_exprs, sac=sac) - else: - return src_coeff_exprs - - def m2l_preprocess_multipole_nexprs(self, src_expansion): - return 2*src_expansion.order + 1 - - def m2l_postprocess_local_exprs(self, src_expansion, m2l_result, src_rscale, - tgt_rscale, sac): - - if self.use_fft_for_m2l: - m2l_result = fft(m2l_result, inverse=True, sac=sac) - m2l_result = m2l_result[:2*self.order+1] - - # Filter out the dummy rows and scale them for target - result = [] - for j in self.get_coefficient_identifiers(): - result.append(m2l_result[self.get_storage_index(j)] - * tgt_rscale**(abs(j)) * sym.Integer(-1)**j) - - return result - - def m2l_postprocess_local_nexprs(self, src_expansion): - return 2*self.order + 1 - - def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale, sac=None, m2l_translation_classes_dependent_data=None): - from sumpy.symbolic import sym_real_norm_2, BesselJ + for c, in self.get_coefficient_identifiers())) + + @override + def translate_from(self, + src_expansion: LocalExpansionBase | MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + m2l_translation_classes_dependent_data: ( + TranslationClassesDepData | None) = None + ) -> Sequence[sym.Expr]: + from sumpy.symbolic import BesselJ, sym_real_norm_2 if not self.use_rscale: - src_rscale = 1 - tgt_rscale = 1 + src_rscale = sym.sympify(1) + tgt_rscale = sym.sympify(1) arg_scale = self.get_bessel_arg_scaling() if isinstance(src_expansion, type(self)): dvec_len = sym_real_norm_2(dvec) new_center_angle_rel_old_center = sym.atan2(dvec[1], dvec[0]) - translated_coeffs = [] - for j in self.get_coefficient_identifiers(): + translated_coeffs: list[sym.Expr] = [] + for j, in self.get_coefficient_identifiers(): translated_coeffs.append( - sum(src_coeff_exprs[src_expansion.get_storage_index(m)] - * BesselJ(m - j, arg_scale * dvec_len, 0) - / src_rscale ** abs(m) - * tgt_rscale ** abs(j) - * sym.exp(sym.I * (m - j) * -new_center_angle_rel_old_center) - for m in src_expansion.get_coefficient_identifiers())) + sum((src_coeff_exprs[src_expansion.get_storage_index((m,))] + * BesselJ(m - j, arg_scale * dvec_len, 0) + / src_rscale ** abs(m) + * tgt_rscale ** abs(j) + * sym.exp(sym.I * (m - j) * -new_center_angle_rel_old_center) + for m, in src_expansion.get_coefficient_identifiers()), + sym.sympify(0))) + return translated_coeffs if isinstance(src_expansion, self.mpole_expn_class): - if m2l_translation_classes_dependent_data is None: - derivatives = self.m2l_translation_classes_dependent_data( - src_expansion, src_rscale, dvec, tgt_rscale, sac=sac) - else: - derivatives = m2l_translation_classes_dependent_data - - if self.use_fft_for_m2l: - assert m2l_translation_classes_dependent_data is not None - assert len(derivatives) == len(src_coeff_exprs) - translated_coeffs = [a * b for a, b in zip(derivatives, - src_coeff_exprs)] - else: - if not self.use_preprocessing_for_m2l: - src_coeff_exprs = self.m2l_preprocess_multipole_exprs( - src_expansion, src_coeff_exprs, sac, src_rscale) - - translated_coeffs = [ - sum(derivatives[m + j + self.order + src_expansion.order] - * src_coeff_exprs[src_expansion.get_storage_index(m)] - for m in src_expansion.get_coefficient_identifiers()) - for j in self.get_coefficient_identifiers()] - - if not self.use_preprocessing_for_m2l: - translated_coeffs = self.m2l_postprocess_local_exprs( - src_expansion, translated_coeffs, src_rscale, tgt_rscale, - sac) - - return translated_coeffs + return self.m2l_translation.translate(self, src_expansion, + src_coeff_exprs, src_rscale, dvec, tgt_rscale, sac, + m2l_translation_classes_dependent_data) raise RuntimeError( "do not know how to translate " f"{type(src_expansion).__name__} to {type(self).__name__}") - def loopy_translate_from(self, src_expansion): + def loopy_translate_from(self, src_expansion: ExpansionBase) -> lp.TranslationUnit: if isinstance(src_expansion, self.mpole_expn_class): - if self.use_preprocessing_for_m2l: - ncoeff_src = self.m2l_preprocess_multipole_nexprs(src_expansion) - ncoeff_tgt = self.m2l_postprocess_local_nexprs(src_expansion) - - icoeff_src = pymbolic.var("icoeff_src") - icoeff_tgt = pymbolic.var("icoeff_tgt") - domains = [f"{{[icoeff_tgt]: 0<=icoeff_tgt<{ncoeff_tgt} }}"] + return self.m2l_translation.loopy_translate(self, src_expansion) - coeff = pymbolic.var("coeff") - src_coeffs = pymbolic.var("src_coeffs") - m2l_translation_classes_dependent_data = pymbolic.var("data") - - if self.use_fft_for_m2l: - expr = src_coeffs[icoeff_tgt] \ - * m2l_translation_classes_dependent_data[icoeff_tgt] - else: - expr = src_coeffs[icoeff_src] \ - * m2l_translation_classes_dependent_data[ - icoeff_tgt + icoeff_src] - domains.append( - f"{{[icoeff_src]: 0<=icoeff_src<{ncoeff_src} }}") - - insns = [ - lp.Assignment( - assignee=coeff[icoeff_tgt], - expression=coeff[icoeff_tgt] + expr), - ] - return lp.make_function(domains, insns, - kernel_data=[ - lp.GlobalArg("coeff, src_coeffs, data", - shape=lp.auto), - lp.ValueArg("src_rscale, tgt_rscale"), - ...], - name="e2e", - lang_version=lp.MOST_RECENT_LANGUAGE_VERSION, - ) raise NotImplementedError( - f"A direct loopy kernel for translation from " + f"a direct loopy kernel for translation from " f"{src_expansion} to {self} is not implemented.") -class H2DLocalExpansion(_FourierBesselLocalExpansion): - def __init__(self, kernel, order, use_rscale=None, - use_fft_for_m2l=False, use_preprocessing_for_m2l=None): +class H2DLocalExpansion(FourierBesselLocalExpansionMixin): + def __post_init__(self) -> None: from sumpy.kernel import HelmholtzKernel - assert (isinstance(kernel.get_base_kernel(), HelmholtzKernel) - and kernel.dim == 2) - super().__init__(kernel, order, use_rscale, - use_fft_for_m2l=use_fft_for_m2l, - use_preprocessing_for_m2l=use_preprocessing_for_m2l) + kernel = self.kernel.get_base_kernel() + if not (isinstance(kernel, HelmholtzKernel) and kernel.dim == 2): + raise TypeError( + f"{type(self).__name__} can only be applied to 2D HelmholtzKernel: " + f"{kernel!r}") - from sumpy.expansion.multipole import H2DMultipoleExpansion - self.mpole_expn_class = H2DMultipoleExpansion + @property + @override + def mpole_expn_class(self) -> type[HankelBased2DMultipoleExpansion]: + return H2DMultipoleExpansion - def get_bessel_arg_scaling(self): - return sym.Symbol(self.kernel.get_base_kernel().helmholtz_k_name) + @override + def get_bessel_arg_scaling(self) -> sym.Expr: + from sumpy.kernel import HelmholtzKernel + kernel = self.kernel.get_base_kernel() + assert isinstance(kernel, HelmholtzKernel) + + return sym.Symbol(kernel.helmholtz_k_name) -class Y2DLocalExpansion(_FourierBesselLocalExpansion): - def __init__(self, kernel, order, use_rscale=None, - use_fft_for_m2l=False, use_preprocessing_for_m2l=None): +class Y2DLocalExpansion(FourierBesselLocalExpansionMixin): + def __post_init__(self) -> None: from sumpy.kernel import YukawaKernel - assert (isinstance(kernel.get_base_kernel(), YukawaKernel) - and kernel.dim == 2) - super().__init__(kernel, order, use_rscale, - use_fft_for_m2l=use_fft_for_m2l, - use_preprocessing_for_m2l=use_preprocessing_for_m2l) + kernel = self.kernel.get_base_kernel() + if not (isinstance(kernel, YukawaKernel) and kernel.dim == 2): + raise TypeError( + f"{type(self).__name__} can only be applied to 2D YukawaKernel: " + f"{kernel!r}") - from sumpy.expansion.multipole import Y2DMultipoleExpansion - self.mpole_expn_class = Y2DMultipoleExpansion + @property + @override + def mpole_expn_class(self) -> type[HankelBased2DMultipoleExpansion]: + return Y2DMultipoleExpansion + + @override + def get_bessel_arg_scaling(self) -> sym.Expr: + from sumpy.kernel import YukawaKernel + kernel = self.kernel.get_base_kernel() + assert isinstance(kernel, YukawaKernel) - def get_bessel_arg_scaling(self): - return sym.I * sym.Symbol(self.kernel.get_base_kernel().yukawa_lambda_name) + return sym.I * sym.Symbol(kernel.yukawa_lambda_name) # }}} diff --git a/sumpy/expansion/loopy.py b/sumpy/expansion/loopy.py new file mode 100644 index 000000000..9944183c9 --- /dev/null +++ b/sumpy/expansion/loopy.py @@ -0,0 +1,257 @@ +from __future__ import annotations + + +__copyright__ = "Copyright (C) 2022 Isuru Fernando" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import logging +from typing import TYPE_CHECKING, cast + +import numpy as np + +import loopy as lp +import pymbolic.primitives as prim + +import sumpy.symbolic as sym +from sumpy.assignment_collection import SymbolicAssignmentCollection +from sumpy.tools import gather_loopy_arguments, gather_loopy_source_arguments + + +if TYPE_CHECKING: + from collections.abc import Sequence + + from pymbolic.typing import ArithmeticExpression + + from sumpy.expansion import ExpansionBase + from sumpy.kernel import ScalarKernel + + +logger = logging.getLogger(__name__) + + +def make_e2p_loopy_kernel( + expansion: ExpansionBase, + kernels: Sequence[ScalarKernel], + ) -> lp.TranslationUnit: + """ + A helper function that creates a :mod:`loopy` kernel for multipole/local evaluation. + + This function uses symbolic expressions given by the expansion class, + converts them to :mod:`pymbolic` expressions and generates a :mod:`loopy` + kernel. Note that the :mod:`loopy` kernel returned has lots of expressions + in it and (likely) takes a long time. Therefore, this function should be + used only as a fallback when there is no "loop-y" kernel to evaluate the + expansion. + """ + + sac = SymbolicAssignmentCollection() + + dim = expansion.dim + ncoeffs = len(expansion.get_coefficient_identifiers()) + + bvec = sym.make_sym_vector("b", dim) + rscale = sym.Symbol("rscale") + + domains = [ + "{[idim]: 0<=idim lp.TranslationUnit: + """ + A helper function that creates a :mod:`loopy` kernel for multipole/local evaluation. + + This function uses symbolic expressions given by the expansion class, + converts them to :mod:`pymbolic` expressions and generates a :mod:`loopy` + kernel. Note that the :mod:`loopy` kernel returned has lots of expressions + in it and (likely) takes a long time. Therefore, this function should be + used only as a fallback when there is no "loop-y" kernel to evaluate the + expansion. + """ + sac = SymbolicAssignmentCollection() + + dim = expansion.dim + ncoeffs = len(expansion.get_coefficient_identifiers()) + + avec = sym.make_sym_vector("a", dim) + rscale = sym.Symbol("rscale") + + domains = [ + "{[idim]: 0<=idim type[M2LTranslationBase]: + """ + :returns: a subclass of :class:`M2LTranslationBase` suitable for + *base_kernel* and *local_expansion_class*. + """ + + +class NonFFTM2LTranslationClassFactory(M2LTranslationClassFactoryBase): + """An implementation of :class:`M2LTranslationClassFactoryBase` that uses + non FFT M2L translation class. + """ + + @override + def get_m2l_translation_class( + self, + base_kernel: ScalarKernel, + local_expansion_class: type[LocalExpansionBase] + ) -> type[M2LTranslationBase]: + from sumpy.expansion.local import ( + FourierBesselLocalExpansionMixin, + VolumeTaylorLocalExpansionBase, + ) + if issubclass(local_expansion_class, VolumeTaylorLocalExpansionBase): + return VolumeTaylorM2LTranslation + elif issubclass(local_expansion_class, FourierBesselLocalExpansionMixin): + return FourierBesselM2LTranslation + else: + raise RuntimeError( + f"unknown local_expansion_class: {local_expansion_class}") + + +class FFTM2LTranslationClassFactory(M2LTranslationClassFactoryBase): + """An implementation of :class:`M2LTranslationClassFactoryBase` that uses + FFT M2L translation class. + """ + + @override + def get_m2l_translation_class( + self, + base_kernel: ScalarKernel, + local_expansion_class: type[LocalExpansionBase] + ) -> type[M2LTranslationBase]: + from sumpy.expansion.local import ( + FourierBesselLocalExpansionMixin, + VolumeTaylorLocalExpansionBase, + ) + if issubclass(local_expansion_class, VolumeTaylorLocalExpansionBase): + return VolumeTaylorM2LWithFFT + elif issubclass(local_expansion_class, FourierBesselLocalExpansionMixin): + return FourierBesselM2LWithFFT + else: + raise RuntimeError( + f"unknown local_expansion_class: {local_expansion_class}") + + +class DefaultM2LTranslationClassFactory(M2LTranslationClassFactoryBase): + """An implementation of :class:`M2LTranslationClassFactoryBase` that gives the + 'best known' translation type for each kernel and local expansion class. + """ + + @override + def get_m2l_translation_class( + self, + base_kernel: ScalarKernel, + local_expansion_class: type[LocalExpansionBase] + ) -> type[M2LTranslationBase]: + from sumpy.expansion.local import ( + FourierBesselLocalExpansionMixin, + VolumeTaylorLocalExpansionBase, + ) + if issubclass(local_expansion_class, VolumeTaylorLocalExpansionBase): + return VolumeTaylorM2LWithFFT + elif issubclass(local_expansion_class, FourierBesselLocalExpansionMixin): + return FourierBesselM2LTranslation + else: + raise RuntimeError( + f"unknown local_expansion_class: {local_expansion_class}") + +# }}} + + +# {{{ M2LTranslationBase + +TranslationClassesDepData: TypeAlias = tuple[sym.Expr, ...] +OptimizationCallable: TypeAlias = "Callable[[lp.TranslationUnit], lp.TranslationUnit]" + + +class M2LTranslationBase(ABC): + """Base class for Multipole to Local Translation + + .. automethod:: translate + .. automethod:: loopy_translate + .. automethod:: translation_classes_dependent_data + .. automethod:: translation_classes_dependent_ndata + .. automethod:: preprocess_multipole_exprs + .. automethod:: preprocess_multipole_nexprs + .. automethod:: postprocess_local_exprs + .. automethod:: postprocess_local_nexprs + .. autoattribute:: use_fft + .. autoattribute:: use_preprocessing + """ + + use_fft: ClassVar[bool] = False + use_preprocessing: ClassVar[bool] = False + + # Don't convert to frozen dataclass: plain (non-dc) subclasses wind up mutable. + @override + def __setattr__(self, name: str, value: object) -> None: + # These are intended to be stateless. + raise AttributeError( + f"{type(self)} is stateless and does not permit attribute modification") + + @override + def __eq__(self, other: object) -> bool: + return type(self) is type(other) + + @abstractmethod + def translate(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + translation_classes_dependent_data: + TranslationClassesDepData | None = None + ) -> Sequence[sym.Expr]: + ... + + def loopy_translate(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + ) -> lp.TranslationUnit: + raise NotImplementedError( + f"A direct loopy kernel for translation from " + f"{src_expansion} to {tgt_expansion} using {self} is not implemented.") + + def translation_classes_dependent_data(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_rscale: sym.Expr, + dvec: sym.Matrix, + sac: SymbolicAssignmentCollection | None = None, + ) -> TranslationClassesDepData: + """Return an iterable of expressions that needs to be precomputed + for multipole-to-local translations that depend only on the + distance between the multipole center and the local center which + is given as *dvec*. + + Since there are only a finite number of different values for the + distance between per level, these can be precomputed for the tree. + In :mod:`boxtree`, these distances are referred to as translation + classes. + + When FFT is turned on, the output expressions are assumed to be + transformed into Fourier space at the end by the caller. + """ + return () + + def translation_classes_dependent_ndata(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + ) -> int: + """Return the number of expressions returned by + :func:`~sumpy.expansion.m2l.M2LTranslationBase.translation_classes_dependent_data`. + This method exists because calculating the number of expressions using + the above method might be costly and + :func:`~sumpy.expansion.m2l.M2LTranslationBase.translation_classes_dependent_data` + cannot be memoized due to it having side effects through the argument + *sac*. + """ + return 0 + + def loopy_translation_classes_dependent_data(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + result_dtype: DTypeLike) -> lp.TranslationUnit: + """ + :arg result_dtype: the :mod:`numpy` type of the result. + :returns: a :mod:`loopy` kernel that calculates the data described by + :func:`~sumpy.expansion.m2l.M2LTranslationBase.translation_classes_dependent_data`. + """ + return loopy_translation_classes_dependent_data( + tgt_expansion, src_expansion, result_dtype + ) + + @abstractmethod + def preprocess_multipole_exprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + sac: SymbolicAssignmentCollection | None, + src_rscale: sym.Expr, + ) -> Sequence[sym.Expr]: + """Preprocess the multipole expansion for an optimized M2L. + + Preprocessing happens once per source box before M2L translation is done. + + These expressions are used in a separate :mod:`loopy` kernel to avoid + having to process for each target and source box pair. When FFT is + turned on, the output expressions are assumed to be transformed into + Fourier space at the end by the caller. When FFT is turned off, the + output expressions are equal to the multipole expansion coefficients + with zeros added to make the M2L computation a circulant matrix. + """ + + def preprocess_multipole_nexprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + ) -> int: + """Return the number of expressions returned by + :func:`~sumpy.expansion.m2l.M2LTranslationBase.preprocess_multipole_exprs`. + + This method exists because calculating the number of expressions using + the above method might be costly and it cannot be memoized due to it having + side effects through the argument *sac*. + """ + # For all use-cases we have right now, this is equal to the number of + # translation classes dependent exprs. Use that as a default. + return self.translation_classes_dependent_ndata(tgt_expansion, src_expansion) + + @abstractmethod + def postprocess_local_exprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + m2l_result: Sequence[sym.Expr], + src_rscale: sym.Expr, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None) -> Sequence[sym.Expr]: + """Postprocess the local expansion for an optimized M2L. + + Postprocessing happens once per target box just after the M2L translation + is done and before storing the expansion coefficients for the local + expansion. + + When FFT is turned on, the output expressions are assumed to have been + transformed from Fourier space back to the original space by the caller. + """ + + def postprocess_local_nexprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase + ) -> int: + """Return the number of expressions given as input to + :func:`~sumpy.expansion.m2l.M2LTranslationBase.postprocess_local_exprs`. + + This method exists because calculating the number of expressions using + the above method might be costly and it cannot be memoized due to it + having side effects through the argument *sac*. + """ + # For all use-cases we have right now, this is equal to the number of + # translation classes dependent exprs. Use that as a default. + return self.translation_classes_dependent_ndata(tgt_expansion, src_expansion) + + def update_persistent_hash(self, key_hash: Hash, key_builder: KeyBuilder) -> None: + key_hash.update(type(self).__name__.encode("utf8")) + + def optimize_loopy_kernel(self, + knl: lp.TranslationUnit, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + ) -> lp.TranslationUnit: + return lp.tag_inames(knl, {"itgt_box": "g.0"}) + + +# }}} M2LTranslationBase + +# {{{ VolumeTaylorM2LTranslation + +class VolumeTaylorM2LTranslation(M2LTranslationBase): + @override + def translate(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + translation_classes_dependent_data: ( + TranslationClassesDepData | None) = None) -> Sequence[sym.Expr]: + if translation_classes_dependent_data: + derivatives = translation_classes_dependent_data + else: + derivatives = self.translation_classes_dependent_data( + tgt_expansion, src_expansion, src_rscale, dvec, sac=sac) + + src_coeff_exprs = self.preprocess_multipole_exprs( + tgt_expansion, src_expansion, src_coeff_exprs, sac, src_rscale) + + # Returns a big symbolic sum of matrix entries + # (FIXME? Though this is just the correctness-checking + # fallback for the FFT anyhow) + result = matvec_toeplitz_upper_triangular(src_coeff_exprs, derivatives) + return self.postprocess_local_exprs(tgt_expansion, src_expansion, + result, src_rscale, tgt_rscale, sac) + + @override + def translation_classes_dependent_ndata(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + ) -> int: + """Returns number of expressions in M2L global precomputation step. + """ + mis_with_dummy_rows, _, _ = self._translation_classes_dependent_data_mis( + tgt_expansion, src_expansion) + + return len(mis_with_dummy_rows) + + def _translation_classes_dependent_data_mis(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + ) -> tuple[Sequence[MultiIndex], Sequence[MultiIndex], MultiIndex]: + """We would like to compute the M2L by way of a circulant matrix below. + To get the matrix representing the M2L into circulant form, a certain + numbering of rows and columns (as identified by multi-indices) is + required. This routine returns that numbering. + + .. note:: + + The set of multi-indices returned may be a superset of the + coefficients used by the expansion. On the input end, those + coefficients are taken as zero. On output, they are simply + dropped from the computed result. + + This method returns the multi-indices representing the rows + of the circulant matrix, the multi-indices representing the rows + of the M2L translation matrix and the maximum multi-index of the + latter. + """ + from pytools import generate_nonnegative_integer_tuples_below as gnitb + + from sumpy.tools import add_mi + + dim = tgt_expansion.dim + # max_mi is the multi-index which is the sum of the + # element-wise maximum of source multi-indices and the + # element-wise maximum of target multi-indices. + max_mi = [0]*dim + for i in range(dim): + max_mi[i] = max(mi[i] for mi in src_expansion.get_coefficient_identifiers()) + max_mi[i] += max(mi[i] for mi in tgt_expansion.get_coefficient_identifiers()) # ruff:ignore[line-too-long] + + # These are the multi-indices representing the rows + # in the circulant matrix. Note that to get the circulant + # matrix structure some multi-indices that are not in the + # M2L translation matrix are added. + # This corresponds to adding O(p^(d-1)) + # additional rows and columns in the case of some PDEs + # like Laplace and O(p^d) in other cases. + circulant_matrix_mis = list(gnitb([m + 1 for m in max_mi])) + + # These are the multi-indices representing the rows + # in the M2L translation matrix without the additional + # multi-indices in the circulant matrix + needed_vector_terms: set[MultiIndex] = set() + + # For eg: 2D full Taylor Laplace, we only need kernel derivatives + # (n1+n2, m1+m2), n1+m1<=p, n2+m2<=p + for tgt_deriv in tgt_expansion.get_coefficient_identifiers(): + for src_deriv in src_expansion.get_coefficient_identifiers(): + needed = add_mi(src_deriv, tgt_deriv) + if needed not in needed_vector_terms: + needed_vector_terms.add(needed) + + return circulant_matrix_mis, tuple(needed_vector_terms), tuple(max_mi) + + @override + def translation_classes_dependent_data(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_rscale: sym.Expr, + dvec: sym.Matrix, + sac: SymbolicAssignmentCollection | None = None, + ) -> TranslationClassesDepData: + assert isinstance(tgt_expansion, VolumeTaylorLocalExpansionBase) + assert isinstance(src_expansion, VolumeTaylorMultipoleExpansionBase) + + # We know the general form of the multipole expansion is: + # + # coeff0 * diff(kernel(src - c1), mi0) + + # coeff1 * diff(kernel(src - c1), mi1) + ... + # + # To get the local expansion coefficients, we take derivatives of + # the multipole expansion. For eg: the coefficient w.r.t mir is + # + # coeff0 * diff(kernel(c2 - c1), mi0 + mir) + + # coeff1 * diff(kernel(c2 - c1), mi1 + mir) + ... + # + # The derivatives above depends only on `c2 - c1` and can be precomputed + # globally as there are only a finite number of values for `c2 - c1` for + # m2l. + + if not tgt_expansion.use_rscale: + src_rscale = sym.sympify(1) + + circulant_matrix_mis, needed_vector_terms, max_mi = ( + self._translation_classes_dependent_data_mis(tgt_expansion, + src_expansion)) + + circulant_matrix_ident_to_index = { + ident: i for i, ident in enumerate(circulant_matrix_mis)} + + # Create a expansion terms wrangler for derivatives up to order + # (tgt order)+(src order) including a corresponding reduction matrix + # For eg: 2D full Taylor Laplace, this is (n, m), + # n+m<=2*p, n<=2*p, m<=2*p + srcplusderiv_terms_wrangler = ( + src_expansion.expansion_terms_wrangler.copy( + order=tgt_expansion.order + src_expansion.order, + max_mi=tuple(max_mi))) + srcplusderiv_full_coeff_ids = ( + srcplusderiv_terms_wrangler.get_full_coefficient_identifiers()) + srcplusderiv_ident_to_index = { + ident: i for i, ident in enumerate(srcplusderiv_full_coeff_ids)} + + # The vector has the kernel derivatives and depends only on the distance + # between the two centers + taker = src_expansion.kernel.get_derivative_taker(dvec, src_rscale, sac) + vector_stored: list[sym.Expr] = [] + + # Calculate the kernel derivatives for the compressed set + for term in srcplusderiv_terms_wrangler.get_coefficient_identifiers(): + kernel_deriv = taker.diff(term) + vector_stored.append(kernel_deriv) + + # Calculate the kernel derivatives for the full set + vector_full = ( + srcplusderiv_terms_wrangler.get_full_kernel_derivatives_from_stored( + vector_stored, src_rscale)) + + for term in srcplusderiv_full_coeff_ids: + assert term in needed_vector_terms + + vector: list[sym.Expr] = [sym.sympify(0)] * len(needed_vector_terms) + for i, term in enumerate(needed_vector_terms): + vector[i] = add_to_sac(sac, vector_full[srcplusderiv_ident_to_index[term]]) + + # Add zero values needed to make the translation matrix circulant + derivatives_full: list[sym.Expr] = [sym.sympify(0)] * len(circulant_matrix_mis) + for expr, mi in zip(vector, needed_vector_terms, strict=True): + derivatives_full[circulant_matrix_ident_to_index[mi]] = expr + + return tuple(derivatives_full) + + @override + def preprocess_multipole_exprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + sac: SymbolicAssignmentCollection | None, + src_rscale: sym.Expr) -> Sequence[sym.Expr]: + circulant_matrix_mis, _, _ = self._translation_classes_dependent_data_mis( + tgt_expansion, src_expansion) + circulant_matrix_ident_to_index = { + ident: i for i, ident in enumerate(circulant_matrix_mis)} + + # Calculate the input vector for the circulant matrix + input_vector: list[sym.Expr] = [sym.sympify(0)] * len(circulant_matrix_mis) + for coeff, term in zip( + src_coeff_exprs, + src_expansion.get_coefficient_identifiers(), strict=True): + input_vector[circulant_matrix_ident_to_index[term]] = add_to_sac(sac, coeff) + + return input_vector + + @override + def preprocess_multipole_nexprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase) -> int: + circulant_matrix_mis, _, _ = self._translation_classes_dependent_data_mis( + tgt_expansion, src_expansion) + + return len(circulant_matrix_mis) + + def loopy_preprocess_multipole(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + result_dtype: DTypeLike, + ) -> tuple[lp.TranslationUnit, Sequence[OptimizationCallable]]: + assert isinstance(tgt_expansion, VolumeTaylorLocalExpansionBase) + assert isinstance(src_expansion, VolumeTaylorMultipoleExpansionBase) + + _, _, max_mi = self._translation_classes_dependent_data_mis( + tgt_expansion, src_expansion) + + ncoeff_src = len(src_expansion.get_coefficient_identifiers()) + ncoeff_preprocessed = self.preprocess_multipole_nexprs( + tgt_expansion, src_expansion) + order = src_expansion.order + + output_coeffs = p.Variable("output_coeffs") + input_coeffs = p.Variable("input_coeffs") + output_icoeff = p.Variable("output_icoeff") + input_icoeff = p.Variable("input_icoeff") + input_coeffs_copy = p.Variable("input_coeffs_copy") + + dim = tgt_expansion.dim + v = tuple(p.Variable(f"x{i}") for i in range(dim)) + + from sumpy.expansion import ( + FullExpansionTermsWrangler, + LinearPDEBasedExpansionTermsWrangler, + ) + + wrangler = src_expansion.expansion_terms_wrangler + assert isinstance(wrangler, (FullExpansionTermsWrangler, + LinearPDEBasedExpansionTermsWrangler)) + + _, axis_permutation = wrangler._get_mi_ordering_key_and_axis_permutation() + slowest_idx = axis_permutation[0] + + # max_mi[slowest_idx] = 2*(c - 1) + c = max_mi[slowest_idx] // 2 + 1 + noutput_coeffs = cast("int", c * (2*order + 1) ** (dim - 1)) + + domains = [ + "{[output_icoeff]: 0<=output_icoeff 0 else idx + insns.append(lp.Assignment( + assignee=v[i], + expression=new_idx, + id=f"set_x{i}", + temp_var_type=lp.Optional(None), + )) + idx = idx // (max_mi[i] + 1) + + input_idx = wrangler.get_storage_index(v) + output_idx = 0 + mult = 1 + for i in range(dim - 1, -1, -1): + output_idx += mult*v[i] + mult *= (max_mi[i] + 1) + + insns += [ + lp.Assignment( + assignee=output_coeffs[output_icoeff], + expression=input_coeffs_copy[input_idx], + predicates=frozenset([ + p.Comparison(sum(v), "<=", order), + p.Comparison(v[slowest_idx], "<", c), + ]), + happens_after=frozenset( + [f"set_x{i}" for i in range(dim)] + ["input_copy"] + ), + ) + ] + + knl = lp.make_function(domains, insns, + kernel_data=[ + lp.ValueArg("src_rscale", None), + lp.GlobalArg("output_coeffs", result_dtype, shape=ncoeff_preprocessed, + is_input=False, is_output=True), + lp.GlobalArg("input_coeffs", None, shape=ncoeff_src), + ...], + name="m2l_preprocess_inner", + lang_version=lp.MOST_RECENT_LANGUAGE_VERSION, + fixed_parameters={"noutput_coeffs": noutput_coeffs, + "ninput_coeffs": ncoeff_src}, + ) + + from functools import partial + optimizations = [ + partial(lp.split_iname, split_iname="m2l__input_icoeff", + inner_length=32, inner_tag="l.0"), + partial(lp.split_iname, split_iname="m2l__output_icoeff", + inner_length=32, inner_tag="l.0"), + ] + + return knl, optimizations + + @override + def postprocess_local_exprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + m2l_result: Sequence[sym.Expr], + src_rscale: sym.Expr, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> Sequence[sym.Expr]: + circulant_matrix_mis, _, _ = self._translation_classes_dependent_data_mis( + tgt_expansion, src_expansion) + circulant_matrix_ident_to_index = { + ident: i for i, ident in enumerate(circulant_matrix_mis)} + + # Filter out the dummy rows and scale them for target + rscale_ratio = add_to_sac(sac, tgt_rscale/src_rscale) + return [ + m2l_result[circulant_matrix_ident_to_index[term]] + * rscale_ratio**sum(term) + for term in tgt_expansion.get_coefficient_identifiers()] + + @override + def postprocess_local_nexprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + ) -> int: + return self.translation_classes_dependent_ndata(tgt_expansion, src_expansion) + + def loopy_postprocess_local(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + result_dtype: DTypeLike, + ) -> tuple[lp.TranslationUnit, Sequence[OptimizationCallable]]: + circulant_matrix_mis, _, _ = self._translation_classes_dependent_data_mis( + tgt_expansion, src_expansion) + circulant_matrix_ident_to_index = { + ident: i for i, ident in enumerate(circulant_matrix_mis)} + + ncoeff_tgt = len(tgt_expansion.get_coefficient_identifiers()) + ncoeff_before_postprocessed = self.postprocess_local_nexprs( + tgt_expansion, src_expansion) + order = tgt_expansion.order + + fixed_parameters = { + "ncoeff_tgt": ncoeff_tgt, + "ncoeff_before_postprocessed": ncoeff_before_postprocessed, + "order": order, + } + + domains = [ + "{[iorder]: 0 rscale_ratio = tgt_rscale / src_rscale {id=rscale_ratio}"] + + rscale_arr = p.Variable("rscale_arr") + rscale_ratio = p.Variable("rscale_ratio") + iorder = p.Variable("iorder") + + insns += [ + lp.Assignment( + assignee=rscale_arr[0], + expression=1, + id="rscale_arr0", + happens_after=frozenset(["rscale_ratio"]), + ), + lp.Assignment( + assignee=rscale_arr[iorder], + expression=rscale_arr[iorder - 1]*rscale_ratio, + id="rscale_arr", + happens_after=frozenset(["rscale_arr0"]), + ), + ] + + if self.use_fft and result_dtype in (np.float64, np.float32): + result_func = p.Variable("real") + else: + def result_func(x: ArithmeticExpression) -> ArithmeticExpression: + return x + + output_coeffs = p.Variable("output_coeffs") + input_coeffs = p.Variable("input_coeffs") + src_idx_sym = p.Variable("src_idx") + rscale_idx_arr_sym = p.Variable("rscale_idx_arr") + output_icoeff_sym = p.Variable("output_icoeff") + + src_idx = np.full(ncoeff_tgt, -1, dtype=np.int32) + for output_icoeff, term in enumerate( + tgt_expansion.get_coefficient_identifiers()): + if self.use_fft: + # since we reversed the M2L matrix, we reverse the result + # to get the correct result + n = len(circulant_matrix_mis) + input_icoeff = n - 1 - circulant_matrix_ident_to_index[term] + else: + input_icoeff = circulant_matrix_ident_to_index[term] + src_idx[output_icoeff] = input_icoeff + + rscale_idx_arr = np.full(ncoeff_tgt, -1, dtype=np.int32) + for output_icoeff, term in enumerate( + tgt_expansion.get_coefficient_identifiers()): + rscale_idx_arr[output_icoeff] = sum(term) + + insns += [ + lp.Assignment( + assignee=output_coeffs[output_icoeff_sym], + expression=( + result_func(input_coeffs[src_idx_sym[output_icoeff_sym]]) + * rscale_arr[rscale_idx_arr_sym[output_icoeff_sym]]), + id="coeff_insn", + happens_after=frozenset(["rscale_arr"]), + ) + ] + + domains += [ + "{[output_icoeff]: 0<=output_icoeff Sequence[sym.Expr]: + assert translation_classes_dependent_data + + derivatives = translation_classes_dependent_data + # Returns a big symbolic sum of matrix entries + # (FIXME? Though this is just the correctness-checking + # fallback for the FFT anyhow) + return matvec_toeplitz_upper_triangular(src_coeff_exprs, derivatives) + + @override + def loopy_translate(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase) -> lp.TranslationUnit: + ncoeff_src = self.preprocess_multipole_nexprs(tgt_expansion, src_expansion) + ncoeff_tgt = self.postprocess_local_nexprs(tgt_expansion, src_expansion) + + icoeff_src = p.Variable("icoeff_src") + icoeff_tgt = p.Variable("icoeff_tgt") + domains = [f"{{[icoeff_tgt]: 0<=icoeff_tgt<{ncoeff_tgt} }}"] + + tgt_coeffs = p.Variable("tgt_coeffs") + src_coeffs = p.Variable("src_coeffs") + translation_classes_dependent_data = p.Variable("data") + + if self.use_fft: + expr = src_coeffs[icoeff_tgt]*translation_classes_dependent_data[icoeff_tgt] + else: + toeplitz_first_row = src_coeffs[icoeff_src-icoeff_tgt] + vector = translation_classes_dependent_data[icoeff_src] + expr = toeplitz_first_row * vector + + domains.append(f"{{[icoeff_src]: icoeff_tgt<=icoeff_src<{ncoeff_src} }}") + + expr = src_coeffs[icoeff_tgt] * translation_classes_dependent_data[icoeff_tgt] + + insns = [ + lp.Assignment( + assignee=tgt_coeffs[icoeff_tgt], + expression=tgt_coeffs[icoeff_tgt] + expr + ), + ] + + return lp.make_function( + domains, + insns, + kernel_data=[ + lp.GlobalArg("tgt_coeffs", + shape=lp.auto, is_input=True, is_output=True), + lp.GlobalArg("src_coeffs, data", + shape=lp.auto, is_input=True, is_output=False), + lp.ValueArg("src_rscale, tgt_rscale", is_input=True), + ...], + name="e2e", + lang_version=lp.MOST_RECENT_LANGUAGE_VERSION, + ) + + +# }}} VolumeTaylorM2LWithPreprocessedMultipoles + + +# {{{ VolumeTaylorM2LWithFFT + +class VolumeTaylorM2LWithFFT(VolumeTaylorM2LWithPreprocessedMultipoles): + use_fft: ClassVar[bool] = True + + @override + def translate(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + translation_classes_dependent_data: ( + TranslationClassesDepData | None) = None, + ) -> Sequence[sym.Expr]: + assert translation_classes_dependent_data + + derivatives = translation_classes_dependent_data + assert len(src_coeff_exprs) == len(derivatives) + + return [a*b for a, b in zip(derivatives, src_coeff_exprs, strict=True)] + + @override + def translation_classes_dependent_data(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_rscale: sym.Expr, + dvec: sym.Matrix, + sac: SymbolicAssignmentCollection | None = None, + ) -> TranslationClassesDepData: + """Return an iterable of expressions that needs to be precomputed + for multipole-to-local translations that depend only on the + distance between the multipole center and the local center which + is given as *dvec*. + + The final result should be transformed using an FFT. + """ + derivatives_full = super().translation_classes_dependent_data( + tgt_expansion, src_expansion, src_rscale, dvec, sac) + + # Note that the matrix we have now is a mirror image of a + # circulant matrix. We reverse the first column to get the + # first column for the circulant matrix and then finally + # use the FFT for convolution represented by the circulant + # matrix. + return tuple(reversed(derivatives_full)) + + @override + def postprocess_local_exprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + m2l_result: Sequence[sym.Expr], + src_rscale: sym.Expr, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> Sequence[sym.Expr]: + circulant_matrix_mis, _, _ = self._translation_classes_dependent_data_mis( + tgt_expansion, src_expansion) + n = len(circulant_matrix_mis) + + # since we reversed the M2L matrix, we reverse the result + # to get the correct result + m2l_result = list(reversed(m2l_result[:n])) + + return super().postprocess_local_exprs(tgt_expansion, + src_expansion, m2l_result, src_rscale, tgt_rscale, sac) + + @override + def optimize_loopy_kernel(self, + knl: lp.TranslationUnit, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + ) -> lp.TranslationUnit: + # Transform the kernel so that icoeff_tgt and its duplicates + # become the outermost iname + inames = knl.default_entrypoint.all_inames() + knl = lp.rename_inames(knl, + [iname for iname in inames if "icoeff_tgt" in iname], + "icoeff_tgt", existing_ok=True) + knl = lp.add_inames_to_insn(knl, "icoeff_tgt", None) + + # unprivatize icoeff_tgt because it is the outermost iname + knl = lp.unprivatize_temporaries_with_inames( + knl, + frozenset({"icoeff_tgt"}), frozenset({"tgt_expansion"})) + + knl = lp.split_iname(knl, "icoeff_tgt", 64, inner_iname="inner", + inner_tag="l.0", outer_tag="g.1") + return lp.tag_inames(knl, {"itgt_box": "g.0"}) + + +# }}} VolumeTaylorM2LWithFFT + +# {{{ FourierBesselM2LTranslation + +class FourierBesselM2LTranslation(M2LTranslationBase): + @override + def translate(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + translation_classes_dependent_data: ( + TranslationClassesDepData | None) = None) -> Sequence[sym.Expr]: + if translation_classes_dependent_data is None: + derivatives = self.translation_classes_dependent_data(tgt_expansion, + src_expansion, src_rscale, dvec, sac=sac) + else: + derivatives = translation_classes_dependent_data + + src_coeff_exprs = self.preprocess_multipole_exprs(tgt_expansion, + src_expansion, src_coeff_exprs, sac, src_rscale) + + translated_coeffs = [ + sum((derivatives[m + j + tgt_expansion.order + src_expansion.order] + * src_coeff_exprs[src_expansion.get_storage_index((m,))] + for m, in src_expansion.get_coefficient_identifiers()), + sym.sympify(0)) + for j, in tgt_expansion.get_coefficient_identifiers()] + + return self.postprocess_local_exprs(tgt_expansion, + src_expansion, translated_coeffs, src_rscale, tgt_rscale, + sac) + + @override + def translation_classes_dependent_ndata(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + ) -> int: + return 2 * tgt_expansion.order + 2 * src_expansion.order + 1 + + @override + def translation_classes_dependent_data(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_rscale: sym.Expr, + dvec: sym.Matrix, + sac: SymbolicAssignmentCollection | None = None, + ) -> TranslationClassesDepData: + assert isinstance(tgt_expansion, FourierBesselLocalExpansionMixin) + assert isinstance(src_expansion, HankelBased2DMultipoleExpansion) + + dvec_len = sym.sym_real_norm_2(dvec) + new_center_angle_rel_old_center = sym.atan2(dvec[1], dvec[0]) + arg_scale = tgt_expansion.get_bessel_arg_scaling() + + # [-(src_order+tgt_order), ..., 0, ..., (src_order + tgt_order)] + translation_classes_dependent_data: list[sym.Expr] = ( + [sym.sympify(0)] * (2*tgt_expansion.order + 2 * src_expansion.order + 1)) + + # The M2L is a mirror image of a Toeplitz matvec with Hankel function + # evaluations. https://dlmf.nist.gov/10.23.F1 + # This loop computes the first row and the last column vector sufficient + # to specify the matrix entries. + for j, in tgt_expansion.get_coefficient_identifiers(): + idx_j = tgt_expansion.get_storage_index((j,)) + for m, in src_expansion.get_coefficient_identifiers(): + idx_m = src_expansion.get_storage_index((m,)) + translation_classes_dependent_data[idx_j + idx_m] = ( + sym.Hankel1(m + j, arg_scale * dvec_len, 0) + * sym.exp(sym.I * (m + j) * new_center_angle_rel_old_center)) + + return tuple(translation_classes_dependent_data) + + @override + def preprocess_multipole_exprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + sac: SymbolicAssignmentCollection | None, + src_rscale: sym.Expr) -> Sequence[sym.Expr]: + src_coeff_exprs = list(src_coeff_exprs) + for m, in src_expansion.get_coefficient_identifiers(): + src_coeff_exprs[src_expansion.get_storage_index((m,))] *= src_rscale**abs(m) + + return src_coeff_exprs + + @override + def preprocess_multipole_nexprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase) -> int: + return 2*src_expansion.order + 1 + + @override + def postprocess_local_exprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + m2l_result: Sequence[sym.Expr], + src_rscale: sym.Expr, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None) -> Sequence[sym.Expr]: + # Filter out the dummy rows and scale them for target + result: list[sym.Expr] = [] + for j, in tgt_expansion.get_coefficient_identifiers(): + result.append( + m2l_result[tgt_expansion.get_storage_index((j,))] + * tgt_rscale**(abs(j)) * sym.Integer(-1)**j) + + return result + + @override + def postprocess_local_nexprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase) -> int: + return 2*tgt_expansion.order + 1 + +# }}} FourierBesselM2LTranslation + + +# {{{ FourierBesselM2LWithPreprocessedMultipoles + +class FourierBesselM2LWithPreprocessedMultipoles(FourierBesselM2LTranslation): + use_preprocessing: ClassVar[bool] = True + + @override + def translate(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + translation_classes_dependent_data: ( + TranslationClassesDepData | None) = None) -> Sequence[sym.Expr]: + assert translation_classes_dependent_data + derivatives = translation_classes_dependent_data + + return [ + sum((derivatives[m + j + tgt_expansion.order + src_expansion.order] + * src_coeff_exprs[src_expansion.get_storage_index((m,))] + for m, in src_expansion.get_coefficient_identifiers()), + sym.sympify(0)) + for j, in tgt_expansion.get_coefficient_identifiers() + ] + + @override + def loopy_translate(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase) -> lp.TranslationUnit: + ncoeff_src = self.preprocess_multipole_nexprs(tgt_expansion, src_expansion) + ncoeff_tgt = self.postprocess_local_nexprs(tgt_expansion, src_expansion) + + icoeff_src = p.Variable("icoeff_src") + icoeff_tgt = p.Variable("icoeff_tgt") + domains = [f"{{[icoeff_tgt]: 0<=icoeff_tgt<{ncoeff_tgt} }}"] + + tgt_coeffs = p.Variable("tgt_coeffs") + src_coeffs = p.Variable("src_coeffs") + translation_classes_dependent_data = p.Variable("data") + + if self.use_fft: + expr = (src_coeffs[icoeff_tgt] + * translation_classes_dependent_data[icoeff_tgt]) + else: + expr = (src_coeffs[icoeff_src] + * translation_classes_dependent_data[icoeff_tgt + icoeff_src]) + domains.append(f"{{[icoeff_src]: 0<=icoeff_src<{ncoeff_src} }}") + + insns = [ + lp.Assignment( + assignee=tgt_coeffs[icoeff_tgt], + expression=tgt_coeffs[icoeff_tgt] + expr), + ] + + return lp.make_function(domains, insns, + kernel_data=[ + lp.GlobalArg("tgt_coeffs", shape=lp.auto, is_input=True, + is_output=True), + lp.GlobalArg("src_coeffs, data", + shape=lp.auto, is_input=True, is_output=False), + lp.ValueArg("src_rscale, tgt_rscale", is_input=True), + ...], + name="e2e", + lang_version=lp.MOST_RECENT_LANGUAGE_VERSION, + ) + + +# }}} FourierBesselM2LWithPreprocessedMultipoles + + +# {{{ FourierBesselM2LWithFFT + +class FourierBesselM2LWithFFT(FourierBesselM2LWithPreprocessedMultipoles): + use_fft: ClassVar[bool] = True + + def __init__(self) -> None: + # FIXME: expansion with FFT is correct symbolically and can be verified + # with sympy. However there are numerical issues that we have to deal + # with. Greengard and Rokhlin 1988 attributes this to numerical + # instability but gives rscale as a possible solution. Sumpy's rscale + # choice is slightly different from Greengard and Rokhlin and that + # might be the reason for this numerical issue. + raise ValueError("Bessel based expansions with FFT are not supported yet.") + + @override + def translate(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + translation_classes_dependent_data: ( + TranslationClassesDepData | None) = None) -> Sequence[sym.Expr]: + assert translation_classes_dependent_data is not None + + derivatives = translation_classes_dependent_data + assert len(derivatives) == len(src_coeff_exprs) + + return [a * b for a, b in zip(derivatives, src_coeff_exprs, strict=True)] + + @override + def loopy_translate(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase) -> lp.TranslationUnit: + raise NotImplementedError + + @override + def translation_classes_dependent_data(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_rscale: sym.Expr, + dvec: sym.Matrix, + sac: SymbolicAssignmentCollection | None = None, + ) -> TranslationClassesDepData: + translation_classes_dependent_data = ( + super().translation_classes_dependent_data( + tgt_expansion, src_expansion, src_rscale, dvec, sac)) + order = src_expansion.order + + # For this expansion, we have a mirror image of a Toeplitz matrix. + # First, we have to take the mirror image of the M2L matrix. + # + # After that the Toeplitz matrix has to be embedded in a circulant + # matrix. In this cicrcular matrix the first part of the first + # column is the first column of the Toeplitz matrix which is + # the last column of the M2L matrix. The second part is the + # reverse of the first row of the Toeplitz matrix which + # is the reverse of the first row of the M2L matrix. + first_row_m2l, last_column_m2l = ( + translation_classes_dependent_data[:2*order], + translation_classes_dependent_data[2*order:]) + + first_column_toeplitz = last_column_m2l + first_row_toeplitz = list(reversed(first_row_m2l)) + + first_column_circulant = ( + list(first_column_toeplitz) + + list(reversed(first_row_toeplitz))) + + return tuple(first_column_circulant) + + @override + def preprocess_multipole_exprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + sac: SymbolicAssignmentCollection | None, + src_rscale: sym.Expr) -> Sequence[sym.Expr]: + result = super().preprocess_multipole_exprs( + tgt_expansion, src_expansion, src_coeff_exprs, sac, src_rscale) + + result = list(reversed(result)) + result += [sym.sympify(0)] * (len(result) - 1) + + return result + + @override + def postprocess_local_exprs(self, + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + m2l_result: Sequence[sym.Expr], + src_rscale: sym.Expr, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None) -> Sequence[sym.Expr]: + m2l_result = m2l_result[:2*tgt_expansion.order + 1] + return super().postprocess_local_exprs( + tgt_expansion, src_expansion, m2l_result, src_rscale, tgt_rscale, sac) + +# }}} FourierBesselM2LWithFFT + + +# {{{ loopy_translation_classes_dependent_data + +def loopy_translation_classes_dependent_data( + tgt_expansion: LocalExpansionBase, + src_expansion: MultipoleExpansionBase, + result_dtype: DTypeLike) -> lp.TranslationUnit: + """ + This is a helper function to create a loopy kernel to generate translation + classes dependent data. This function uses symbolic expressions given by the + M2L translation, converts them to pymboltc expressions and generates a loopy + kernel. Note that the loopy kernel returned has lots of expressions in it and + takes a long time. Therefore, this function should be used only as a fallback + when there is no "loop-y" kernel to calculate the data. + """ + src_rscale = sym.Symbol("src_rscale") + dvec = sym.make_sym_vector("d", tgt_expansion.dim) + + from sumpy.assignment_collection import SymbolicAssignmentCollection + + sac = SymbolicAssignmentCollection() + derivatives = tgt_expansion.m2l_translation.translation_classes_dependent_data( + tgt_expansion, src_expansion, src_rscale, dvec, sac) + + vec_name = "m2l_translation_classes_dependent_data" + tgt_coeff_names = [ + sac.assign_unique(f"m2l_translation_classes_dependent_data{i}", coeff_i) + for i, coeff_i in enumerate(derivatives)] + sac = sac.run_global_cse() + + from sumpy.codegen import to_loopy_insns + from sumpy.tools import to_complex_dtype + + insns = to_loopy_insns( + sac.assignments.items(), + vector_names=frozenset(["d"]), + pymbolic_expr_maps=[tgt_expansion.get_code_transformer()], + retain_names=frozenset(tgt_coeff_names), + complex_dtype=to_complex_dtype(result_dtype), + ) + insns = list(insns) + + data = p.Variable("m2l_translation_classes_dependent_data") + happens_after = None + for i in range(len(insns)): + insn = insns[i] + if isinstance(insn, lp.Assignment) and \ + cast("p.Variable", insn.assignee).name.startswith(vec_name): + idx = int(cast("p.Variable", insn.assignee).name[len(vec_name):]) + insns[i] = lp.Assignment( + assignee=data[idx], + expression=insn.expression, + id=f"data_{idx}", + happens_after=happens_after, + ) + happens_after = frozenset([f"data_{idx}"]) + + return lp.make_function([], insns, + kernel_data=[ + lp.ValueArg("src_rscale", None), + lp.GlobalArg("d", None, shape=tgt_expansion.dim), + lp.GlobalArg(data.name, None, + shape=len(derivatives), is_input=False, + is_output=True), + ], + name="m2l_data", + lang_version=lp.MOST_RECENT_LANGUAGE_VERSION, + ) + + +# }}} loopy_translation_classes_dependent_data + +# vim: fdm=marker diff --git a/sumpy/expansion/multipole.py b/sumpy/expansion/multipole.py index fd5a3b1d1..a9f98ce7e 100644 --- a/sumpy/expansion/multipole.py +++ b/sumpy/expansion/multipole.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2012 Andreas Kloeckner" __license__ = """ @@ -20,19 +23,37 @@ THE SOFTWARE. """ +import logging import math +from abc import ABC, abstractmethod +from typing import TYPE_CHECKING + +from typing_extensions import override import sumpy.symbolic as sym from sumpy.expansion import ( - ExpansionBase, VolumeTaylorExpansion, LinearPDEConformingVolumeTaylorExpansion) -from sumpy.tools import mi_set_axis, add_to_sac, mi_power, mi_factorial + ExpansionBase, + LinearPDEConformingVolumeTaylorExpansion, + VolumeTaylorExpansion, + VolumeTaylorExpansionMixin, +) +from sumpy.tools import add_to_sac, mi_factorial, mi_power, mi_set_axis + + +if TYPE_CHECKING: + from collections.abc import Sequence + + from sumpy.assignment_collection import SymbolicAssignmentCollection + from sumpy.expansion.diff_op import MultiIndex + from sumpy.kernel import ScalarKernel + -import logging logger = logging.getLogger(__name__) __doc__ = """ +.. autoclass:: MultipoleExpansionBase .. autoclass:: VolumeTaylorMultipoleExpansion .. autoclass:: H2DMultipoleExpansion .. autoclass:: Y2DMultipoleExpansion @@ -40,19 +61,28 @@ """ -class MultipoleExpansionBase(ExpansionBase): +class MultipoleExpansionBase(ExpansionBase, ABC): pass # {{{ volume taylor -class VolumeTaylorMultipoleExpansionBase(MultipoleExpansionBase): +class VolumeTaylorMultipoleExpansionBase(VolumeTaylorExpansionMixin, + MultipoleExpansionBase, + ABC): """ Coefficients represent the terms in front of the kernel derivatives. """ - def coefficients_from_source_vec(self, kernels, avec, bvec, rscale, weights, - sac=None): + @override + def coefficients_from_source_vec(self, + kernels: Sequence[ScalarKernel], + avec: sym.Matrix, + bvec: sym.Matrix | None, + rscale: sym.Expr, + weights: Sequence[sym.Expr], + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: """This method calculates the full coefficients, sums them up and compresses them. This is more efficient that calculating full coefficients, compressing and then summing. @@ -60,61 +90,91 @@ def coefficients_from_source_vec(self, kernels, avec, bvec, rscale, weights, from sumpy.kernel import KernelWrapper if not self.use_rscale: - rscale = 1 + rscale = sym.sympify(1) - result = [0]*len(self.get_full_coefficient_identifiers()) - for kernel, weight in zip(kernels, weights): + mis = self.get_full_coefficient_identifiers() + n = len(mis) + + result: list[sym.Expr] = [sym.sympify(0)] * n + for kernel, weight in zip(kernels, weights, strict=True): if isinstance(kernel, KernelWrapper): coeffs = [ kernel.postprocess_at_source(mi_power(avec, mi), avec) / rscale ** sum(mi) - for mi in self.get_full_coefficient_identifiers()] + for mi in mis] else: avec_scaled = [sym.UnevaluatedExpr(a * rscale**-1) for a in avec] - coeffs = [mi_power(avec_scaled, mi) - for mi in self.get_full_coefficient_identifiers()] + coeffs = [mi_power(avec_scaled, mi) for mi in mis] - for i, mi in enumerate(self.get_full_coefficient_identifiers()): + for i, mi in enumerate(mis): result[i] += coeffs[i] * weight / mi_factorial(mi) - return ( - self.expansion_terms_wrangler.get_stored_mpole_coefficients_from_full( - result, rscale, sac=sac)) - def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): - return self.coefficients_from_source_vec((kernel,), avec, bvec, - rscale, (1,), sac=sac) - - def evaluate(self, kernel, coeffs, bvec, rscale, sac=None): - from sumpy.tools import DifferentiatedExprDerivativeTaker + return ( + self.expansion_terms_wrangler + .get_stored_mpole_coefficients_from_full(result, rscale, sac=sac)) + + @override + def coefficients_from_source( + self, + kernel: ScalarKernel, + avec: sym.Matrix, + bvec: sym.Matrix | None, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> Sequence[sym.Expr]: + return self.coefficients_from_source_vec( + (kernel,), + avec, + bvec, + rscale, (sym.sympify(1),), sac=sac) + + @override + def evaluate(self, + kernel: ScalarKernel, + coeffs: Sequence[sym.Expr], + bvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + ) -> sym.Expr: if not self.use_rscale: - rscale = 1 + rscale = sym.sympify(1) base_taker = kernel.get_derivative_taker(bvec, rscale, sac) - # Following is a no-op, but AxisTargetDerivative.postprocess_at_target and - # DirectionalTargetDerivative.postprocess_at_target only handles - # DifferentiatedExprDerivativeTaker and sympy expressions, so we need to - # make the taker a DifferentitatedExprDerivativeTaker instance. - base_taker = DifferentiatedExprDerivativeTaker(base_taker, - {tuple([0]*self.dim): 1}) + + # Following is a no-op, but AxisTargetDerivative.postprocess_at_target + # only handles DifferentiatedExprDerivativeTaker and sympy expressions, + # so we need to make the taker a DifferentitatedExprDerivativeTaker instance. + from sumpy.derivative_taker import DifferentiatedExprDerivativeTaker + base_taker = DifferentiatedExprDerivativeTaker( + base_taker, + {(0,)*self.dim: 1}) taker = kernel.postprocess_at_target(base_taker, bvec) - result = [] - for coeff, mi in zip(coeffs, self.get_coefficient_identifiers()): + result: list[sym.Expr] = [] + for coeff, mi in zip(coeffs, self.get_coefficient_identifiers(), strict=True): result.append(coeff * taker.diff(mi, lambda x: add_to_sac(sac, x))) - result = sym.Add(*tuple(result)) - return result - - def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale, sac=None, _fast_version=True): + return sym.Add(*tuple(result)) + + def translate_from(self, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None, + _fast_version: bool = True + ) -> Sequence[sym.Expr]: if not isinstance(src_expansion, type(self)): raise RuntimeError( f"do not know how to translate {type(src_expansion).__name__} to " "a Taylor multipole expansion") + src_coeff_exprs = list(src_coeff_exprs) + if not self.use_rscale: - src_rscale = 1 - tgt_rscale = 1 + src_rscale = sym.sympify(1) + tgt_rscale = sym.sympify(1) logger.info("building translation operator for %s: %s(%d) -> %s(%d): start", src_expansion.kernel, @@ -123,11 +183,12 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, type(self).__name__, self.order) - src_mi_to_index = {mi: i for i, mi in enumerate( - src_expansion.get_coefficient_identifiers())} - - tgt_mi_to_index = {mi: i for i, mi in enumerate( - self.get_full_coefficient_identifiers())} + src_mi_to_index = { + mi: i for i, mi in enumerate(src_expansion.get_coefficient_identifiers()) + } + tgt_mi_to_index = { + mi: i for i, mi in enumerate(self.get_full_coefficient_identifiers()) + } # This algorithm uses the observation that M2M coefficients # have the following form in 2D @@ -203,7 +264,7 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, # └───⬏ ↑ # └─────┘ # - # For the second hyperplane, data is propogated rightwards first + # For the second hyperplane, data is propagated rightwards first # and then upwards second which is opposite to that of the first # hyperplane. # @@ -224,19 +285,22 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, # the output from (n, 0) with the first dimension as the fastest # varying dimension. - tgt_hyperplanes = \ - self.expansion_terms_wrangler._split_coeffs_into_hyperplanes() - result = [0] * len(self.get_full_coefficient_identifiers()) + tgt_hyperplanes = ( + self.expansion_terms_wrangler._split_coeffs_into_hyperplanes()) + + tgt_mis = self.get_full_coefficient_identifiers() + n_tgt_mis = len(tgt_mis) # axis morally iterates over 'hyperplane directions' + result: list[sym.Expr] = [sym.sympify(0)] * n_tgt_mis for axis in range(self.dim): # {{{ index gymnastics # First, let's write source coefficients in target coefficient # indices. If target order is lower than source order, then # we will discard higher order terms from source coefficients. - cur_dim_input_coeffs = \ - [0] * len(self.get_full_coefficient_identifiers()) + cur_dim_input_coeffs: list[sym.Expr] = [sym.sympify(0)] * n_tgt_mis + for d, mis in tgt_hyperplanes: # Only consider hyperplanes perpendicular to *axis*. if d != axis: @@ -249,8 +313,9 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, src_idx = src_mi_to_index[mi] tgt_idx = tgt_mi_to_index[mi] - cur_dim_input_coeffs[tgt_idx] = src_coeff_exprs[src_idx] * \ - sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(mi) + cur_dim_input_coeffs[tgt_idx] = ( + src_coeff_exprs[src_idx] + * sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(mi)) if all(coeff == 0 for coeff in cur_dim_input_coeffs): continue @@ -261,53 +326,53 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, # As explained above using the unicode art, we use the orthogonal axis # as the last dimension to vary to reduce the number of operations. - dims = list(range(axis)) + \ - list(range(axis+1, self.dim)) + [axis] + dims = [*range(axis), *range(axis + 1, self.dim), axis] # d is the axis along which we translate. + cur_dim_output_coeffs = cur_dim_input_coeffs for d in dims: # We build the full target multipole and then compress it # at the very end. - cur_dim_output_coeffs = \ - [0] * len(self.get_full_coefficient_identifiers()) - for i, tgt_mi in enumerate( - self.get_full_coefficient_identifiers()): + cur_dim_output_coeffs: list[sym.Expr] = [sym.sympify(0)] * n_tgt_mis + for i, tgt_mi in enumerate(tgt_mis): # Calling this input_mis instead of src_mis because we # converted the source coefficients to target coefficient # indices beforehand. - for mi_i in range(tgt_mi[d]+1): + for mi_i in range(tgt_mi[d] + 1): input_mi = mi_set_axis(tgt_mi, d, mi_i) contrib = cur_dim_input_coeffs[tgt_mi_to_index[input_mi]] - for n, k, dist in zip(tgt_mi, input_mi, dvec): + + for n, k, dist in zip(tgt_mi, input_mi, dvec, strict=True): assert n >= k - contrib /= math.factorial(n-k) - contrib *= \ - sym.UnevaluatedExpr(dist/tgt_rscale)**(n-k) + contrib /= math.factorial(n - k) + contrib *= sym.UnevaluatedExpr(dist/tgt_rscale)**(n-k) cur_dim_output_coeffs[i] += contrib + # cur_dim_output_coeffs is the input in the next iteration cur_dim_input_coeffs = cur_dim_output_coeffs # }}} - for i in range(len(cur_dim_output_coeffs)): + for i in range(n_tgt_mis): result[i] += cur_dim_output_coeffs[i] # {{{ simpler, functionally equivalent code + if not _fast_version: - src_mi_to_index = {mi: i for i, mi in enumerate( - src_expansion.get_coefficient_identifiers())} - result = [0] * len(self.get_full_coefficient_identifiers()) + src_mi_to_index = { + mi: i + for i, mi in enumerate(src_expansion.get_coefficient_identifiers()) + } + result = [sym.sympify(0)] * n_tgt_mis for i, mi in enumerate(src_expansion.get_coefficient_identifiers()): src_coeff_exprs[i] *= mi_factorial(mi) from pytools import generate_nonnegative_integer_tuples_below as gnitb - for i, tgt_mi in enumerate( - self.get_full_coefficient_identifiers()): - + for i, tgt_mi in enumerate(tgt_mis): tgt_mi_plus_one = tuple(mi_i + 1 for mi_i in tgt_mi) for src_mi in gnitb(tgt_mi_plus_one): @@ -323,14 +388,17 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, n = tgt_mi[idim] k = src_mi[idim] assert n >= k - from sympy import binomial - contrib *= (binomial(n, k) - * sym.UnevaluatedExpr(dvec[idim]/tgt_rscale)**(n-k)) - result[i] += (contrib + contrib *= ( + math.comb(n, k) + * sym.UnevaluatedExpr(dvec[idim]/tgt_rscale)**(n-k)) + + result[i] += ( + contrib * sym.UnevaluatedExpr(src_rscale/tgt_rscale)**sum(src_mi)) result[i] /= mi_factorial(tgt_mi) + # }}} logger.info("building translation operator: done") @@ -342,74 +410,49 @@ def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, class VolumeTaylorMultipoleExpansion( VolumeTaylorExpansion, VolumeTaylorMultipoleExpansionBase): - - def __init__(self, kernel, order, use_rscale=None): - VolumeTaylorMultipoleExpansionBase.__init__(self, kernel, order, use_rscale) - VolumeTaylorExpansion.__init__(self, kernel, order, use_rscale) + pass class LinearPDEConformingVolumeTaylorMultipoleExpansion( LinearPDEConformingVolumeTaylorExpansion, VolumeTaylorMultipoleExpansionBase): - - def __init__(self, kernel, order, use_rscale=None): - VolumeTaylorMultipoleExpansionBase.__init__(self, kernel, order, use_rscale) - LinearPDEConformingVolumeTaylorExpansion.__init__( - self, kernel, order, use_rscale) - - -class LaplaceConformingVolumeTaylorMultipoleExpansion( - LinearPDEConformingVolumeTaylorMultipoleExpansion): - - def __init__(self, *args, **kwargs): - from warnings import warn - warn("LaplaceConformingVolumeTaylorMultipoleExpansion is deprecated. " - "Use LinearPDEConformingVolumeTaylorMultipoleExpansion instead.", - DeprecationWarning, stacklevel=2) - super().__init__(*args, **kwargs) - - -class HelmholtzConformingVolumeTaylorMultipoleExpansion( - LinearPDEConformingVolumeTaylorMultipoleExpansion): - - def __init__(self, *args, **kwargs): - from warnings import warn - warn("HelmholtzConformingVolumeTaylorMultipoleExpansion is deprecated. " - "Use LinearPDEConformingVolumeTaylorMultipoleExpansion instead.", - DeprecationWarning, stacklevel=2) - super().__init__(*args, **kwargs) - - -class BiharmonicConformingVolumeTaylorMultipoleExpansion( - LinearPDEConformingVolumeTaylorMultipoleExpansion): - - def __init__(self, *args, **kwargs): - from warnings import warn - warn("BiharmonicConformingVolumeTaylorMultipoleExpansion is deprecated. " - "Use LinearPDEConformingVolumeTaylorMultipoleExpansion instead.", - DeprecationWarning, stacklevel=2) - super().__init__(*args, **kwargs) + pass # }}} # {{{ 2D Hankel-based expansions -class _HankelBased2DMultipoleExpansion(MultipoleExpansionBase): - def get_storage_index(self, k): - return self.order+k - - def get_coefficient_identifiers(self): - return list(range(-self.order, self.order+1)) - - def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): +class HankelBased2DMultipoleExpansion(MultipoleExpansionBase, ABC): + @abstractmethod + def get_bessel_arg_scaling(self) -> sym.Expr: + ... + + @override + def get_storage_index(self, mi: MultiIndex) -> int: + ind, = mi + return self.order+ind + + @override + def get_coefficient_identifiers(self) -> Sequence[MultiIndex]: + return [(i,) for i in range(-self.order, self.order+1)] + + @override + def coefficients_from_source( + self, + kernel: ScalarKernel, + avec: sym.Matrix, + bvec: sym.Matrix | None, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: if not self.use_rscale: - rscale = 1 + rscale = sym.sympify(1) if kernel is None: kernel = self.kernel - from sumpy.symbolic import sym_real_norm_2, BesselJ + from sumpy.symbolic import BesselJ, sym_real_norm_2 avec_len = sym_real_norm_2(avec) arg_scale = self.get_bessel_arg_scaling() @@ -422,78 +465,106 @@ def coefficients_from_source(self, kernel, avec, bvec, rscale, sac=None): / rscale ** abs(c) * sym.exp(sym.I * c * -source_angle_rel_center), avec) - for c in self.get_coefficient_identifiers()] - - def evaluate(self, kernel, coeffs, bvec, rscale, sac=None): + for c, in self.get_coefficient_identifiers()] + + @override + def evaluate(self, + kernel: ScalarKernel, + coeffs: Sequence[sym.Expr], + bvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None + ) -> sym.Expr: if not self.use_rscale: - rscale = 1 + rscale = sym.sympify(1) - from sumpy.symbolic import sym_real_norm_2, Hankel1 + from sumpy.symbolic import Hankel1, sym_real_norm_2 bvec_len = sym_real_norm_2(bvec) target_angle_rel_center = sym.atan2(bvec[1], bvec[0]) arg_scale = self.get_bessel_arg_scaling() - return sum(coeffs[self.get_storage_index(c)] - * kernel.postprocess_at_target( - Hankel1(c, arg_scale * bvec_len, 0) - * rscale ** abs(c) - * sym.exp(sym.I * c * target_angle_rel_center), bvec) - for c in self.get_coefficient_identifiers()) - - def translate_from(self, src_expansion, src_coeff_exprs, src_rscale, - dvec, tgt_rscale, sac=None): + return sum((coeffs[self.get_storage_index((c,))] + * kernel.postprocess_at_target( + Hankel1(c, arg_scale * bvec_len, 0) + * rscale ** abs(c) + * sym.exp(sym.I * c * target_angle_rel_center), bvec) + for c, in self.get_coefficient_identifiers()), + sym.sympify(0)) + + def translate_from(self, + src_expansion: MultipoleExpansionBase, + src_coeff_exprs: Sequence[sym.Expr], + src_rscale: sym.Expr, + dvec: sym.Matrix, + tgt_rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None = None + ) -> Sequence[sym.Expr]: if not isinstance(src_expansion, type(self)): raise RuntimeError( "do not know how to translate " f"{type(src_expansion).__name__} to {type(self).__name__}") if not self.use_rscale: - src_rscale = 1 - tgt_rscale = 1 + src_rscale = sym.sympify(1) + tgt_rscale = sym.sympify(1) - from sumpy.symbolic import sym_real_norm_2, BesselJ + from sumpy.symbolic import BesselJ, sym_real_norm_2 dvec_len = sym_real_norm_2(dvec) new_center_angle_rel_old_center = sym.atan2(dvec[1], dvec[0]) arg_scale = self.get_bessel_arg_scaling() - translated_coeffs = [] - for j in self.get_coefficient_identifiers(): + translated_coeffs: list[sym.Expr] = [] + for j, in self.get_coefficient_identifiers(): translated_coeffs.append( - sum(src_coeff_exprs[src_expansion.get_storage_index(m)] - * BesselJ(m - j, arg_scale * dvec_len, 0) - * src_rscale ** abs(m) - / tgt_rscale ** abs(j) - * sym.exp(sym.I * (m - j) * new_center_angle_rel_old_center) - for m in src_expansion.get_coefficient_identifiers())) + sum((src_coeff_exprs[src_expansion.get_storage_index((m,))] + * BesselJ(m - j, arg_scale * dvec_len, 0) + * src_rscale ** abs(m) + / tgt_rscale ** abs(j) + * sym.exp(sym.I * (m - j) * new_center_angle_rel_old_center) + for m, in src_expansion.get_coefficient_identifiers()), + sym.sympify(0))) + return translated_coeffs -class H2DMultipoleExpansion(_HankelBased2DMultipoleExpansion): - def __init__(self, kernel, order, use_rscale=None): +class H2DMultipoleExpansion(HankelBased2DMultipoleExpansion): + def __post_init__(self): from sumpy.kernel import HelmholtzKernel - assert (isinstance(kernel.get_base_kernel(), HelmholtzKernel) - and kernel.dim == 2) - super().__init__( - kernel, order, use_rscale=use_rscale) + kernel = self.kernel.get_base_kernel() + if not (isinstance(kernel, HelmholtzKernel) and kernel.dim == 2): + raise TypeError( + f"{type(self).__name__} can only be applied to 2D HelmholtzKernel: " + f"{kernel!r}") + + @override + def get_bessel_arg_scaling(self) -> sym.Expr: + from sumpy.kernel import HelmholtzKernel + kernel = self.kernel.get_base_kernel() + assert isinstance(kernel, HelmholtzKernel) - def get_bessel_arg_scaling(self): - return sym.Symbol(self.kernel.get_base_kernel().helmholtz_k_name) + return sym.Symbol(kernel.helmholtz_k_name) -class Y2DMultipoleExpansion(_HankelBased2DMultipoleExpansion): - def __init__(self, kernel, order, use_rscale=None): +class Y2DMultipoleExpansion(HankelBased2DMultipoleExpansion): + def __post_init__(self): from sumpy.kernel import YukawaKernel - assert (isinstance(kernel.get_base_kernel(), YukawaKernel) - and kernel.dim == 2) - super().__init__( - kernel, order, use_rscale=use_rscale) + kernel = self.kernel.get_base_kernel() + if not (isinstance(kernel, YukawaKernel) and kernel.dim == 2): + raise TypeError( + f"{type(self).__name__} can only be applied to 2D YukawaKernel: " + f"{kernel!r}") + + @override + def get_bessel_arg_scaling(self) -> sym.Expr: + from sumpy.kernel import YukawaKernel + kernel = self.kernel.get_base_kernel() + assert isinstance(kernel, YukawaKernel) - def get_bessel_arg_scaling(self): - return sym.I * sym.Symbol(self.kernel.get_base_kernel().yukawa_lambda_name) + return sym.I * sym.Symbol(kernel.yukawa_lambda_name) # }}} diff --git a/sumpy/fmm.py b/sumpy/fmm.py index 828497197..12149303a 100644 --- a/sumpy/fmm.py +++ b/sumpy/fmm.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2013 Andreas Kloeckner" __license__ = """ @@ -23,25 +26,72 @@ __doc__ = """Integrates :mod:`boxtree` with :mod:`sumpy`. .. autoclass:: SumpyTreeIndependentDataForWrangler +.. autodata:: FMMLevelToOrder + :no-index: +.. class:: FMMLevelToOrder + + See above. + .. autoclass:: SumpyExpansionWrangler + +.. autoclass:: MultipoleExpansionFromOrderFactory +.. autoclass:: LocalExpansionFromOrderFactory """ +from collections.abc import Callable, Mapping +from typing import TYPE_CHECKING, Any, Protocol, TypeAlias -import pyopencl as cl -import pyopencl.array # noqa +import numpy as np +from boxtree.fmm import ExpansionWranglerInterface, TreeIndependentDataForWrangler +from boxtree.tree import Tree -from pytools import memoize_method -from boxtree.fmm import TreeIndependentDataForWrangler, ExpansionWranglerInterface +from pytools import memoize_in, memoize_method, obj_array from sumpy import ( - P2EFromSingleBox, P2EFromCSR, - E2PFromSingleBox, E2PFromCSR, - P2PFromCSR, - E2EFromCSR, M2LUsingTranslationClassesDependentData, - E2EFromChildren, E2EFromParent, - M2LGenerateTranslationClassesDependentData, - M2LPreprocessMultipole, M2LPostprocessLocal) -from sumpy.tools import to_complex_dtype + E2EFromChildren, + E2EFromCSR, + E2EFromParent, + E2PFromCSR, + E2PFromSingleBox, + M2LGenerateTranslationClassesDependentData, + M2LPostprocessLocal, + M2LPreprocessMultipole, + M2LUsingTranslationClassesDependentData, + P2EFromCSR, + P2EFromSingleBox, + P2PFromCSR, +) +from sumpy.kernel import ScalarKernel +from sumpy.tools import ( + get_opencl_fft_app, + run_opencl_fft, + to_complex_dtype, +) + + +if TYPE_CHECKING: + from collections.abc import Sequence + + from boxtree.traversal import FMMTraversalInfo + from numpy.typing import DTypeLike + + import pyopencl + from arraycontext import Array, ArrayContext + + from sumpy.expansion.local import LocalExpansionBase + from sumpy.expansion.multipole import MultipoleExpansionBase + + +class MultipoleExpansionFromOrderFactory(Protocol): + def __call__(self, + order: int, + *, use_rscale: bool = True) -> MultipoleExpansionBase: + ... + + +class LocalExpansionFromOrderFactory(Protocol): + def __call__(self, order: int, *, use_rscale: bool = True) -> LocalExpansionBase: + ... # {{{ tree-independent data for wrangler @@ -49,21 +99,27 @@ class SumpyTreeIndependentDataForWrangler(TreeIndependentDataForWrangler): """Objects of this type serve as a place to keep the code needed for :class:`SumpyExpansionWrangler`. Since :class:`SumpyExpansionWrangler` - necessarily must have a :class:`pyopencl.CommandQueue`, but this queue - is allowed to be more ephemeral than the code, the code's lifetime - is decoupled by storing it in this object. - - Timing results returned by this wrangler contain the values *wall_elapsed* - which measures elapsed wall time. This requires a command queue with - profiling enabled. + contains data that is allowed to be more ephemeral than the code, the code's + lifetime is decoupled by storing it in this object. """ - def __init__(self, cl_context, - multipole_expansion_factory, - local_expansion_factory, - target_kernels, exclude_self=False, use_rscale=None, - strength_usage=None, source_kernels=None, - use_fft_for_m2l=False, use_preprocessing_for_m2l=None): + multipole_expansion_factory: MultipoleExpansionFromOrderFactory + local_expansion_factory: LocalExpansionFromOrderFactory + source_kernels: Sequence[ScalarKernel] | None + target_kernels: Sequence[ScalarKernel] + exclude_self: bool + use_rscale: bool | None + strength_usage: Sequence[int] | None + + def __init__(self, + array_context: ArrayContext, + multipole_expansion_factory: MultipoleExpansionFromOrderFactory, + local_expansion_factory: LocalExpansionFromOrderFactory, + target_kernels: Sequence[ScalarKernel], + exclude_self: bool = False, + use_rscale: bool | None = None, + strength_usage: Sequence[int] | None = None, + source_kernels: Sequence[ScalarKernel] | None = None): """ :arg multipole_expansion_factory: a callable of a single argument (order) that returns a multipole expansion. @@ -73,11 +129,11 @@ def __init__(self, cl_context, :arg exclude_self: whether the self contribution should be excluded :arg strength_usage: passed unchanged to p2l, p2m and p2p. :arg source_kernels: passed unchanged to p2l, p2m and p2p. - :arg use_fft_for_m2l: Use an FFT based multipole-to-local expansion. - :arg use_preprocessing_for_m2l: do preprocessing of the source multipole - expansion and postprocessing of the target local expansion for - multipole-to-local expansion. """ + super().__init__() + + self._setup_actx: ArrayContext = array_context + self.multipole_expansion_factory = multipole_expansion_factory self.local_expansion_factory = local_expansion_factory self.source_kernels = source_kernels @@ -85,15 +141,6 @@ def __init__(self, cl_context, self.exclude_self = exclude_self self.use_rscale = use_rscale self.strength_usage = strength_usage - self.use_fft_for_m2l = use_fft_for_m2l - if use_preprocessing_for_m2l is None: - self.use_preprocessing_for_m2l = use_fft_for_m2l - else: - self.use_preprocessing_for_m2l = use_preprocessing_for_m2l - - super().__init__() - - self.cl_context = cl_context @memoize_method def get_base_kernel(self): @@ -101,34 +148,42 @@ def get_base_kernel(self): return single_valued(k.get_base_kernel() for k in self.target_kernels) @memoize_method - def multipole_expansion(self, order): - return self.multipole_expansion_factory(order, self.use_rscale) + def multipole_expansion(self, order: int): + if self.use_rscale is None: + return self.multipole_expansion_factory(order) + else: + return self.multipole_expansion_factory(order, use_rscale=self.use_rscale) @memoize_method - def local_expansion(self, order): - return self.local_expansion_factory(order, self.use_rscale, - use_fft_for_m2l=self.use_fft_for_m2l, - use_preprocessing_for_m2l=self.use_preprocessing_for_m2l) + def local_expansion(self, order: int): + if self.use_rscale is None: + return self.local_expansion_factory(order) + else: + return self.local_expansion_factory(order, use_rscale=self.use_rscale) + + @property + def m2l_translation(self): + return self.local_expansion(0).m2l_translation @memoize_method def p2m(self, tgt_order): - return P2EFromSingleBox(self.cl_context, + return P2EFromSingleBox( kernels=self.source_kernels, expansion=self.multipole_expansion(tgt_order), - strength_usage=self.strength_usage) + strength_usage=self.strength_usage, name="p2m") @memoize_method def p2l(self, tgt_order): - return P2EFromCSR(self.cl_context, + return P2EFromCSR( kernels=self.source_kernels, expansion=self.local_expansion(tgt_order), - strength_usage=self.strength_usage) + strength_usage=self.strength_usage, name="p2l") @memoize_method def m2m(self, src_order, tgt_order): - return E2EFromChildren(self.cl_context, + return E2EFromChildren( self.multipole_expansion(src_order), - self.multipole_expansion(tgt_order)) + self.multipole_expansion(tgt_order), name="m2m") @memoize_method def m2l(self, src_order, tgt_order, @@ -137,153 +192,74 @@ def m2l(self, src_order, tgt_order, m2l_class = M2LUsingTranslationClassesDependentData else: m2l_class = E2EFromCSR - return m2l_class(self.cl_context, + return m2l_class( self.multipole_expansion(src_order), - self.local_expansion(tgt_order)) + self.local_expansion(tgt_order), name="m2l") @memoize_method def m2l_translation_class_dependent_data_kernel(self, src_order, tgt_order): - return M2LGenerateTranslationClassesDependentData(self.cl_context, + return M2LGenerateTranslationClassesDependentData( self.multipole_expansion(src_order), self.local_expansion(tgt_order)) @memoize_method def m2l_preprocess_mpole_kernel(self, src_order, tgt_order): - return M2LPreprocessMultipole(self.cl_context, + return M2LPreprocessMultipole( self.multipole_expansion(src_order), self.local_expansion(tgt_order)) @memoize_method def m2l_postprocess_local_kernel(self, src_order, tgt_order): - return M2LPostprocessLocal(self.cl_context, + return M2LPostprocessLocal( self.multipole_expansion(src_order), self.local_expansion(tgt_order)) @memoize_method def l2l(self, src_order, tgt_order): - return E2EFromParent(self.cl_context, + return E2EFromParent( self.local_expansion(src_order), - self.local_expansion(tgt_order)) + self.local_expansion(tgt_order), name="l2l") @memoize_method def m2p(self, src_order): - return E2PFromCSR(self.cl_context, + return E2PFromCSR( self.multipole_expansion(src_order), - self.target_kernels) + self.target_kernels, name="m2p") @memoize_method def l2p(self, src_order): - return E2PFromSingleBox(self.cl_context, + return E2PFromSingleBox( self.local_expansion(src_order), - self.target_kernels) + self.target_kernels, name="l2p") @memoize_method def p2p(self): - return P2PFromCSR(self.cl_context, target_kernels=self.target_kernels, + return P2PFromCSR(target_kernels=self.target_kernels, source_kernels=self.source_kernels, exclude_self=self.exclude_self, - strength_usage=self.strength_usage) - -# }}} - - -# {{{ timing future - -_SECONDS_PER_NANOSECOND = 1e-9 - + strength_usage=self.strength_usage, name="p2p") -class UnableToCollectTimingData(UserWarning): - pass + def opencl_fft_app(self, + shape: tuple[int, ...], + dtype: np.dtype[Any], + inverse: bool) -> Any: + @memoize_in(self._setup_actx, ( + SumpyTreeIndependentDataForWrangler.opencl_fft_app, + shape, dtype, inverse)) + def app() -> Any: + return get_opencl_fft_app(self._setup_actx, shape, dtype, inverse=inverse) - -class SumpyTimingFuture: - - def __init__(self, queue, events): - self.queue = queue - self.events = events - - @memoize_method - def result(self): - from boxtree.timing import TimingResult - - if not self.queue.properties & cl.command_queue_properties.PROFILING_ENABLE: - from warnings import warn - warn( - "Profiling was not enabled in the command queue. " - "Timing data will not be collected.", - category=UnableToCollectTimingData, - stacklevel=3) - return TimingResult(wall_elapsed=None) - - if self.events: - pyopencl.wait_for_events(self.events) - - result = 0 - for event in self.events: - result += ( - (event.profile.end - event.profile.start) - * _SECONDS_PER_NANOSECOND) - - return TimingResult(wall_elapsed=result) - - def done(self): - return all( - event.get_info(cl.event_info.COMMAND_EXECUTION_STATUS) - == cl.command_execution_status.COMPLETE - for event in self.events) + return app() # }}} -# {{{ translation classes data - -class SumpyTranslationClassesData: - """A class for building and storing additional, optional data for - precomputation of translation classes passed to the expansion wrangler.""" - - def __init__(self, queue, trav, is_translation_per_level=True): - # FIXME: Queues should not be part of data. - self.queue = queue - self.trav = trav - self.tree = trav.tree - self.is_translation_per_level = is_translation_per_level - - @property - @memoize_method - def translation_classes_builder(self): - from boxtree.translation_classes import TranslationClassesBuilder - return TranslationClassesBuilder(self.queue.context) - - @memoize_method - def build_translation_classes_lists(self): - return self.translation_classes_builder(self.queue, self.trav, self.tree, - is_translation_per_level=self.is_translation_per_level)[0] - - @memoize_method - def m2l_translation_classes_lists(self): - return (self - .build_translation_classes_lists() - .from_sep_siblings_translation_classes) - - @memoize_method - def m2l_translation_vectors(self): - return (self - .build_translation_classes_lists() - .from_sep_siblings_translation_class_to_distance_vector) - - def m2l_translation_classes_level_starts(self): - return (self - .build_translation_classes_lists() - .from_sep_siblings_translation_classes_level_starts) - - -class SumpyTranslationClassesDataNotSuppliedWarning(UserWarning): - pass - -# }}} +# {{{ expansion wrangler +FMMLevelToOrder: TypeAlias = Callable[ + [ScalarKernel, frozenset[tuple[str, object]], Tree, int], + int] -# {{{ expansion wrangler class SumpyExpansionWrangler(ExpansionWranglerInterface): """Implements the :class:`boxtree.fmm.ExpansionWranglerInterface` @@ -310,22 +286,43 @@ class SumpyExpansionWrangler(ExpansionWranglerInterface): Type for the preprocessed multipole expansion if used for M2L. """ - def __init__(self, tree_indep, traversal, dtype, fmm_level_to_order, - source_extra_kwargs=None, - kernel_extra_kwargs=None, - self_extra_kwargs=None, - translation_classes_data=None, - preprocessed_mpole_dtype=None): + tree_indep: SumpyTreeIndependentDataForWrangler + traversal: FMMTraversalInfo + + source_extra_kwargs: Mapping[str, object] + kernel_extra_kwargs: Mapping[str, object] + self_extra_kwargs: Mapping[str, object] + extra_kwargs: Mapping[str, object] + + dtype: np.dtype[Any] + preprocessed_mpole_dtype: np.dtype[Any] + + level_order: Sequence[int] + + issued_timing_data_warning: bool + + def __init__(self, + tree_indep: SumpyTreeIndependentDataForWrangler, + traversal: FMMTraversalInfo, + dtype: DTypeLike, + fmm_level_to_order: FMMLevelToOrder, + source_extra_kwargs: Mapping[str, object] | None = None, + kernel_extra_kwargs: Mapping[str, object] | None = None, + self_extra_kwargs: Mapping[str, object] | None = None, + translation_classes_data=None, + preprocessed_mpole_dtype: DTypeLike | None = None, + *, + _disable_translation_classes=False + ): super().__init__(tree_indep, traversal) - self.issued_timing_data_warning = False - self.dtype = dtype + self.dtype = np.dtype(dtype) - if not self.tree_indep.use_fft_for_m2l: + if not self.tree_indep.m2l_translation.use_fft: # If not FFT, we don't need complex dtypes - self.preprocessed_mpole_dtype = dtype + self.preprocessed_mpole_dtype = self.dtype elif preprocessed_mpole_dtype is not None: - self.preprocessed_mpole_dtype = preprocessed_mpole_dtype + self.preprocessed_mpole_dtype = np.dtype(preprocessed_mpole_dtype) else: # FIXME: It is weird that the wrangler has to compute this. self.preprocessed_mpole_dtype = to_complex_dtype(dtype) @@ -350,48 +347,58 @@ def __init__(self, tree_indep, traversal, dtype, fmm_level_to_order, self.kernel_extra_kwargs = kernel_extra_kwargs self.self_extra_kwargs = self_extra_kwargs - self.extra_kwargs = source_extra_kwargs.copy() + self.extra_kwargs = dict(source_extra_kwargs) self.extra_kwargs.update(self.kernel_extra_kwargs) - if base_kernel.is_translation_invariant: - if translation_classes_data is None: - from warnings import warn - if self.tree_indep.use_fft_for_m2l: - raise NotImplementedError( - "FFT based List 2 (multipole-to-local) translations " - "without translation_classes_data argument is not " - "implemented. Supply a translation_classes_data argument " - "to the wrangler for optimized List 2.") - else: - warn( - "List 2 (multipole-to-local) translations will be " - "unoptimized. Supply a translation_classes_data argument " - "to the wrangler for optimized List 2.", - SumpyTranslationClassesDataNotSuppliedWarning, - stacklevel=2) - self.supports_translation_classes = False - else: - self.supports_translation_classes = True - else: + if _disable_translation_classes or not base_kernel.is_translation_invariant: self.supports_translation_classes = False + else: + if translation_classes_data is None: + from boxtree.translation_classes import TranslationClassesBuilder + + actx = tree_indep._setup_actx + translation_classes_builder = TranslationClassesBuilder(actx) + translation_classes_data, _ = translation_classes_builder( + actx, traversal, self.tree, is_translation_per_level=True) + translation_classes_data = actx.freeze(translation_classes_data) + + self.supports_translation_classes = True self.translation_classes_data = translation_classes_data - self.use_fft_for_m2l = self.tree_indep.use_fft_for_m2l - def level_to_rscale(self, level): + def level_to_rscale(self, level: int) -> float: tree = self.tree order = self.level_orders[level] + r = tree.root_extent * (2**-level) # See L. Greengard and V. Rokhlin. On the efficient implementation of the # fast multipole algorithm. Technical report, # YALE UNIV NEW HAVEN CT DEPT OF COMPUTER SCIENCE, 1988. - return tree.root_extent * (2**-level) / order + # rscale that we use in sumpy is the inverse of the scaling used in the + # paper and therefore we should use r / order. However empirically + # we have observed that 2r / order is better for numerical stability + # for Laplace and 4r / order for biharmonic kernel. + knl = self.tree_indep.get_base_kernel() + from sumpy.kernel import BiharmonicKernel + if isinstance(knl, BiharmonicKernel): + return r * 4 / order + else: + return r * 2 / order # {{{ data vector utilities - def _expansions_level_starts(self, order_to_size): + @property + @memoize_method + def tree_level_start_box_nrs(self): + # NOTE: a host version of `level_start_box_nrs` is used repeatedly and + # this simply caches it to avoid repeated transfers + actx = self.tree_indep._setup_actx + assert self.tree.level_start_box_nrs is not None + return actx.to_numpy(self.tree.level_start_box_nrs) + + def _expansions_level_starts(self, order_to_size: Callable[[int], int]): return build_csr_level_starts(self.level_orders, order_to_size, - self.tree.level_start_box_nrs) + self.tree_level_start_box_nrs) @memoize_method def multipole_expansions_level_starts(self): @@ -405,136 +412,140 @@ def local_expansions_level_starts(self): @memoize_method def m2l_translation_class_level_start_box_nrs(self): - with cl.CommandQueue(self.tree_indep.cl_context) as queue: - data = self.translation_classes_data - return data.m2l_translation_classes_level_starts().get(queue) + actx = self.tree_indep._setup_actx + return actx.to_numpy( + self.translation_classes_data + .from_sep_siblings_translation_classes_level_starts) @memoize_method def m2l_translation_classes_dependent_data_level_starts(self): - def order_to_size(order): + def order_to_size(order: int): mpole_expn = self.tree_indep.multipole_expansion(order) local_expn = self.tree_indep.local_expansion(order) - return local_expn.m2l_translation_classes_dependent_ndata(mpole_expn) + m2l_translation = local_expn.m2l_translation + return m2l_translation.translation_classes_dependent_ndata( + local_expn, mpole_expn) return build_csr_level_starts(self.level_orders, order_to_size, level_starts=self.m2l_translation_class_level_start_box_nrs()) - def multipole_expansion_zeros(self, template_ary): + def multipole_expansion_zeros(self, actx: ArrayContext) -> Array: """Return an expansions array (which must support addition) capable of holding one multipole or local expansion for every box in the tree. - :arg template_ary: an array (not necessarily of the same shape or dtype as - the one to be created) whose run-time environment - (e.g. :class:`pyopencl.CommandQueue`) the returned array should - reuse. """ - return cl.array.zeros( - template_ary.queue, + return actx.np.zeros( self.multipole_expansions_level_starts()[-1], dtype=self.dtype) - def local_expansion_zeros(self, template_ary): + def local_expansion_zeros(self, actx) -> Array: """Return an expansions array (which must support addition) capable of holding one multipole or local expansion for every box in the tree. - :arg template_ary: an array (not necessarily of the same shape or dtype as - the one to be created) whose run-time environment - (e.g. :class:`pyopencl.CommandQueue`) the returned array should - reuse. """ - return cl.array.zeros( - template_ary.queue, + return actx.np.zeros( self.local_expansions_level_starts()[-1], dtype=self.dtype) - def m2l_translation_classes_dependent_data_zeros(self, queue): - return cl.array.zeros( - queue, - self.m2l_translation_classes_dependent_data_level_starts()[-1], - dtype=self.preprocessed_mpole_dtype) + def m2l_translation_classes_dependent_data_zeros( + self, actx: ArrayContext): + data_level_starts = ( + self.m2l_translation_classes_dependent_data_level_starts()) + level_start_box_nrs = ( + self.m2l_translation_class_level_start_box_nrs()) + + result = [] + for level in range(self.tree.nlevels): + expn_start, expn_stop = data_level_starts[level:level + 2] + translation_class_start, translation_class_stop = ( + level_start_box_nrs[level:level + 2]) + + exprs_level = actx.np.zeros( + expn_stop - expn_start, + dtype=self.preprocessed_mpole_dtype + ).reshape(translation_class_stop - translation_class_start, -1) + result.append(exprs_level) + + return result def multipole_expansions_view(self, mpole_exps, level): - expn_start, expn_stop = \ - self.multipole_expansions_level_starts()[level:level+2] - box_start, box_stop = self.tree.level_start_box_nrs[level:level+2] + expn_start, expn_stop = ( + self.multipole_expansions_level_starts()[level:level + 2]) + box_start, box_stop = self.tree_level_start_box_nrs[level:level + 2] return (box_start, mpole_exps[expn_start:expn_stop].reshape(box_stop-box_start, -1)) def local_expansions_view(self, local_exps, level): - expn_start, expn_stop = \ - self.local_expansions_level_starts()[level:level+2] - box_start, box_stop = self.tree.level_start_box_nrs[level:level+2] + expn_start, expn_stop = ( + self.local_expansions_level_starts()[level:level + 2]) + box_start, box_stop = self.tree_level_start_box_nrs[level:level + 2] return (box_start, local_exps[expn_start:expn_stop].reshape(box_stop-box_start, -1)) def m2l_translation_classes_dependent_data_view(self, m2l_translation_classes_dependent_data, level): - expn_start, expn_stop = \ - self.m2l_translation_classes_dependent_data_level_starts()[level:level+2] - translation_class_start, translation_class_stop = \ - self.m2l_translation_class_level_start_box_nrs()[level:level+2] - - exprs_level = m2l_translation_classes_dependent_data[expn_start:expn_stop] - return (translation_class_start, exprs_level.reshape( - translation_class_stop - translation_class_start, -1)) + translation_class_start, _ = ( + self.m2l_translation_class_level_start_box_nrs()[level:level + 2]) + exprs_level = m2l_translation_classes_dependent_data[level] + return (translation_class_start, exprs_level) @memoize_method def m2l_preproc_mpole_expansions_level_starts(self): def order_to_size(order): mpole_expn = self.tree_indep.multipole_expansion(order) local_expn = self.tree_indep.local_expansion(order) - res = local_expn.m2l_preprocess_multipole_nexprs(mpole_expn) - return res + return local_expn.m2l_translation.preprocess_multipole_nexprs( + local_expn, mpole_expn) return build_csr_level_starts(self.level_orders, order_to_size, - level_starts=self.tree.level_start_box_nrs) + level_starts=self.tree_level_start_box_nrs) - def m2l_preproc_mpole_expansion_zeros(self, template_ary): - return cl.array.zeros( - template_ary.queue, - self.m2l_preproc_mpole_expansions_level_starts()[-1], - dtype=self.preprocessed_mpole_dtype) + def m2l_preproc_mpole_expansion_zeros( + self, actx: ArrayContext, template_ary): + level_starts = self.m2l_preproc_mpole_expansions_level_starts() - def m2l_preproc_mpole_expansions_view(self, mpole_exps, level): - expn_start, expn_stop = \ - self.m2l_preproc_mpole_expansions_level_starts()[level:level+2] - box_start, box_stop = self.tree.level_start_box_nrs[level:level+2] + result = [] + for level in range(self.tree.nlevels): + expn_start, expn_stop = level_starts[level:level+2] + box_start, box_stop = self.tree_level_start_box_nrs[level:level+2] - return (box_start, - mpole_exps[expn_start:expn_stop].reshape(box_stop-box_start, -1)) + exprs_level = actx.np.zeros( + expn_stop - expn_start, + dtype=self.preprocessed_mpole_dtype, + ).reshape(box_stop - box_start, -1) + result.append(exprs_level) + + return result + + def m2l_preproc_mpole_expansions_view(self, mpole_exps, level): + box_start, _ = self.tree_level_start_box_nrs[level:level+2] + return (box_start, mpole_exps[level]) m2l_work_array_view = m2l_preproc_mpole_expansions_view m2l_work_array_zeros = m2l_preproc_mpole_expansion_zeros - m2l_work_array_level_starts = \ - m2l_preproc_mpole_expansions_level_starts + m2l_work_array_level_starts = m2l_preproc_mpole_expansions_level_starts - def output_zeros(self, template_ary): + def output_zeros(self, + actx: ArrayContext + ) -> obj_array.ObjectArray1D[Array]: """Return a potentials array (which must support addition) capable of holding a potential value for each target in the tree. Note that :func:`drive_fmm` makes no assumptions about *potential* other than that it supports addition--it may consist of potentials, gradients of the potential, or arbitrary other per-target output data. - :arg template_ary: an array (not necessarily of the same shape or dtype as - the one to be created) whose run-time environment - (e.g. :class:`pyopencl.CommandQueue`) the returned array should - reuse. """ - from pytools.obj_array import make_obj_array - return make_obj_array([ - cl.array.zeros( - template_ary.queue, - self.tree.ntargets, - dtype=self.dtype) + return obj_array.new_1d([ + actx.np.zeros(self.tree.ntargets, dtype=self.dtype) for k in self.tree_indep.target_kernels]) def reorder_sources(self, source_array): - return source_array.with_queue(source_array.queue)[self.tree.user_source_ids] + return source_array[self.tree.user_source_ids] def reorder_potentials(self, potentials): - from pytools.obj_array import obj_array_vectorize import numpy as np + assert ( isinstance(potentials, np.ndarray) and potentials.dtype.char == "O") @@ -542,21 +553,23 @@ def reorder_potentials(self, potentials): def reorder(x): return x[self.tree.sorted_target_ids] - return obj_array_vectorize(reorder, potentials) + return obj_array.vectorize(reorder, potentials) @property @memoize_method def max_nsources_in_one_box(self): - with cl.CommandQueue(self.tree_indep.cl_context) as queue: - return int(pyopencl.array.max(self.tree.box_source_counts_nonchild, - queue).get()) + actx = self.tree_indep._setup_actx + return actx.to_numpy( + actx.np.max(self.tree.box_source_counts_nonchild) + ).item() @property @memoize_method def max_ntargets_in_one_box(self): - with cl.CommandQueue(self.tree_indep.cl_context) as queue: - return int(pyopencl.array.max(self.tree.box_target_counts_nonchild, - queue).get()) + actx = self.tree_indep._setup_actx + return actx.to_numpy( + actx.np.max(self.tree.box_target_counts_nonchild) + ).item() # }}} @@ -567,30 +580,42 @@ def max_ntargets_in_one_box(self): # use a FilteredTargetListsInTreeOrder object. def box_source_list_kwargs(self): - return dict( - box_source_starts=self.tree.box_source_starts, - box_source_counts_nonchild=self.tree.box_source_counts_nonchild, - sources=self.tree.sources) + return { + "box_source_starts": self.tree.box_source_starts, + "box_source_counts_nonchild": self.tree.box_source_counts_nonchild, + "sources": self.tree.sources} def box_target_list_kwargs(self): - return dict( - box_target_starts=self.tree.box_target_starts, - box_target_counts_nonchild=self.tree.box_target_counts_nonchild, - targets=self.tree.targets) + return { + "box_target_starts": self.tree.box_target_starts, + "box_target_counts_nonchild": self.tree.box_target_counts_nonchild, + "targets": self.tree.targets} # }}} + def run_opencl_fft(self, actx: ArrayContext, + input_vec, inverse, wait_for): + app = self.tree_indep.opencl_fft_app(input_vec.shape, input_vec.dtype, + inverse) + evt, result = run_opencl_fft( + actx, app, input_vec, inverse=inverse, wait_for=wait_for) + + from sumpy.tools import get_native_event + input_vec.add_event(get_native_event(evt)) + result.add_event(get_native_event(evt)) + + return result + def form_multipoles(self, + actx: ArrayContext, level_start_source_box_nrs, source_boxes, src_weight_vecs): - mpoles = self.multipole_expansion_zeros(src_weight_vecs[0]) + mpoles = self.multipole_expansion_zeros(actx) + level_start_source_box_nrs = actx.to_numpy(level_start_source_box_nrs) - kwargs = self.extra_kwargs.copy() + kwargs = dict(self.extra_kwargs) kwargs.update(self.box_source_list_kwargs()) - events = [] - queue = src_weight_vecs[0].queue - for lev in range(self.tree.nlevels): p2m = self.tree_indep.p2m(self.level_orders[lev]) start, stop = level_start_source_box_nrs[lev:lev+2] @@ -600,8 +625,8 @@ def form_multipoles(self, level_start_ibox, mpoles_view = self.multipole_expansions_view( mpoles, lev) - evt, (mpoles_res,) = p2m( - queue, + mpoles_res = p2m( + actx, source_boxes=source_boxes[start:stop], centers=self.tree.box_centers, strengths=src_weight_vecs, @@ -610,20 +635,19 @@ def form_multipoles(self, rscale=self.level_to_rscale(lev), **kwargs) - events.append(evt) assert mpoles_res is mpoles_view - return (mpoles, SumpyTimingFuture(queue, events)) + return mpoles def coarsen_multipoles(self, + actx: ArrayContext, level_start_source_parent_box_nrs, source_parent_boxes, mpoles): tree = self.tree - - events = [] - queue = mpoles.queue + level_start_source_parent_box_nrs = ( + actx.to_numpy(level_start_source_parent_box_nrs)) # nlevels-1 is the last valid level index # nlevels-2 is the last valid level that could have children @@ -645,13 +669,13 @@ def coarsen_multipoles(self, self.level_orders[source_level], self.level_orders[target_level]) - source_level_start_ibox, source_mpoles_view = \ - self.multipole_expansions_view(mpoles, source_level) - target_level_start_ibox, target_mpoles_view = \ - self.multipole_expansions_view(mpoles, target_level) + source_level_start_ibox, source_mpoles_view = ( + self.multipole_expansions_view(mpoles, source_level)) + target_level_start_ibox, target_mpoles_view = ( + self.multipole_expansions_view(mpoles, target_level)) - evt, (mpoles_res,) = m2m( - queue, + mpoles_res = m2m( + actx, src_expansions=source_mpoles_view, src_base_ibox=source_level_start_ibox, tgt_expansions=target_mpoles_view, @@ -665,28 +689,23 @@ def coarsen_multipoles(self, tgt_rscale=self.level_to_rscale(target_level), **self.kernel_extra_kwargs) - events.append(evt) assert mpoles_res is target_mpoles_view - if events: - mpoles.add_event(events[-1]) + return mpoles - return (mpoles, SumpyTimingFuture(queue, events)) - - def eval_direct(self, target_boxes, source_box_starts, + def eval_direct(self, + actx: ArrayContext, + target_boxes, source_box_starts, source_box_lists, src_weight_vecs): - pot = self.output_zeros(src_weight_vecs[0]) + pot = self.output_zeros(actx) - kwargs = self.extra_kwargs.copy() + kwargs = dict(self.extra_kwargs) kwargs.update(self.self_extra_kwargs) kwargs.update(self.box_source_list_kwargs()) kwargs.update(self.box_target_list_kwargs()) - events = [] - queue = src_weight_vecs[0].queue - - evt, pot_res = self.tree_indep.p2p()(queue, + pot_res = self.tree_indep.p2p()(actx, target_boxes=target_boxes, source_box_starts=source_box_starts, source_box_lists=source_box_lists, @@ -695,58 +714,65 @@ def eval_direct(self, target_boxes, source_box_starts, max_nsources_in_one_box=self.max_nsources_in_one_box, max_ntargets_in_one_box=self.max_ntargets_in_one_box, **kwargs) - events.append(evt) - for pot_i, pot_res_i in zip(pot, pot_res): + for pot_i, pot_res_i in zip(pot, pot_res, strict=True): assert pot_i is pot_res_i - pot_i.add_event(evt) - return (pot, SumpyTimingFuture(queue, events)) + return pot @memoize_method def multipole_to_local_precompute(self): - with cl.CommandQueue(self.tree_indep.cl_context) as queue: - m2l_translation_classes_dependent_data = \ - self.m2l_translation_classes_dependent_data_zeros(queue) - for lev in range(self.tree.nlevels): - src_rscale = self.level_to_rscale(lev) - order = self.level_orders[lev] - precompute_kernel = \ - self.tree_indep.m2l_translation_class_dependent_data_kernel( - order, order) + actx = self.tree_indep._setup_actx - translation_classes_level_start, \ - m2l_translation_classes_dependent_data_view = \ - self.m2l_translation_classes_dependent_data_view( - m2l_translation_classes_dependent_data, lev) + result = [] + m2l_translation_classes_dependent_data = ( + self.m2l_translation_classes_dependent_data_zeros(actx)) - ntranslation_classes = \ - m2l_translation_classes_dependent_data_view.shape[0] + for lev in range(self.tree.nlevels): + src_rscale = self.level_to_rscale(lev) + order = self.level_orders[lev] + precompute_kernel = ( + self.tree_indep.m2l_translation_class_dependent_data_kernel( + order, order) + ) - if ntranslation_classes == 0: - continue + translation_classes_level_start, \ + m2l_translation_classes_dependent_data_view = \ + self.m2l_translation_classes_dependent_data_view( + m2l_translation_classes_dependent_data, lev) - m2l_translation_vectors = ( - self.translation_classes_data.m2l_translation_vectors()) - - evt, _ = precompute_kernel( - queue, - src_rscale=src_rscale, - translation_classes_level_start=translation_classes_level_start, - ntranslation_classes=ntranslation_classes, - m2l_translation_classes_dependent_data=( - m2l_translation_classes_dependent_data_view), - m2l_translation_vectors=m2l_translation_vectors, - ntranslation_vectors=m2l_translation_vectors.shape[1], - **self.kernel_extra_kwargs - ) - m2l_translation_classes_dependent_data.add_event(evt) + ntranslation_classes = ( + m2l_translation_classes_dependent_data_view.shape[0]) - m2l_translation_classes_dependent_data.finish() + if ntranslation_classes == 0: + result.append(actx.np.zeros_like( + m2l_translation_classes_dependent_data_view)) + continue - m2l_translation_classes_dependent_data = \ - m2l_translation_classes_dependent_data.with_queue(None) - return m2l_translation_classes_dependent_data + data = self.translation_classes_data + m2l_translation_vectors = ( + data.from_sep_siblings_translation_class_to_distance_vector) + + precompute_kernel( + actx, + src_rscale=src_rscale, + translation_classes_level_start=translation_classes_level_start, + ntranslation_classes=ntranslation_classes, + m2l_translation_classes_dependent_data=( + m2l_translation_classes_dependent_data_view), + m2l_translation_vectors=m2l_translation_vectors, + ntranslation_vectors=m2l_translation_vectors.shape[1], + **self.kernel_extra_kwargs + ) + + if self.tree_indep.m2l_translation.use_fft: + m2l_translation_classes_dependent_data_view = ( + self.run_opencl_fft(actx, + m2l_translation_classes_dependent_data_view, + inverse=False, wait_for=None)) + result.append(m2l_translation_classes_dependent_data_view) + + return [actx.freeze(arr) for arr in result] def _add_m2l_precompute_kwargs(self, kwargs_for_m2l, lev): @@ -767,21 +793,37 @@ def _add_m2l_precompute_kwargs(self, kwargs_for_m2l, kwargs_for_m2l["translation_classes_level_start"] = \ translation_classes_level_start kwargs_for_m2l["m2l_translation_classes_lists"] = \ - self.translation_classes_data.m2l_translation_classes_lists() + self.translation_classes_data.from_sep_siblings_translation_classes def multipole_to_local(self, + actx: ArrayContext, level_start_target_box_nrs, target_boxes, src_box_starts, src_box_lists, mpole_exps): - preprocess_evts = [] - queue = mpole_exps.queue - local_exps = self.local_expansion_zeros(mpole_exps) + local_exps = self.local_expansion_zeros(actx) + level_start_target_box_nrs = actx.to_numpy(level_start_target_box_nrs) - if self.tree_indep.use_preprocessing_for_m2l: - preprocessed_mpole_exps = \ - self.m2l_preproc_mpole_expansion_zeros(mpole_exps) - for lev in range(self.tree.nlevels): + if self.tree_indep.m2l_translation.use_preprocessing: + preprocessed_mpole_exps = ( + self.m2l_preproc_mpole_expansion_zeros(actx, mpole_exps)) + m2l_work_array = self.m2l_work_array_zeros(actx, local_exps) + mpole_exps_view_func = self.m2l_preproc_mpole_expansions_view + local_exps_view_func = self.m2l_work_array_view + else: + preprocessed_mpole_exps = mpole_exps + m2l_work_array = local_exps + mpole_exps_view_func = self.multipole_expansions_view + local_exps_view_func = self.local_expansions_view + + for lev in range(self.tree.nlevels): + wait_for: list[pyopencl.Event] = [] + + start, stop = level_start_target_box_nrs[lev:lev+2] + if start == stop: + continue + + if self.tree_indep.m2l_translation.use_preprocessing: order = self.level_orders[lev] preprocess_mpole_kernel = \ self.tree_indep.m2l_preprocess_mpole_kernel(order, order) @@ -789,45 +831,32 @@ def multipole_to_local(self, _, source_mpoles_view = \ self.multipole_expansions_view(mpole_exps, lev) - _, preprocessed_source_mpoles_view = \ - self.m2l_preproc_mpole_expansions_view( - preprocessed_mpole_exps, lev) - tr_classes = self.m2l_translation_class_level_start_box_nrs() if tr_classes[lev] == tr_classes[lev + 1]: # There is no M2L happening in this level continue - evt, _ = preprocess_mpole_kernel( - queue, + preprocess_mpole_kernel( + actx, src_expansions=source_mpoles_view, - preprocessed_src_expansions=preprocessed_source_mpoles_view, + preprocessed_src_expansions=preprocessed_mpole_exps[lev], src_rscale=self.level_to_rscale(lev), + wait_for=wait_for, **self.kernel_extra_kwargs ) - preprocess_evts.append(evt) - mpole_exps = preprocessed_mpole_exps - m2l_work_array = self.m2l_work_array_zeros(local_exps) - mpole_exps_view_func = self.m2l_preproc_mpole_expansions_view - local_exps_view_func = self.m2l_work_array_view - else: - m2l_work_array = local_exps - mpole_exps_view_func = self.multipole_expansions_view - local_exps_view_func = self.local_expansions_view - translate_evts = [] - - for lev in range(self.tree.nlevels): - start, stop = level_start_target_box_nrs[lev:lev+2] - if start == stop: - continue + if self.tree_indep.m2l_translation.use_fft: + preprocessed_mpole_exps[lev] = \ + self.run_opencl_fft(actx, + preprocessed_mpole_exps[lev], + inverse=False, wait_for=wait_for) order = self.level_orders[lev] m2l = self.tree_indep.m2l(order, order, self.supports_translation_classes) source_level_start_ibox, source_mpoles_view = \ - mpole_exps_view_func(mpole_exps, lev) + mpole_exps_view_func(preprocessed_mpole_exps, lev) target_level_start_ibox, target_locals_view = \ local_exps_view_func(m2l_work_array, lev) @@ -838,7 +867,7 @@ def multipole_to_local(self, tgt_base_ibox=target_level_start_ibox, target_boxes=target_boxes[start:stop], - src_box_starts=src_box_starts[start:stop], + src_box_starts=src_box_starts[start:stop+1], src_box_lists=src_box_lists, centers=self.tree.box_centers, @@ -852,14 +881,9 @@ def multipole_to_local(self, kwargs["m2l_translation_classes_dependent_data"].size == 0: # There is nothing to do for this level continue - evt, _ = m2l(queue, **kwargs, wait_for=preprocess_evts) - - translate_evts.append(evt) + m2l(actx, **kwargs, wait_for=wait_for) - postprocess_evts = [] - - if self.tree_indep.use_preprocessing_for_m2l: - for lev in range(self.tree.nlevels): + if self.tree_indep.m2l_translation.use_preprocessing: order = self.level_orders[lev] postprocess_local_kernel = \ self.tree_indep.m2l_postprocess_local_kernel(order, order) @@ -876,34 +900,34 @@ def multipole_to_local(self, # There is no M2L happening in this level continue - evt, _ = postprocess_local_kernel( - queue, + if self.tree_indep.m2l_translation.use_fft: + target_locals_before_postprocessing_view = \ + self.run_opencl_fft(actx, + target_locals_before_postprocessing_view, + inverse=True, wait_for=wait_for) + + postprocess_local_kernel( + actx, tgt_expansions=target_locals_view, tgt_expansions_before_postprocessing=( target_locals_before_postprocessing_view), src_rscale=self.level_to_rscale(lev), tgt_rscale=self.level_to_rscale(lev), - wait_for=translate_evts, + wait_for=wait_for, **self.kernel_extra_kwargs, ) - postprocess_evts.append(evt) - - timing_events = preprocess_evts + translate_evts + postprocess_evts - return (local_exps, SumpyTimingFuture(queue, timing_events)) + return local_exps def eval_multipoles(self, + actx: ArrayContext, target_boxes_by_source_level, source_boxes_by_level, mpole_exps): - pot = self.output_zeros(mpole_exps) + pot = self.output_zeros(actx) - kwargs = self.kernel_extra_kwargs.copy() + kwargs = dict(self.kernel_extra_kwargs) kwargs.update(self.box_target_list_kwargs()) - events = [] - queue = mpole_exps.queue - wait_for = mpole_exps.events - for isrc_level, ssn in enumerate(source_boxes_by_level): if len(target_boxes_by_source_level[isrc_level]) == 0: continue @@ -913,8 +937,8 @@ def eval_multipoles(self, source_level_start_ibox, source_mpoles_view = \ self.multipole_expansions_view(mpole_exps, isrc_level) - evt, pot_res = m2p( - queue, + pot_res = m2p( + actx, src_expansions=source_mpoles_view, src_base_ibox=source_level_start_ibox, @@ -930,33 +954,26 @@ def eval_multipoles(self, wait_for=wait_for, **kwargs) - events.append(evt) - wait_for = [evt] - - for pot_i, pot_res_i in zip(pot, pot_res): + for pot_i, pot_res_i in zip(pot, pot_res, strict=True): assert pot_i is pot_res_i - if events: - for pot_i in pot: - pot_i.add_event(events[-1]) - - return (pot, SumpyTimingFuture(queue, events)) + return pot def form_locals(self, + actx: ArrayContext, level_start_target_or_target_parent_box_nrs, target_or_target_parent_boxes, starts, lists, src_weight_vecs): - local_exps = self.local_expansion_zeros(src_weight_vecs[0]) + local_exps = self.local_expansion_zeros(actx) + level_start_target_or_target_parent_box_nrs = ( + actx.to_numpy(level_start_target_or_target_parent_box_nrs)) - kwargs = self.extra_kwargs.copy() + kwargs = dict(self.extra_kwargs) kwargs.update(self.box_source_list_kwargs()) - events = [] - queue = src_weight_vecs[0].queue - for lev in range(self.tree.nlevels): - start, stop = \ - level_start_target_or_target_parent_box_nrs[lev:lev+2] + start, stop = ( + level_start_target_or_target_parent_box_nrs[lev:lev+2]) if start == stop: continue @@ -965,8 +982,8 @@ def form_locals(self, target_level_start_ibox, target_local_exps_view = \ self.local_expansions_view(local_exps, lev) - evt, (result,) = p2l( - queue, + result = p2l( + actx, target_boxes=target_or_target_parent_boxes[start:stop], source_box_starts=starts[start:stop+1], source_box_lists=lists, @@ -979,23 +996,22 @@ def form_locals(self, rscale=self.level_to_rscale(lev), **kwargs) - events.append(evt) assert result is target_local_exps_view - return (local_exps, SumpyTimingFuture(queue, events)) + return local_exps def refine_locals(self, + actx: ArrayContext, level_start_target_or_target_parent_box_nrs, target_or_target_parent_boxes, local_exps): - - events = [] - queue = local_exps.queue + level_start_target_or_target_parent_box_nrs = ( + actx.to_numpy(level_start_target_or_target_parent_box_nrs)) for target_lev in range(1, self.tree.nlevels): - start, stop = level_start_target_or_target_parent_box_nrs[ - target_lev:target_lev+2] + start, stop = ( + level_start_target_or_target_parent_box_nrs[target_lev:target_lev+2]) if start == stop: continue @@ -1009,7 +1025,7 @@ def refine_locals(self, target_level_start_ibox, target_local_exps_view = \ self.local_expansions_view(local_exps, target_lev) - evt, (local_exps_res,) = l2l(queue, + local_exps_res = l2l(actx, src_expansions=source_local_exps_view, src_base_ibox=source_level_start_ibox, tgt_expansions=target_local_exps_view, @@ -1023,23 +1039,20 @@ def refine_locals(self, tgt_rscale=self.level_to_rscale(target_lev), **self.kernel_extra_kwargs) - events.append(evt) assert local_exps_res is target_local_exps_view - local_exps.add_event(evt) + return local_exps - return (local_exps, SumpyTimingFuture(queue, [evt])) + def eval_locals(self, + actx: ArrayContext, + level_start_target_box_nrs, target_boxes, local_exps): + pot = self.output_zeros(actx) + level_start_target_box_nrs = actx.to_numpy(level_start_target_box_nrs) - def eval_locals(self, level_start_target_box_nrs, target_boxes, local_exps): - pot = self.output_zeros(local_exps) - - kwargs = self.kernel_extra_kwargs.copy() + kwargs = dict(self.kernel_extra_kwargs) kwargs.update(self.box_target_list_kwargs()) - events = [] - queue = local_exps.queue - for lev in range(self.tree.nlevels): start, stop = level_start_target_box_nrs[lev:lev+2] if start == stop: @@ -1050,8 +1063,8 @@ def eval_locals(self, level_start_target_box_nrs, target_boxes, local_exps): source_level_start_ibox, source_local_exps_view = \ self.local_expansions_view(local_exps, lev) - evt, pot_res = l2p( - queue, + pot_res = l2p( + actx, src_expansions=source_local_exps_view, src_base_ibox=source_level_start_ibox, @@ -1063,14 +1076,13 @@ def eval_locals(self, level_start_target_box_nrs, target_boxes, local_exps): rscale=self.level_to_rscale(lev), **kwargs) - events.append(evt) - for pot_i, pot_res_i in zip(pot, pot_res): + for pot_i, pot_res_i in zip(pot, pot_res, strict=True): assert pot_i is pot_res_i - return (pot, SumpyTimingFuture(queue, events)) + return pot - def finalize_potentials(self, potentials, template_ary): + def finalize_potentials(self, actx: ArrayContext, potentials): return potentials # }}} diff --git a/sumpy/kernel.py b/sumpy/kernel.py index d307e6aa7..821f9993e 100644 --- a/sumpy/kernel.py +++ b/sumpy/kernel.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2012 Andreas Kloeckner" __license__ = """ @@ -20,40 +23,155 @@ THE SOFTWARE. """ +from abc import ABC, abstractmethod +from collections import defaultdict +from dataclasses import dataclass +from typing import ( + TYPE_CHECKING, + Any, + ClassVar, + Generic, + Literal, + TypeVar, + cast, + overload, +) -import loopy as lp import numpy as np -from pymbolic.mapper import IdentityMapper, CSECachingMapperMixin -from sumpy.symbolic import pymbolic_real_norm_2 -import sumpy.symbolic as sym +from typing_extensions import Self, override + +import loopy as lp +import pymbolic.primitives as prim +from pymbolic import Expression, var +from pymbolic.mapper import CSECachingMapperMixin, IdentityMapper from pymbolic.primitives import make_sym_vector -from pymbolic import var, parse -from pytools import memoize_method -from collections import defaultdict +from pytools import keyed_memoize_method, memoize_method, obj_array + +import sumpy.symbolic as sym +from sumpy.derivative_taker import ( + DerivativeCoeffDict, + DifferentiatedExprDerivativeTaker, + ExprDerivativeTaker, + diff_derivative_coeff_dict, +) + + +if TYPE_CHECKING: + from collections.abc import Callable, Iterable, Sequence + + import sympy as sp + + from sumpy.assignment_collection import SymbolicAssignmentCollection + from sumpy.expansion.diff_op import LinearPDESystemOperator __doc__ = """ Kernel interface ---------------- -.. autoclass:: Kernel .. autoclass:: KernelArgument +.. autoclass:: ScalarKernel + :show-inheritance: +.. autoclass:: SystemKernel + :show-inheritance: Symbolic kernels ---------------- .. autoclass:: ExpressionKernel + :show-inheritance: + :members: mapper_method -PDE kernels ------------ +.. autoclass:: OneKernel + :show-inheritance: + :members: mapper_method + +Scalar PDE kernels +------------------ .. autoclass:: LaplaceKernel + :show-inheritance: + :members: mapper_method + .. autoclass:: BiharmonicKernel + :show-inheritance: + :members: mapper_method + .. autoclass:: HelmholtzKernel + :show-inheritance: + :members: mapper_method + .. autoclass:: YukawaKernel -.. autoclass:: StokesletKernel -.. autoclass:: StressletKernel -.. autoclass:: ElasticityKernel + :show-inheritance: + :members: mapper_method + +.. autoclass:: StokesComponentKernelBase + :show-inheritance: +.. autoclass:: StokesletComponentKernel + :show-inheritance: + :members: mapper_method +.. autoclass:: StressletComponentKernel + :show-inheritance: + :members: mapper_method + +.. autoclass:: ElasticityComponentKernelBase + :show-inheritance: +.. autoclass:: ElasticityComponentKernel + :show-inheritance: + :members: mapper_method +.. autoclass:: ElasticityStressComponentKernel + :show-inheritance: + :members: mapper_method .. autoclass:: LineOfCompressionKernel + :show-inheritance: + :members: mapper_method + +.. autoclass:: BrinkmanComponentKernelBase + :show-inheritance: +.. autoclass:: BrinkmanletComponentKernel + :show-inheritance: + :members: mapper_method +.. autoclass:: BrinkmanStressComponentKernel + :show-inheritance: + :members: mapper_method +.. autoclass:: HeatKernel + :show-inheritance: + :members: mapper_method + +System PDE Kernels +------------------ + +.. autoclass:: ElasticitySystemKernel + :show-inheritance: + :members: mapper_method +.. autoclass:: ElasticityStressSystemKernel + :show-inheritance: + :members: mapper_method + +.. autoclass:: StokesletSystemKernel + :show-inheritance: + :members: mapper_method +.. autoclass:: StressletSystemKernel + :show-inheritance: + :members: mapper_method + +.. autoclass:: BrinkmanletSystemKernel + :show-inheritance: + :members: mapper_method +.. autoclass:: BrinkmanStressSystemKernel + :show-inheritance: + :members: mapper_method + +.. [Pozrikidis1992] C. Pozrikidis, + *Boundary Integral and Singularity Methods for Linearized Viscous Flow*, + Cambridge University Press, 1992. + +.. [Hsiao2008] G. C. Hsiao, W. L. Wendland, + *Boundary Integral Equations*, + Springer, 2008. + +.. [Kress2013] R. Kress, + *Linear Integral Equations*, + Springer Science & Business Media, 2013. Derivatives ----------- @@ -63,378 +181,528 @@ .. autoclass:: DerivativeBase .. autoclass:: AxisTargetDerivative + :show-inheritance: + :undoc-members: + :members: mapper_method,target_array_name .. autoclass:: AxisSourceDerivative + :show-inheritance: + :members: mapper_method +.. autoclass:: DirectionalDerivative + :show-inheritance: + :members: directional_kind .. autoclass:: DirectionalSourceDerivative -.. autoclass:: DirectionalTargetDerivative + :show-inheritance: + :members: mapper_method,directional_kind Transforming kernels -------------------- +.. autoclass:: TargetPointMultiplier + :undoc-members: + :members: mapper_method,target_array_name + +.. autoclass:: ResultT + .. autoclass:: KernelMapper + :show-inheritance: .. autoclass:: KernelCombineMapper + :show-inheritance: .. autoclass:: KernelIdentityMapper + :show-inheritance: .. autoclass:: AxisSourceDerivativeRemover + :show-inheritance: .. autoclass:: AxisTargetDerivativeRemover + :show-inheritance: .. autoclass:: SourceDerivativeRemover + :show-inheritance: .. autoclass:: TargetDerivativeRemover + :show-inheritance: +.. autoclass:: TargetTransformationRemover + :show-inheritance: .. autoclass:: DerivativeCounter + :show-inheritance: """ +@dataclass(frozen=True) class KernelArgument: """ - .. attribute:: loopy_arg - - A :class:`loopy.KernelArgument` instance describing the type, - name, and other features of this kernel argument when - passed to a generated piece of code. + .. autoattribute:: loopy_arg """ - def __init__(self, loopy_arg): - self.loopy_arg = loopy_arg + loopy_arg: lp.KernelArgument + """A :class:`loopy.KernelArgument` instance describing the type, name, and + other features of this kernel argument when passed to a generated piece of + code. + """ @property - def name(self): + def name(self) -> str: return self.loopy_arg.name - def __eq__(self, other): - if id(self) == id(other): - return True - if not type(self) == KernelArgument: - return NotImplemented - if not type(other) == KernelArgument: - return NotImplemented - return self.loopy_arg == other.loopy_arg - def __ne__(self, other): - # Needed for python2 - return not self == other +# {{{ basic kernel interface + - def __hash__(self): - return (type(self), self.loopy_arg) +def _diff(expr: sym.Expr, vec: sp.Matrix, mi: tuple[int, ...]) -> sym.Expr: + """Take the derivative of an expression.""" + dim = len(mi) + assert vec.shape == (dim, 1) + for i in range(dim): + if mi[i] == 0: + continue + expr = expr.diff(vec[i], mi[i]) -# {{{ basic kernel interface + return expr + + +@dataclass(frozen=True, repr=False) +class ScalarKernel(ABC): + """Scalar kernel interface. -class Kernel: - """Basic kernel interface. + .. autoattribute:: mapper_method + .. autoattribute:: is_translation_invariant - .. attribute:: is_complex_valued - .. attribute:: is_translation_invariant - .. attribute:: dim + .. autoattribute:: dim + .. autoproperty:: is_complex_valued .. automethod:: get_base_kernel .. automethod:: replace_base_kernel + .. automethod:: get_pde_system_kernel .. automethod:: prepare_loopy_kernel .. automethod:: get_code_transformer .. automethod:: get_expression .. automethod:: postprocess_at_source .. automethod:: postprocess_at_target + .. automethod:: get_global_scaling_const .. automethod:: get_args .. automethod:: get_source_args + .. automethod:: get_pde_as_diff_op """ - def __init__(self, dim): - self.dim = dim - - # {{{ hashing/pickling/equality - - def __eq__(self, other): - if self is other: - return True - elif hash(self) != hash(other): - return False - else: - return (type(self) is type(other) - and self.__getinitargs__() == other.__getinitargs__()) - - def __ne__(self, other): - return not self.__eq__(other) - - def __hash__(self): - try: - return self.hash_value - except AttributeError: - self.hash_value = hash((type(self),) + self.__getinitargs__()) - return self.hash_value - - def update_persistent_hash(self, key_hash, key_builder): - key_hash.update(type(self).__name__.encode("utf8")) - key_builder.rec(key_hash, self.__getinitargs__()) + dim: int + """Dimension of the space the kernel is defined in.""" - def __getstate__(self): - return self.__getinitargs__() + # TODO: Allow kernels that are not translation invariant + is_translation_invariant: ClassVar[bool] = True + """A boolean flag indicating whether the kernel is translation invariant.""" + mapper_method: ClassVar[str] + """The name of the mapper method called for the kernel.""" - def __setstate__(self, state): - # Can't use trivial pickling: hash_value cache must stay unset - assert len(self.init_arg_names) == len(state) - self.__init__(*state) + @property + @abstractmethod + def is_complex_valued(self) -> bool: + """A boolean flag indicating whether this kernel is complex valued.""" + + @override + def __repr__(self) -> str: + from dataclasses import fields + + args: list[str] = [] + for f in fields(self): + value = getattr(self, f.name) + if isinstance(value, prim.ExpressionNode): + args.append(f"{f.name}={value}") + else: + args.append(f"{f.name}={value!r}") - # }}} + return f"{type(self).__name__}({', '.join(args)})" - def get_base_kernel(self): - """Return the kernel being wrapped by this one, or else - *self*. + def get_base_kernel(self) -> ScalarKernel: + """ + :returns: the kernel being wrapped by this one, or else *self*. """ return self - def replace_base_kernel(self, new_base_kernel): - """Return the base kernel being wrapped by this one, or else - *new_base_kernel*. + def replace_base_kernel(self, new_base_kernel: ScalarKernel) -> ScalarKernel: + """ + :returns: the base kernel being wrapped by this one, or else + *new_base_kernel*. """ return new_base_kernel - def prepare_loopy_kernel(self, loopy_knl): - """Apply some changes (such as registering function - manglers) to the kernel. Return the new kernel. + def get_pde_system_kernel(self) -> tuple[SystemKernel, tuple[int, ...]]: + """ + :returns: if the kernel is a component kernel of a :class:`SystemKernel`, + this returns the system kernel and the index of the kernel in that + system. + """ + raise TypeError(f"kernel {type(self)} is not part of a system") + + def prepare_loopy_kernel(self, loopy_knl: lp.TranslationUnit) -> lp.TranslationUnit: + """Apply some changes (such as registering function manglers) to the kernel. + + :returns: a new :mod:`loopy` kernel with the applied changes. """ return loopy_knl - def get_code_transformer(self): - """Return a function to postprocess the :mod:`pymbolic` - expression generated from the result of - :meth:`get_expression` on the way to code generation. + def get_code_transformer(self) -> Callable[[Expression], Expression]: + """ + :returns: a function to postprocess the :mod:`pymbolic` expression + generated from the result of :meth:`get_expression` on the way to + code generation. """ return lambda expr: expr - def get_expression(self, dist_vec): - r"""Return a :mod:`sympy` expression for the kernel.""" - raise NotImplementedError + @abstractmethod + def get_expression(self, dist_vec: sym.Matrix) -> sym.Expr: + """ + :returns: a :mod:`sympy` expression for the kernel. + """ - def _diff(self, expr, vec, mi): - """Take the derivative of an expression + @abstractmethod + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + r""" + :returns: the PDE for the kernel as a + :class:`sumpy.expansion.diff_op.LinearPDESystemOperator` object + :math:`\mathcal{L}`, where :math:`\mathcal{L}(u) = 0` is the PDE. """ - for i in range(self.dim): - if mi[i] == 0: - continue - expr = expr.diff(vec[i], mi[i]) - return expr - def postprocess_at_source(self, expr, avec): + @abstractmethod + def get_derivative_taker( + self, + dvec: sp.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None, + ) -> ExprDerivativeTaker: + """ + :returns: an :class:`~sumpy.derivative_taker.ExprDerivativeTaker` instance + that supports taking derivatives of the base kernel with respect to + *dvec*. + """ + + @overload + def postprocess_at_source( + self, expr: sym.Expr, avec: sym.Matrix + ) -> sym.Expr: ... + + @overload + def postprocess_at_source( + self, expr: ExprDerivativeTaker, avec: sym.Matrix + ) -> DifferentiatedExprDerivativeTaker: ... + + def postprocess_at_source( + self, expr: sym.Expr | ExprDerivativeTaker, avec: sym.Matrix, + ) -> sym.Expr | DifferentiatedExprDerivativeTaker: """Transform a kernel evaluation or expansion expression in a place - where the vector a (something - source) is known. ("something" may be - an expansion center or a target.) + where the vector :math:`a` (something - source) is known. ("something" may be + an expansion center or a target) The typical use of this function is to apply source-variable derivatives to the kernel. """ - from sumpy.tools import (ExprDerivativeTaker, - DifferentiatedExprDerivativeTaker) - expr_dict = {(0,)*self.dim: 1} + expr_dict: DerivativeCoeffDict = {(0,)*self.dim: 1} expr_dict = self.get_derivative_coeff_dict_at_source(expr_dict) if isinstance(expr, ExprDerivativeTaker): return DifferentiatedExprDerivativeTaker(expr, expr_dict) result = 0 for mi, coeff in expr_dict.items(): - result += coeff * self._diff(expr, avec, mi) + result += coeff * _diff(expr, avec, mi) + + assert isinstance(result, sym.Expr) return result - def postprocess_at_target(self, expr, bvec): + @overload + def postprocess_at_target( + self, expr: sym.Expr, bvec: sym.Matrix, + ) -> sym.Expr: ... + + @overload + def postprocess_at_target( + self, expr: ExprDerivativeTaker | DifferentiatedExprDerivativeTaker, + bvec: sym.Matrix, + ) -> DifferentiatedExprDerivativeTaker: ... + + def postprocess_at_target(self, + expr: + sym.Expr | ExprDerivativeTaker | DifferentiatedExprDerivativeTaker, + bvec: sym.Matrix, + ) -> sym.Expr | DifferentiatedExprDerivativeTaker: """Transform a kernel evaluation or expansion expression in a place - where the vector b (target - something) is known. ("something" may be - an expansion center or a target.) + where the vector :math:`b` (target - something) is known. ("something" may + be an expansion center or a target.) The typical use of this function is to apply target-variable derivatives to the kernel. - - :arg expr: may be a :class:`sympy.core.expr.Expr` or a - :class:`sumpy.tools.DifferentiatedExprDerivativeTaker`. """ return expr - def get_derivative_coeff_dict_at_source(self, expr_dict): - r"""Get the derivative transformation of the expression at source - represented by the dictionary expr_dict which is mapping from multi-index - `mi` to coefficient `coeff`. - Expression represented by the dictionary `expr_dict` is - :math:`\sum_{mi} \frac{\partial^mi}{x^mi}G * coeff`. Returns an - expression of the same type. + def get_derivative_coeff_dict_at_source( + self, expr_dict: DerivativeCoeffDict, + ) -> DerivativeCoeffDict: + r"""Get the derivative transformation of the expression at the source. + + The transformation is represented by the *expr_dict* which maps from a + multi-index *mi* to a coefficient *coeff*. The Expression represented by + *expr_dict* is :math:`\sum_{mi} \frac{\partial^mi}{x^mi}G * coeff`. This function is meant to be overridden by child classes where necessary. """ return expr_dict - def get_global_scaling_const(self): - r"""Return a global scaling constant of the kernel. + @abstractmethod + def get_global_scaling_const(self) -> sym.Expr: + r"""A global scaling constant of the kernel. + Typically, this ensures that the kernel is scaled so that - :math:`\mathcal L(G)(x)=C\delta(x)` with a constant of 1, where - :math:`\mathcal L` is the PDE operator associated with the kernel. - Not to be confused with *rscale*, which keeps expansion - coefficients benignly scaled. + :math:`\mathcal{L}(G)(x) = C \delta(x)` with a constant of 1, where + :math:`\mathcal{L}` is the PDE operator associated with the kernel. Not + to be confused with *rscale*, which keeps expansion coefficients + benignly scaled. """ - raise NotImplementedError - def get_args(self): - """Return list of :class:`KernelArgument` instances describing - extra arguments used by the kernel. + def get_args(self) -> Sequence[KernelArgument]: + """ + :returns: list of :class:`KernelArgument` instances describing extra + arguments used by the kernel. """ return [] - def get_source_args(self): - """Return list of :class:`KernelArgument` instances describing - extra arguments used by kernel in picking up contributions - from point sources. + def get_source_args(self) -> Sequence[KernelArgument]: + """ + :returns: list of :class:`KernelArgument` instances describing extra + arguments used by kernel in picking up contributions from point sources. """ return [] - # TODO: Allow kernels that are not translation invariant - is_translation_invariant = True -# }}} +@dataclass(frozen=True, repr=False) +class SystemKernel(ABC): + """A kernel representing a vector PDE. + + .. autoattribute:: mapper_method + + .. autoattribute:: dim + .. autoproperty:: ndim + .. autoproperty:: shape + .. automethod:: __getitem__ + .. automethod:: get_expression + .. automethod:: get_pde_as_diff_op + """ -class ExpressionKernel(Kernel): - is_complex_valued = False + mapper_method: ClassVar[str] + """The name of the mapper method called for the kernel.""" - init_arg_names = ("dim", "expression", "global_scaling_const", - "is_complex_valued") + dim: int + """Dimension of the space the kernel is defined in.""" - def __init__(self, dim, expression, global_scaling_const, - is_complex_valued): - r""" - :arg expression: A :mod:`pymbolic` expression depending on - variables *d_1* through *d_N* where *N* equals *dim*. - (These variables match what is returned from - :func:`pymbolic.primitives.make_sym_vector` with - argument `"d"`.) - :arg global_scaling_const: A constant :mod:`pymbolic` expression for the - global scaling of the kernel. Typically, this ensures that - the kernel is scaled so that :math:`\mathcal L(G)(x)=C\delta(x)` - with a constant of 1, where :math:`\mathcal L` is the PDE - operator associated with the kernel. Not to be confused with - *rscale*, which keeps expansion coefficients benignly scaled. + @property + def ndim(self) -> int: + """The number of indices in the kernel tensor.""" + return len(self.shape) + + @property + @abstractmethod + def shape(self) -> tuple[int, ...]: + """The shape of the kernel tensor.""" + + def __getitem__(self, index: tuple[int, ...], /) -> ScalarKernel: + """ + :returns: the scalar kernel at *index*. """ + if len(index) != self.ndim: + raise IndexError( + f"incorrect index size for kernel: kernel is {self.ndim}-dimensional: " + f"{index} given" + ) - # expression and global_scaling_const are pymbolic objects because - # those pickle cleanly. D'oh, sympy! + if any(not 0 <= i < n for i, n in zip(index, self.shape, strict=True)): + raise IndexError( + f"index {index} is out of bounds for kernel with shape {self.shape}" + ) + + return self.get_scalar_component(*index) + + @abstractmethod + def get_scalar_component(self, *args: int) -> ScalarKernel: + """ + :returns: the scalar kernel the indices given by *args*. + """ - Kernel.__init__(self, dim) + def get_expression(self, dist_vec: sym.Matrix) -> obj_array.ObjectArrayND[sym.Expr]: + """ + :returns: a :mod:`sympy` expression for each component the kernel. + """ + from pytools import ndindex - self.expression = expression - self.global_scaling_const = global_scaling_const - self.is_complex_valued = is_complex_valued + result = np.empty(self.shape, dtype=object) + for i in ndindex(result.shape): + result[i] = self[i].get_expression(dist_vec) - def __getinitargs__(self): - return (self.dim, self.expression, self.global_scaling_const, - self.is_complex_valued) + return result - def __repr__(self): - return f"ExprKnl{self.dim}D" + @abstractmethod + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + """ + :returns: the PDE that this kernel satisfies + (see :meth:`ScalarKernel.get_pde_as_diff_op` as the scalar alternative). + """ + return [] - def get_expression(self, scaled_dist_vec): - from sumpy.symbolic import PymbolicToSympyMapperWithSymbols - expr = PymbolicToSympyMapperWithSymbols()(self.expression) +# }}} - if self.dim != len(scaled_dist_vec): - raise ValueError("dist_vec length does not match expected dimension") - from sumpy.symbolic import Symbol - expr = expr.xreplace({ - Symbol(f"d{i}"): dist_vec_i - for i, dist_vec_i in enumerate(scaled_dist_vec) - }) +# {{{ generic expression kernel - return expr +@dataclass(frozen=True, repr=False) +class ExpressionKernel(ScalarKernel, ABC): + r""" + .. autoattribute:: expression + .. autoattribute:: global_scaling_const + """ - def get_global_scaling_const(self): - """Return a global scaling of the kernel.""" + mapper_method: ClassVar[str] = "map_expression_kernel" - from sumpy.symbolic import PymbolicToSympyMapperWithSymbols - return PymbolicToSympyMapperWithSymbols()( - self.global_scaling_const) + expression: Expression + """A :mod:`pymbolic` expression depending on variables *d_1* through *d_N* + where *N* equals *dim*. These variables match what is returned from + :func:`pymbolic.primitives.make_sym_vector` with argument `"d"`. Any + variable that is not *d* or a :class:`~sumpy.symbolic.SpatialConstant` will + be viewed as potentially spatially varying. + """ - def update_persistent_hash(self, key_hash, key_builder): - key_hash.update(type(self).__name__.encode("utf8")) - for name, value in zip(self.init_arg_names, self.__getinitargs__()): - if name in ["expression", "global_scaling_const"]: - from pymbolic.mapper.persistent_hash import ( - PersistentHashWalkMapper as PersistentHashWalkMapper) - PersistentHashWalkMapper(key_hash)(value) - else: - key_builder.rec(key_hash, value) + global_scaling_const: Expression + r"""A constant :mod:`pymbolic` expression for the global scaling of the + kernel. Typically, this ensures that the kernel is scaled so that + :math:`\mathcal{L}(G)(x)=C\delta(x)` with a constant of 1, where + :math:`\mathcal{L}` is the PDE operator associated with the kernel. Not to + be confused with *rscale*, which keeps expansion coefficients benignly + scaled. + """ - mapper_method = "map_expression_kernel" + @override + def __str__(self) -> str: + return f"ExprKnl{self.dim}D" - def get_derivative_taker(self, dvec, rscale, sac): - """Return a :class:`sumpy.tools.ExprDerivativeTaker` instance that supports - taking derivatives of the base kernel with respect to dvec. - """ - from sumpy.tools import ExprDerivativeTaker + @override + def get_expression(self, dist_vec: sym.Matrix) -> sym.Expr: + expr = sym.PymbolicToSympyMapperWithSymbols().to_expr(self.expression) + + if self.dim != len(dist_vec): + raise ValueError( + "'dist_vec' length does not match expected dimension: " + f"kernel dim is '{self.dim}' and dist_vec has length '{len(dist_vec)}'") + + return expr.xreplace({ + sym.Symbol(f"d{i}"): dist_vec_i + for i, dist_vec_i in enumerate(dist_vec) + }) + + @override + def get_global_scaling_const(self) -> sym.Expr: + return sym.PymbolicToSympyMapperWithSymbols().to_expr(self.global_scaling_const) + + @override + def get_derivative_taker( + self, + dvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None, + ) -> ExprDerivativeTaker: return ExprDerivativeTaker(self.get_expression(dvec), dvec, rscale, sac) - def get_pde_as_diff_op(self): - r""" - Returns the PDE for the kernel as a - :class:`sumpy.expansion.diff_op.LinearPDESystemOperator` object `L` - where `L(u) = 0` is the PDE. - """ + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: raise NotImplementedError -one_kernel_2d = ExpressionKernel( - dim=2, - expression=1, - global_scaling_const=1, - is_complex_valued=False) -one_kernel_3d = ExpressionKernel( - dim=3, - expression=1, - global_scaling_const=1, - is_complex_valued=False) +class OneKernel(ExpressionKernel): + def __init__(self, dim: int): + super().__init__( + dim=dim, + expression=1, + global_scaling_const=1, + ) + + @override + def __reduce__(self) -> tuple[object, ...]: + return (self.__class__, (self.dim,)) + + @property + @override + def is_complex_valued(self) -> bool: + return False + + +one_kernel_2d = OneKernel(2) +one_kernel_3d = OneKernel(3) + +# }}} # {{{ PDE kernels class LaplaceKernel(ExpressionKernel): - init_arg_names = ("dim",) + r"""A kernel for the Laplace equation (see e.g. Theorem 6.2 from [Kress2013]_). + + .. math:: + + \Delta K(\mathbf{x}, \mathbf{y}) = \delta(\mathbf{x} - \mathbf{y}). + """ + + mapper_method: ClassVar[str] = "map_laplace_kernel" - def __init__(self, dim): + def __init__(self, dim: int) -> None: # See (Kress LIE, Thm 6.2) for scaling if dim == 2: - r = pymbolic_real_norm_2(make_sym_vector("d", dim)) + r = sym.pymbolic_real_norm_2(make_sym_vector("d", dim)) expr = var("log")(r) scaling = 1/(-2*var("pi")) elif dim == 3: - r = pymbolic_real_norm_2(make_sym_vector("d", dim)) + r = sym.pymbolic_real_norm_2(make_sym_vector("d", dim)) expr = 1/r scaling = 1/(4*var("pi")) else: - raise NotImplementedError("unsupported dimensionality") + raise NotImplementedError(f"unsupported dimension: '{dim}'") - super().__init__( - dim, - expression=expr, - global_scaling_const=scaling, - is_complex_valued=False) + super().__init__(dim, expression=expr, global_scaling_const=scaling) - def __getinitargs__(self): - return (self.dim,) + @override + def __reduce__(self) -> tuple[object, ...]: + return (self.__class__, (self.dim,)) - def __repr__(self): - return f"LapKnl{self.dim}D" + @property + @override + def is_complex_valued(self) -> bool: + return False - mapper_method = "map_laplace_kernel" + @override + def __str__(self) -> str: + return f"LapKnl{self.dim}D" - def get_derivative_taker(self, dvec, rscale, sac): - """Return a :class:`sumpy.tools.ExprDerivativeTaker` instance that supports - taking derivatives of the base kernel with respect to dvec. - """ - from sumpy.tools import LaplaceDerivativeTaker + @override + def get_derivative_taker( + self, + dvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None, + ) -> ExprDerivativeTaker: + from sumpy.derivative_taker import LaplaceDerivativeTaker return LaplaceDerivativeTaker(self.get_expression(dvec), dvec, rscale, sac) - def get_pde_as_diff_op(self): - from sumpy.expansion.diff_op import make_identity_diff_op, laplacian + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import laplacian, make_identity_diff_op w = make_identity_diff_op(self.dim) return laplacian(w) class BiharmonicKernel(ExpressionKernel): - init_arg_names = ("dim",) + r"""A kernel for the biharmonic equation. + + .. math:: - def __init__(self, dim): - r = pymbolic_real_norm_2(make_sym_vector("d", dim)) + \Delta^2 K(\mathbf{x}, \mathbf{y}) = \delta(\mathbf{x} - \mathbf{y}). + """ + + mapper_method: ClassVar[str] = "map_biharmonic_kernel" + + def __init__(self, dim: int) -> None: + r = sym.pymbolic_real_norm_2(make_sym_vector("d", dim)) if dim == 2: # Ref: Farkas, Peter. Mathematical foundations for fast algorithms # for the biharmonic equation. Technical Report 765, @@ -449,123 +717,154 @@ def __init__(self, dim): expr = r scaling = -1/(8*var("pi")) else: - raise RuntimeError("unsupported dimensionality") - - super().__init__( - dim, - expression=expr, - global_scaling_const=scaling, - is_complex_valued=False) + raise NotImplementedError(f"unsupported dimension: '{dim}'") - def __getinitargs__(self): - return (self.dim,) + super().__init__(dim, expression=expr, global_scaling_const=scaling) - def __repr__(self): - return f"BiharmKnl{self.dim}D" + @override + def __reduce__(self) -> tuple[object, ...]: + return (self.__class__, (self.dim,)) - mapper_method = "map_biharmonic_kernel" + @property + @override + def is_complex_valued(self) -> bool: + return False - def get_derivative_taker(self, dvec, rscale, sac): - """Return a :class:`sumpy.tools.ExprDerivativeTaker` instance that supports - taking derivatives of the base kernel with respect to dvec. - """ - from sumpy.tools import RadialDerivativeTaker - return RadialDerivativeTaker(self.get_expression(dvec), dvec, rscale, - sac) + @override + def __str__(self) -> str: + return f"BiharmKnl{self.dim}D" - def get_pde_as_diff_op(self): - from sumpy.expansion.diff_op import make_identity_diff_op, laplacian + @override + def get_derivative_taker( + self, + dvec: sp.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None, + ) -> ExprDerivativeTaker: + from sumpy.derivative_taker import RadialDerivativeTaker + return RadialDerivativeTaker(self.get_expression(dvec), dvec, rscale, sac) + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import laplacian, make_identity_diff_op w = make_identity_diff_op(self.dim) return laplacian(laplacian(w)) +@dataclass(frozen=True, repr=False) class HelmholtzKernel(ExpressionKernel): - init_arg_names = ("dim", "helmholtz_k_name", "allow_evanescent") + r"""A kernel for the Helmholtz equation (see e.g. Example 12.14 in [Kress2013]_). - def __init__(self, dim, helmholtz_k_name="k", - allow_evanescent=False): - """ - :arg helmholtz_k_name: The argument name to use for the Helmholtz - parameter when generating functions to evaluate this kernel. - """ - k = var(helmholtz_k_name) + .. math:: + + \Delta K(\mathbf{x}, \mathbf{y}) + k^2 K(\mathbf{x}, \mathbf{y}) + = \delta(\mathbf{x} - \mathbf{y}). + + .. autoattribute:: helmholtz_k_name + .. autoattribute:: allow_evanescent + """ + + mapper_method: ClassVar[str] = "map_helmholtz_kernel" + + helmholtz_k_name: str + """The argument name to use for the Helmholtz parameter when generating + functions to evaluate this kernel. + """ + allow_evanescent: bool + + def __init__(self, + dim: int, + helmholtz_k_name: str = "k", + allow_evanescent: bool = False) -> None: + k = sym.SpatialConstant(helmholtz_k_name) # Guard against code using the old positional interface. assert isinstance(allow_evanescent, bool) if dim == 2: - r = pymbolic_real_norm_2(make_sym_vector("d", dim)) + r = sym.pymbolic_real_norm_2(make_sym_vector("d", dim)) expr = var("hankel_1")(0, k*r) scaling = var("I")/4 elif dim == 3: - r = pymbolic_real_norm_2(make_sym_vector("d", dim)) + r = sym.pymbolic_real_norm_2(make_sym_vector("d", dim)) expr = var("exp")(var("I")*k*r)/r scaling = 1/(4*var("pi")) else: - raise RuntimeError("unsupported dimensionality") + raise NotImplementedError(f"unsupported dimension: '{dim}'") - super().__init__( - dim, - expression=expr, - global_scaling_const=scaling, - is_complex_valued=True) + super().__init__(dim, expression=expr, global_scaling_const=scaling) - self.helmholtz_k_name = helmholtz_k_name - self.allow_evanescent = allow_evanescent + object.__setattr__(self, "helmholtz_k_name", helmholtz_k_name) + object.__setattr__(self, "allow_evanescent", allow_evanescent) - def __getinitargs__(self): - return (self.dim, self.helmholtz_k_name, - self.allow_evanescent) + @override + def __reduce__(self) -> tuple[object, ...]: + return ( + self.__class__, + (self.dim, self.helmholtz_k_name, self.allow_evanescent), + ) - def update_persistent_hash(self, key_hash, key_builder): - key_hash.update(type(self).__name__.encode("utf8")) - key_builder.rec(key_hash, (self.dim, self.helmholtz_k_name, - self.allow_evanescent)) + @property + @override + def is_complex_valued(self) -> bool: + return True - def __repr__(self): + @override + def __str__(self) -> str: return f"HelmKnl{self.dim}D({self.helmholtz_k_name})" - def prepare_loopy_kernel(self, loopy_knl): + @override + def prepare_loopy_kernel(self, loopy_knl: lp.TranslationUnit) -> lp.TranslationUnit: from sumpy.codegen import register_bessel_callables return register_bessel_callables(loopy_knl) - def get_args(self): - if self.allow_evanescent: - k_dtype = np.complex128 - else: - k_dtype = np.float64 - + @override + def get_args(self) -> Sequence[KernelArgument]: + k_dtype = np.complex128 if self.allow_evanescent else np.float64 return [ - KernelArgument( - loopy_arg=lp.ValueArg(self.helmholtz_k_name, k_dtype), - )] - - mapper_method = "map_helmholtz_kernel" - - def get_derivative_taker(self, dvec, rscale, sac): - """Return a :class:`sumpy.tools.ExprDerivativeTaker` instance that supports - taking derivatives of the base kernel with respect to dvec. - """ - from sumpy.tools import HelmholtzDerivativeTaker + KernelArgument(loopy_arg=lp.ValueArg(self.helmholtz_k_name, k_dtype)), + ] + + @override + def get_derivative_taker( + self, + dvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None, + ) -> ExprDerivativeTaker: + from sumpy.derivative_taker import HelmholtzDerivativeTaker return HelmholtzDerivativeTaker(self.get_expression(dvec), dvec, rscale, sac) - def get_pde_as_diff_op(self): - from sumpy.expansion.diff_op import make_identity_diff_op, laplacian + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import laplacian, make_identity_diff_op w = make_identity_diff_op(self.dim) k = sym.Symbol(self.helmholtz_k_name) - return (laplacian(w) + k**2 * w) + return laplacian(w) + k**2 * w +@dataclass(frozen=True, repr=False) class YukawaKernel(ExpressionKernel): - init_arg_names = ("dim", "yukawa_lambda_name") + r"""A kernel for the Yukawa equation. - def __init__(self, dim, yukawa_lambda_name="lam"): - """ - :arg yukawa_lambda_name: The argument name to use for the Yukawa - parameter when generating functions to evaluate this kernel. - """ - lam = var(yukawa_lambda_name) + .. math:: + + \Delta K(\mathbf{x}, \mathbf{y}) - \lambda^2 K(\mathbf{x}, \mathbf{y}) + = \delta(\mathbf{x} - \mathbf{y}). + + .. autoattribute:: yukawa_lambda_name + """ + + mapper_method: ClassVar[str] = "map_yukawa_kernel" + + yukawa_lambda_name: str + """The argument name to use for the Yukawa parameter when generating + functions to evaluate this kernel. + """ + + def __init__(self, dim: int, yukawa_lambda_name: str = "lam") -> None: + lam = sym.SpatialConstant(yukawa_lambda_name) # NOTE: The Yukawa kernel is given by [1] # -1/(2 pi)**(n/2) * (lam/r)**(n/2-1) * K(n/2-1, lam r) @@ -576,13 +875,13 @@ def __init__(self, dim, yukawa_lambda_name="lam"): # [3] https://dlmf.nist.gov/10.47#E2 # [4] https://dlmf.nist.gov/10.49 - r = pymbolic_real_norm_2(make_sym_vector("d", dim)) + r = sym.pymbolic_real_norm_2(make_sym_vector("d", dim)) if dim == 2: # NOTE: transform K(0, lam r) into a Hankel function using [2] expr = var("hankel_1")(0, var("I")*lam*r) - scaling_for_K0 = var("pi")/2*var("I") # noqa: N806 + scaling_for_K0 = var("pi")/2*var("I") # ruff:ignore[non-lowercase-variable-in-function] - scaling = -1/(2*var("pi")) * scaling_for_K0 + scaling = 1/(2*var("pi")) * scaling_for_K0 elif dim == 3: # NOTE: to get the expression, we do the following and simplify # 1. express K(1/2, lam r) as a modified spherical Bessel function @@ -590,375 +889,1375 @@ def __init__(self, dim, yukawa_lambda_name="lam"): # 2. or use (AS 10.2.17) directly expr = var("exp")(-lam*r) / r - scaling = -1/(4 * var("pi")**2) + scaling = 1/(4 * var("pi")) else: - raise RuntimeError("unsupported dimensionality") + raise NotImplementedError(f"unsupported dimension: '{dim}'") - super().__init__( - dim, - expression=expr, - global_scaling_const=scaling, - is_complex_valued=True) - - self.yukawa_lambda_name = yukawa_lambda_name + super().__init__(dim, expression=expr, global_scaling_const=scaling) + object.__setattr__(self, "yukawa_lambda_name", yukawa_lambda_name) - def __getinitargs__(self): - return (self.dim, self.yukawa_lambda_name) + @override + def __reduce__(self) -> tuple[object, ...]: + return (self.__class__, (self.dim, self.yukawa_lambda_name)) - def update_persistent_hash(self, key_hash, key_builder): - key_hash.update(type(self).__name__.encode("utf8")) - key_builder.rec(key_hash, (self.dim, self.yukawa_lambda_name)) + @property + @override + def is_complex_valued(self) -> bool: + # FIXME: 2D uses Hankel functions (complex-valued); 3D uses real exp + return True - def __repr__(self): + @override + def __str__(self) -> str: return f"YukKnl{self.dim}D({self.yukawa_lambda_name})" - def prepare_loopy_kernel(self, loopy_knl): + @override + def prepare_loopy_kernel(self, loopy_knl: lp.TranslationUnit) -> lp.TranslationUnit: from sumpy.codegen import register_bessel_callables return register_bessel_callables(loopy_knl) - def get_args(self): + @override + def get_args(self) -> Sequence[KernelArgument]: return [ - KernelArgument( - loopy_arg=lp.ValueArg(self.yukawa_lambda_name, np.float64), - )] - - mapper_method = "map_yukawa_kernel" - - def get_derivative_taker(self, dvec, rscale, sac): - """Return a :class:`sumpy.tools.ExprDerivativeTaker` instance that supports - taking derivatives of the base kernel with respect to dvec. - """ - from sumpy.tools import HelmholtzDerivativeTaker + KernelArgument(loopy_arg=lp.ValueArg(self.yukawa_lambda_name, np.float64)), + ] + + @override + def get_derivative_taker( + self, + dvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None, + ) -> ExprDerivativeTaker: + from sumpy.derivative_taker import HelmholtzDerivativeTaker return HelmholtzDerivativeTaker(self.get_expression(dvec), dvec, rscale, sac) - def get_pde_as_diff_op(self): - from sumpy.expansion.diff_op import make_identity_diff_op, laplacian + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import laplacian, make_identity_diff_op + w = make_identity_diff_op(self.dim) lam = sym.Symbol(self.yukawa_lambda_name) - return (laplacian(w) - lam**2 * w) + return laplacian(w) - lam**2 * w -class ElasticityKernel(ExpressionKernel): - init_arg_names = ("dim", "icomp", "jcomp", "viscosity_mu", "poisson_ratio") +@dataclass(frozen=True, repr=False) +class ElasticityComponentKernelBase(ExpressionKernel): + r"""Base kernel class for the linear elasticity (Navier-Cauchy) equations + (see e.g. Section 2.2 in [Hsiao2008]_). - def __new__(cls, dim, icomp, jcomp, viscosity_mu="mu", poisson_ratio="nu"): - if poisson_ratio == 0.5: - instance = super().__new__(StokesletKernel) - else: - instance = super().__new__(cls) - return instance + .. autoattribute:: viscosity_mu_name + .. autoattribute:: poisson_ratio_name + """ - def __init__(self, dim, icomp, jcomp, viscosity_mu="mu", poisson_ratio="nu"): - r""" - :arg viscosity_mu: The argument name to use for - dynamic viscosity :math:`\mu` when generating functions to - evaluate this kernel. Can also be a numeric value. - :arg poisson_ratio: The argument name to use for - Poisson's ratio :math:`\nu` when generating functions to - evaluate this kernel. Can also be a numeric value. - """ - if isinstance(viscosity_mu, str): - mu = parse(viscosity_mu) - else: - mu = viscosity_mu - if isinstance(poisson_ratio, str): - nu = parse(poisson_ratio) - else: - nu = poisson_ratio + viscosity_mu_name: str + r"""The argument name to use for the dynamic viscosity :math:`\mu` when + generating functions to evaluate this kernel. + """ + poisson_ratio_name: str + r"""The argument name to use for Poisson's ratio :math:`\nu` when generating + functions to evaluate this kernel. + """ + + @property + @override + def is_complex_valued(self) -> bool: + return False + + @memoize_method + @override + def get_args(self) -> Sequence[KernelArgument]: + return [ + KernelArgument(loopy_arg=lp.ValueArg(self.viscosity_mu_name, np.float64)), + KernelArgument(loopy_arg=lp.ValueArg(self.poisson_ratio_name, np.float64)), + ] + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import laplacian, make_identity_diff_op + + w = make_identity_diff_op(self.dim) + return laplacian(laplacian(w)) + + +@dataclass(frozen=True, repr=False) +class ElasticityComponentKernel(ElasticityComponentKernelBase): + r"""The displacement kernel for the linear elasticity (Navier-Cauchy) equations + (see e.g. Section 2.2 in [Hsiao2008]_). + + .. math:: + + \mu \Delta K_{ij}(\mathbf{x}, \mathbf{y}) + + \frac{\mu}{1 - 2 \nu} \nabla (\nabla \cdot K_{ij}(\mathbf{x}, \mathbf{y})) + = \delta_{ij} \delta(\mathbf{x} - \mathbf{y}). + + .. autoattribute:: icomp + .. autoattribute:: jcomp + """ + + mapper_method: ClassVar[str] = "map_elasticity_kernel" + + icomp: int + """Component index for the kernel.""" + jcomp: int + """Component index for the kernel.""" + + def __init__(self, + dim: int, + icomp: int, + jcomp: int, + viscosity_mu_name: str = "mu", + poisson_ratio_name: str = "nu") -> None: + if not isinstance(viscosity_mu_name, str): + raise TypeError( + f"'viscosity_mu_name' is not a str: {type(viscosity_mu_name)}" + ) + + if not isinstance(poisson_ratio_name, str): + raise TypeError( + f"'poisson_ratio_name' is not a str: {type(poisson_ratio_name)}" + ) + + mu = sym.SpatialConstant(viscosity_mu_name) + nu = sym.SpatialConstant(poisson_ratio_name) + + d = make_sym_vector("d", dim) + r = sym.pymbolic_real_norm_2(d) + delta_ij = 1 if icomp == jcomp else 0 if dim == 2: - d = make_sym_vector("d", dim) - r = pymbolic_real_norm_2(d) # See (Berger and Karageorghis 2001) - expr = ( - -var("log")(r)*((3 - 4 * nu) if icomp == jcomp else 0) - + # noqa: W504 - d[icomp]*d[jcomp]/r**2 - ) + expr = -var("log")(r)*(3 - 4 * nu)*delta_ij + d[icomp]*d[jcomp]/r**2 scaling = -1/(8*var("pi")*(1 - nu)*mu) - elif dim == 3: - d = make_sym_vector("d", dim) - r = pymbolic_real_norm_2(d) # Kelvin solution - expr = ( - (1/r)*((3 - 4*nu) if icomp == jcomp else 0) - + # noqa: W504 - d[icomp]*d[jcomp]/r**3 - ) + expr = (1/r)*(3 - 4*nu)*delta_ij + d[icomp]*d[jcomp]/r**3 scaling = -1/(16*var("pi")*(1 - nu)*mu) - else: - raise RuntimeError("unsupported dimensionality") + raise NotImplementedError(f"unsupported dimension: '{dim}'") - self.viscosity_mu = mu - self.poisson_ratio = nu - self.icomp = icomp - self.jcomp = jcomp + super().__init__(dim, expression=expr, global_scaling_const=scaling, + viscosity_mu_name=viscosity_mu_name, + poisson_ratio_name=poisson_ratio_name) - super().__init__( - dim, - expression=expr, - global_scaling_const=scaling, - is_complex_valued=False) + object.__setattr__(self, "icomp", icomp) + object.__setattr__(self, "jcomp", jcomp) - def __getinitargs__(self): - return (self.dim, self.icomp, self.jcomp, self.viscosity_mu, - self.poisson_ratio) + @override + def __str__(self) -> str: + return ( + f"ElasticityKnl{self.dim}D_{self.icomp}{self.jcomp}" + f"({self.viscosity_mu_name}, {self.poisson_ratio_name})") + @override def __reduce__(self): - return (ElasticityKernel, self.__getinitargs__()) - - def update_persistent_hash(self, key_hash, key_builder): - from pymbolic.mapper.persistent_hash import PersistentHashWalkMapper - key_hash.update(type(self).__name__.encode()) - key_builder.rec(key_hash, - (self.dim, self.icomp, self.jcomp)) - mapper = PersistentHashWalkMapper(key_hash) - mapper(self.viscosity_mu) - mapper(self.poisson_ratio) - - def __repr__(self): - return f"ElasticityKnl{self.dim}D_{self.icomp}{self.jcomp}" - - @memoize_method - def get_args(self): - from sumpy.tools import get_all_variables - variables = get_all_variables(self.viscosity_mu) - res = [] - for v in variables: - res.append(KernelArgument(loopy_arg=lp.ValueArg(v.name, np.float64))) - return res + self.get_source_args() + return ( + type(self), + (self.dim, self.icomp, self.jcomp, + self.viscosity_mu_name, + self.poisson_ratio_name)) + @override @memoize_method - def get_source_args(self): - from sumpy.tools import get_all_variables - variables = get_all_variables(self.poisson_ratio) - res = [] - for v in variables: - res.append(KernelArgument(loopy_arg=lp.ValueArg(v.name, np.float64))) - return res - - mapper_method = "map_elasticity_kernel" - - def get_pde_as_diff_op(self): - from sumpy.expansion.diff_op import make_identity_diff_op, laplacian - w = make_identity_diff_op(self.dim) - return laplacian(laplacian(w)) + def get_pde_system_kernel(self) -> tuple[SystemKernel, tuple[int, ...]]: + return ElasticitySystemKernel( + self.dim, + viscosity_mu_name=self.viscosity_mu_name, + poisson_ratio_name=self.poisson_ratio_name + ), (self.icomp, self.jcomp) -class StokesletKernel(ElasticityKernel): - def __new__(cls, dim, icomp, jcomp, viscosity_mu="mu", poisson_ratio="0.5"): - return object.__new__(cls) +@dataclass(frozen=True, repr=False) +class ElasticityStressComponentKernel(ElasticityComponentKernelBase): + r"""The stress kernel for the linear elasticity (Navier-Cauchy) equations + (see e.g. Section 2.2 in [Hsiao2008]_). - def __init__(self, dim, icomp, jcomp, viscosity_mu="mu", poisson_ratio=0.5): - super().__init__(dim, icomp, jcomp, viscosity_mu, poisson_ratio) + .. math:: - def __repr__(self): - return f"StokesletKnl{self.dim}D_{self.icomp}{self.jcomp}" + K_{ijk}(\mathbf{x}, \mathbf{y}) = + \lambda \partial_l K_{kl}(\mathbf{x}, \mathbf{y}) \delta_{ij} + + \mu (\partial_j K_{ik}(\mathbf{x}, \mathbf{y}) + + \partial_i K_{jk}(\mathbf{x}, \mathbf{y})), + where the two-index :math:`K_{ij}` is the + :class:`~sumpy.kernel.ElasticityComponentKernel`. -class StressletKernel(ExpressionKernel): - init_arg_names = ("dim", "icomp", "jcomp", "kcomp", "viscosity_mu") + .. autoattribute:: icomp + .. autoattribute:: jcomp + .. autoattribute:: kcomp + """ - def __init__(self, dim, icomp, jcomp, kcomp, viscosity_mu="mu"): - r""" - :arg viscosity_mu: The argument name to use for - dynamic viscosity :math:`\mu` the then generating functions to - evaluate this kernel. - """ - # mu is unused but kept for consistency with the Stokeslet. - if isinstance(viscosity_mu, str): - mu = parse(viscosity_mu) - else: - mu = viscosity_mu + mapper_method: ClassVar[str] = "map_elasticity_stress_kernel" + + icomp: int + """Component index for the kernel.""" + jcomp: int + """Component index for the kernel.""" + kcomp: int + """Component index for the kernel.""" + + def __init__(self, + dim: int, + icomp: int, + jcomp: int, + kcomp: int, + viscosity_mu_name: str = "mu", + poisson_ratio_name: str = "nu") -> None: + nu = sym.SpatialConstant(poisson_ratio_name) + + d = make_sym_vector("d", dim) + r = sym.pymbolic_real_norm_2(d) + delta_ij = 1 if icomp == jcomp else 0 + delta_ik = 1 if icomp == kcomp else 0 + delta_jk = 1 if jcomp == kcomp else 0 if dim == 2: - d = make_sym_vector("d", dim) - r = pymbolic_real_norm_2(d) expr = ( - d[icomp]*d[jcomp]*d[kcomp]/r**4 - ) - scaling = 1/(var("pi")) - + (1 - 2*nu) * ( + d[icomp] / r**2 * delta_jk + + d[jcomp] / r**2 * delta_ik + - d[kcomp] / r**2 * delta_ij) + + 3 * d[icomp] * d[jcomp] * d[kcomp] / r**4 + ) + scaling = -1/(4*var("pi")*(1 - nu)) elif dim == 3: - d = make_sym_vector("d", dim) - r = pymbolic_real_norm_2(d) expr = ( - d[icomp]*d[jcomp]*d[kcomp]/r**5 - ) - scaling = 3/(4*var("pi")) - + (1 - 2*nu) * ( + d[icomp] / r**3 * delta_jk + + d[jcomp] / r**3 * delta_ik + - d[kcomp] / r**3 * delta_ij) + + 3 * d[icomp] * d[jcomp] * d[kcomp] / r**5 + ) + scaling = -1/(8*var("pi")*(1 - nu)) else: - raise RuntimeError("unsupported dimensionality") + raise NotImplementedError(f"unsupported dimension: '{dim}'") - self.icomp = icomp - self.jcomp = jcomp - self.kcomp = kcomp - self.viscosity_mu = mu + super().__init__(dim, expression=expr, global_scaling_const=scaling, + viscosity_mu_name=viscosity_mu_name, + poisson_ratio_name=poisson_ratio_name) + + object.__setattr__(self, "icomp", icomp) + object.__setattr__(self, "jcomp", jcomp) + object.__setattr__(self, "kcomp", kcomp) + + @override + def __str__(self) -> str: + return ( + f"ElasticityStressKnl{self.dim}D_{self.icomp}{self.jcomp}{self.kcomp}" + f"({self.viscosity_mu_name}, {self.poisson_ratio_name})") + + @override + def __reduce__(self): + return ( + type(self), + (self.dim, self.icomp, self.jcomp, self.kcomp, + self.viscosity_mu_name, + self.poisson_ratio_name)) + + @override + @memoize_method + def get_pde_system_kernel(self) -> tuple[SystemKernel, tuple[int, ...]]: + return ElasticityStressSystemKernel( + self.dim, + viscosity_mu_name=self.viscosity_mu_name, + poisson_ratio_name=self.poisson_ratio_name + ), (self.icomp, self.jcomp, self.kcomp) - super().__init__( - dim, - expression=expr, - global_scaling_const=scaling, - is_complex_valued=False) - def __getinitargs__(self): - return (self.dim, self.icomp, self.jcomp, self.kcomp, self.viscosity_mu) +@dataclass(frozen=True, repr=False) +class StokesComponentKernelBase(ExpressionKernel): + """Base class for kernels of the Stokes equations + (see e.g. Chapter 2 in [Pozrikidis1992]_). - def update_persistent_hash(self, key_hash, key_builder): - key_hash.update(type(self).__name__.encode()) - key_builder.rec(key_hash, (self.dim, self.icomp, self.jcomp, self.kcomp)) + .. autoattribute:: viscosity_mu_name + """ - from pymbolic.mapper.persistent_hash import PersistentHashWalkMapper - mapper = PersistentHashWalkMapper(key_hash) - mapper(self.viscosity_mu) + viscosity_mu_name: str + r"""The argument name to use for the dynamic viscosity :math:`\mu` when + generating functions to evaluate this kernel. + """ - def __repr__(self): - return f"StressletKnl{self.dim}D_{self.icomp}{self.jcomp}{self.kcomp}" + @property + @override + def is_complex_valued(self) -> bool: + return False @memoize_method - def get_args(self): - from sumpy.tools import get_all_variables - variables = get_all_variables(self.viscosity_mu) + @override + def get_args(self) -> Sequence[KernelArgument]: return [ - KernelArgument(loopy_arg=lp.ValueArg(v.name, np.float64)) - for v in variables] + KernelArgument(loopy_arg=lp.ValueArg(self.viscosity_mu_name, np.float64)), + ] - mapper_method = "map_stresslet_kernel" + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import laplacian, make_identity_diff_op - def get_pde_as_diff_op(self): - from sumpy.expansion.diff_op import make_identity_diff_op, laplacian w = make_identity_diff_op(self.dim) return laplacian(laplacian(w)) -class LineOfCompressionKernel(ExpressionKernel): - """A kernel for the line of compression or dilatation of constant strength - along the axis "axis" from zero to negative infinity. This is used for the - explicit solution to half-space Elasticity problem. See [1] for details. +@dataclass(frozen=True, repr=False) +class StokesletComponentKernel(StokesComponentKernelBase): + r"""The velocity kernel for the Stokes equations (see e.g. Chapter 2 in + [Pozrikidis1992]_). - [1]: Mindlin, R.: Force at a Point in the Interior of a Semi-Infinite Solid - https://doi.org/10.1063/1.1745385 + .. math:: + + \begin{cases} + -\mu \Delta K_{ij}(\mathbf{x}, \mathbf{y}) + + \nabla_i P_j(\mathbf{x}, \mathbf{y}) + = \delta_{ij} \delta(\mathbf{x} - \mathbf{y}), \\ + \nabla_i K_{ij}(\mathbf{x}, \mathbf{y}) = 0, \\ + \end{cases} + + where pressure kernel :math:`P_j = \partial_j K` is the derivative of the + Laplace kernel. This kernel is often called the Stokeslet or the Oseen-Burgers + tensor and it represents the velocity field. + + .. autoattribute:: icomp + .. autoattribute:: jcomp """ - init_arg_names = ("dim", "axis", "viscosity_mu", "poisson_ratio") - def __init__(self, dim=3, axis=2, viscosity_mu="mu", poisson_ratio="nu"): - r""" - :arg axis: axis number defaulting to 2 for the z axis. - :arg viscosity_mu: The argument name to use for - dynamic viscosity :math:`\mu` when generating functions to - evaluate this kernel. Can also be a numeric value. - :arg poisson_ratio: The argument name to use for - Poisson's ratio :math:`\nu` when generating functions to - evaluate this kernel. Can also be a numeric value. - """ - if isinstance(viscosity_mu, str): - mu = parse(viscosity_mu) - else: - mu = viscosity_mu - if isinstance(poisson_ratio, str): - nu = parse(poisson_ratio) - else: - nu = poisson_ratio + mapper_method: ClassVar[str] = "map_stokeslet_kernel" - if dim == 3: - d = make_sym_vector("d", dim) - r = pymbolic_real_norm_2(d) - # Kelvin solution - expr = d[axis] * var("log")(r + d[axis]) - r - scaling = (1 - 2*nu)/(4*var("pi")*mu) - else: - raise RuntimeError("unsupported dimensionality") + icomp: int + """Component index for the kernel.""" + jcomp: int + """Component index for the kernel.""" - self.viscosity_mu = mu - self.poisson_ratio = nu - self.axis = axis + def __init__(self, + dim: int, + icomp: int, + jcomp: int, + viscosity_mu_name: str = "mu") -> None: + if not isinstance(viscosity_mu_name, str): + raise TypeError( + f"'viscosity_mu_name' is not a str: {type(viscosity_mu_name)}" + ) + mu = sym.SpatialConstant(viscosity_mu_name) - super().__init__( - dim, - expression=expr, - global_scaling_const=scaling, - is_complex_valued=False) + d = make_sym_vector("d", dim) + r = sym.pymbolic_real_norm_2(d) + delta_ij = 1 if icomp == jcomp else 0 + + if dim == 2: + expr = -var("log")(r)*delta_ij + d[icomp]*d[jcomp]/r**2 + scaling = -1/(4*var("pi")*mu) + elif dim == 3: + expr = (1/r)*delta_ij + d[icomp]*d[jcomp]/r**3 + scaling = -1/(8*var("pi")*mu) + else: + raise NotImplementedError(f"unsupported dimension: '{dim}'") - def __getinitargs__(self): - return (self.dim, self.axis, self.viscosity_mu, self.poisson_ratio) + super().__init__(dim, expression=expr, global_scaling_const=scaling, + viscosity_mu_name=viscosity_mu_name) + object.__setattr__(self, "icomp", icomp) + object.__setattr__(self, "jcomp", jcomp) - def update_persistent_hash(self, key_hash, key_builder): - from pymbolic.mapper.persistent_hash import PersistentHashWalkMapper - key_hash.update(type(self).__name__.encode()) - key_builder.rec(key_hash, (self.dim, self.axis)) - mapper = PersistentHashWalkMapper(key_hash) - mapper(self.viscosity_mu) - mapper(self.poisson_ratio) + @override + def __str__(self) -> str: + return ( + f"StokesletKnl{self.dim}D_{self.icomp}{self.jcomp}" + f"({self.viscosity_mu_name})") - def __repr__(self): - return f"LineOfCompressionKnl{self.dim}D_{self.axis}" + @override + def __reduce__(self): + return ( + type(self), + (self.dim, self.icomp, self.jcomp, self.viscosity_mu_name)) + @override @memoize_method - def get_args(self): - from sumpy.tools import get_all_variables - variables = list(get_all_variables(self.viscosity_mu)) \ - + list(get_all_variables(self.poisson_ratio)) - res = [] - for v in variables: - res.append(KernelArgument(loopy_arg=lp.ValueArg(v.name, np.float64))) - return res - - mapper_method = "map_line_of_compression_kernel" - - def get_pde_as_diff_op(self): - from sumpy.expansion.diff_op import make_identity_diff_op, laplacian - w = make_identity_diff_op(self.dim) - return laplacian(w) + def get_pde_system_kernel(self) -> tuple[SystemKernel, tuple[int, ...]]: + return StokesletSystemKernel( + self.dim, + viscosity_mu_name=self.viscosity_mu_name, + ), (self.icomp, self.jcomp) -# }}} +@dataclass(frozen=True, repr=False) +class StressletComponentKernel(StokesComponentKernelBase): + r"""The stress kernel for the Stokes equations (see e.g. Chapter 2 in + [Pozrikidis1992]_). + .. math:: -# {{{ a kernel defined as wrapping another one--e.g., derivatives + K_{ijk}(\mathbf{x}, \mathbf{y}) = + -P_j \delta_{ik} + + \mu (\partial_k K_{ij} + \partial_i K_{kj}) -class KernelWrapper(Kernel): - def __init__(self, inner_kernel): - Kernel.__init__(self, inner_kernel.dim) - self.inner_kernel = inner_kernel + where the two-index :math:`K_{ij}` is the + :class:`~sumpy.kernel.StokesletComponentKernel`. This kernel is often + called the Stresslet and it represents the stress tensor. - def get_base_kernel(self): - return self.inner_kernel.get_base_kernel() + .. autoattribute:: icomp + .. autoattribute:: jcomp + .. autoattribute:: kcomp + """ + mapper_method: ClassVar[str] = "map_stresslet_kernel" + + icomp: int + """Component index for the kernel.""" + jcomp: int + """Component index for the kernel.""" + kcomp: int + """Component index for the kernel.""" + + def __init__(self, + dim: int, + icomp: int, + jcomp: int, + kcomp: int, + viscosity_mu_name: str = "mu") -> None: + # mu is unused but kept for consistency with the Stokeslet. + if not isinstance(viscosity_mu_name, str): + raise TypeError( + f"'viscosity_mu_name' is not a str: {type(viscosity_mu_name)}" + ) - def prepare_loopy_kernel(self, loopy_knl): - return self.inner_kernel.prepare_loopy_kernel(loopy_knl) + d = make_sym_vector("d", dim) + r = sym.pymbolic_real_norm_2(d) + + if dim == 2: + expr = d[icomp]*d[jcomp]*d[kcomp]/r**4 + scaling = 1/(var("pi")) + elif dim == 3: + expr = d[icomp]*d[jcomp]*d[kcomp]/r**5 + scaling = 3/(4*var("pi")) + else: + raise NotImplementedError(f"unsupported dimension: '{dim}'") + + super().__init__(dim, expression=expr, global_scaling_const=scaling, + viscosity_mu_name=viscosity_mu_name) + + object.__setattr__(self, "icomp", icomp) + object.__setattr__(self, "jcomp", jcomp) + object.__setattr__(self, "kcomp", kcomp) + + @override + def __reduce__(self) -> tuple[object, ...]: + return ( + self.__class__, + (self.dim, self.icomp, self.jcomp, self.kcomp, + self.viscosity_mu_name)) + + @override + def __str__(self) -> str: + return ( + f"StressletKnl{self.dim}D_{self.icomp}{self.jcomp}{self.kcomp}" + f"({self.viscosity_mu_name})") + + @override + @memoize_method + def get_pde_system_kernel(self) -> tuple[SystemKernel, tuple[int, ...]]: + return StressletSystemKernel( + self.dim, + viscosity_mu_name=self.viscosity_mu_name, + ), (self.icomp, self.jcomp, self.kcomp) + + +@dataclass(frozen=True, repr=False) +class LineOfCompressionKernel(ExpressionKernel): + """A kernel for the line of compression or dilatation of constant strength + along an axis from zero to negative infinity. + + This is used for the explicit solution to half-space linear elasticity problem. + See [Mindlin1936]_ for details. + + .. [Mindlin1936] R. D. Mindlin (1936). + *Force at a Point in the Interior of a Semi-Infinite Solid*. + Physics. 7 (5): 195-202. + `doi:10.1063/1.1745385 `__. + + .. autoattribute:: axis + .. autoattribute:: viscosity_mu_name + .. autoattribute:: poisson_ratio_name + """ + + mapper_method: ClassVar[str] = "map_line_of_compression_kernel" + + axis: int + """Axis number (defaulting to 2 for the z axis).""" + + viscosity_mu_name: str + r"""The argument name to use for the dynamic viscosity :math:`\mu` when + generating functions to evaluate this kernel. + """ + poisson_ratio_name: str + r"""The argument name to use for Poisson's ratio :math:`\nu` when + generating functions to evaluate this kernel. + """ + + def __init__(self, + dim: int = 3, + axis: int = 2, + viscosity_mu_name: str = "mu", + poisson_ratio_name: str = "nu" + ) -> None: + if not isinstance(viscosity_mu_name, str): + raise TypeError( + f"'viscosity_mu_name' is not a str: {type(viscosity_mu_name)}" + ) + + if not isinstance(poisson_ratio_name, str): + raise TypeError( + f"'poisson_ratio_name' is not a str: {type(poisson_ratio_name)}" + ) + + mu = sym.SpatialConstant(viscosity_mu_name) + nu = sym.SpatialConstant(poisson_ratio_name) + + if dim == 3: + d = make_sym_vector("d", dim) + r = sym.pymbolic_real_norm_2(d) + + # Kelvin solution + expr = d[axis] * var("log")(r + d[axis]) - r + scaling = (1 - 2*nu)/(4*var("pi")*mu) + else: + raise NotImplementedError(f"unsupported dimension: '{dim}'") + + super().__init__(dim, expression=expr, global_scaling_const=scaling) + + object.__setattr__(self, "axis", axis) + object.__setattr__(self, "viscosity_mu_name", viscosity_mu_name) + object.__setattr__(self, "poisson_ratio_name", poisson_ratio_name) + + @override + def __str__(self) -> str: + return ( + f"LineOfCompressionKnl{self.dim}D_{self.axis}" + f"({self.viscosity_mu_name}, {self.poisson_ratio_name})") + + @override + def __reduce__(self) -> tuple[object, ...]: + return ( + self.__class__, + (self.dim, self.axis, + self.viscosity_mu_name, self.poisson_ratio_name)) + + @property + @override + def is_complex_valued(self) -> bool: + return False + + @memoize_method + @override + def get_args(self) -> Sequence[KernelArgument]: + return [ + KernelArgument(loopy_arg=lp.ValueArg(self.viscosity_mu_name, np.float64)), + KernelArgument(loopy_arg=lp.ValueArg(self.poisson_ratio_name, np.float64)), + ] + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import laplacian, make_identity_diff_op + + w = make_identity_diff_op(self.dim) + return laplacian(w) + + +@dataclass(frozen=True, repr=False) +class BrinkmanComponentKernelBase(ExpressionKernel): + """Base class for the Brinkman equations. + + .. autoattribute:: viscosity_mu_name + .. autoattribute:: darcy_impermeability_name + """ + + viscosity_mu_name: str + """The argument name to use for the dynamic viscosity when generating + functions to evaluate this kernel. + """ + darcy_impermeability_name: str + """The argument name to use for the Darcy impermeability when generating + functions to evaluate this kernel. + """ + + @property + @override + def is_complex_valued(self) -> bool: + # FIXME: 2D uses Hankel functions (complex-valued); 3D uses real exp + return self.dim == 2 + + @override + def prepare_loopy_kernel(self, loopy_knl: lp.TranslationUnit) -> lp.TranslationUnit: + from sumpy.codegen import register_bessel_callables + return register_bessel_callables(loopy_knl) + + @memoize_method + @override + def get_args(self) -> Sequence[KernelArgument]: + return [ + KernelArgument(loopy_arg=lp.ValueArg(self.viscosity_mu_name, np.float64)), + KernelArgument(loopy_arg=lp.ValueArg(self.darcy_impermeability_name, np.float64)), # ruff:ignore[line-too-long] + ] + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import laplacian, make_identity_diff_op + + w = make_identity_diff_op(self.dim) + k = sym.Symbol(self.darcy_impermeability_name) + + return laplacian(laplacian(w) - k**2 * w) + + +@dataclass(frozen=True, repr=False) +class BrinkmanletComponentKernel(BrinkmanComponentKernelBase): + r"""The velocity kernel for the Brinkman equations. + + .. math:: + + \begin{cases} + -\mu (\Delta K_{ij}(\mathbf{x}, \mathbf{y}) + - k^2 K_{ij}(\mathbf{x}, \mathbf{y})) + + \nabla_i P_j(\mathbf{x}, \mathbf{y}) + = \delta_{ij}(\mathbf{x} - \mathbf{y}), \\ + \nabla_i K_{ij} = 0, + \end{cases} + + where :math:`P_j` is the pressure kernel. + + .. autoattribute:: icomp + .. autoattribute:: jcomp + """ + + mapper_method: ClassVar[str] = "map_brinkmanlet_kernel" + + icomp: int + """Component index for the kernel.""" + jcomp: int + """Component index for the kernel.""" + + def __init__(self, + dim: int, + icomp: int, + jcomp: int, + viscosity_mu_name: str = "mu", + darcy_impermeability_name: str = "k") -> None: + mu = sym.SpatialConstant(viscosity_mu_name) + k = sym.SpatialConstant(darcy_impermeability_name) + + d = make_sym_vector("d", dim) + r = sym.pymbolic_real_norm_2(d) + R = k * r # ruff:ignore[non-lowercase-variable-in-function] + delta_ij = 1 if icomp == jcomp else 0 + + # NOTE: + # [1] C. Pozrikidis, A Practical Guide to Boundary Element Methods, + # CRC Press, 2002. + # [2] https://dlmf.nist.gov/10.27#E8 + # [3] https://doi.org/10.1080/00036811.2011.614604 + # [4] https://doi.org/10.1002/mana.200710797 + + if dim == 2: + # transforming Bessel functions to Hankel functions using [2] + K0 = var("pi") * var("I") / 2 * var("hankel_1")(0, var("I") * R) # ruff:ignore[non-lowercase-variable-in-function] + K1 = -var("pi") / 2 * var("hankel_1")(1, var("I") * R) # ruff:ignore[non-lowercase-variable-in-function] + + # [1] Equations 7.7.5 and 7.7.6 + # [3] Equations 5.2 and 5.3 (for the scaling we use here) + a = 2 * (K0 + K1 / R - 1 / R**2) + b = 2 * (2 / R**2 - 2 * K1 / R - K0) + expr = a * delta_ij + b * d[icomp] * d[jcomp] / r ** 2 + scaling = -1 / (4 * var("pi") * mu) + elif dim == 3: + # [4] Equations 4.2 and 4.3 + a = 2 * var("exp")(-R) * (1 + 1 / R + 1 / R**2) - 2 / R**2 + b = 6 / R**2 - 2 * var("exp")(-R) * (1 + 3 / R + 3 / R**2) + expr = a * delta_ij / r + b * d[icomp] * d[jcomp] / r ** 3 + scaling = -1 / (8 * var("pi") * mu) + else: + raise NotImplementedError(f"unsupported dimension: '{dim}'") + + super().__init__(dim, expression=expr, global_scaling_const=scaling, + viscosity_mu_name=viscosity_mu_name, + darcy_impermeability_name=darcy_impermeability_name) + + object.__setattr__(self, "icomp", icomp) + object.__setattr__(self, "jcomp", jcomp) + + @override + def __reduce__(self) -> tuple[object, ...]: + return ( + self.__class__, + (self.dim, self.icomp, self.jcomp, + self.viscosity_mu_name, self.darcy_impermeability_name), + ) + + @override + def __str__(self) -> str: + return ( + f"BrinkmanletKnl{self.dim}D_{self.icomp}{self.jcomp}" + f"({self.viscosity_mu_name}, {self.darcy_impermeability_name})") + + @override + @memoize_method + def get_pde_system_kernel(self) -> tuple[SystemKernel, tuple[int, ...]]: + return BrinkmanletSystemKernel( + self.dim, + viscosity_mu_name=self.viscosity_mu_name, + darcy_impermeability_name=self.darcy_impermeability_name, + ), (self.icomp, self.jcomp) + + +@dataclass(frozen=True, repr=False) +class BrinkmanStressComponentKernel(BrinkmanComponentKernelBase): + r"""A kernel for the Brinkman equations. + + .. math:: + + K_{ijk} = + -p_j \delta_{ik} + + \mu (\partial_k K_{ij} + \partial_i K_{jk}), + + where the two-index :math:`K_{ij}` is the + :class:`~sumpy.kernel.BrinkmanletComponentKernel` and :math:`P_j` is the + pressure kernel. + + .. autoattribute:: icomp + .. autoattribute:: jcomp + .. autoattribute:: kcomp + """ + + mapper_method: ClassVar[str] = "map_brinkman_stress_kernel" + + icomp: int + """Component index for the kernel.""" + jcomp: int + """Component index for the kernel.""" + kcomp: int + """Component index for the kernel.""" + + def __init__(self, + dim: int, + icomp: int, + jcomp: int, + kcomp: int, + viscosity_mu_name: str = "mu", + darcy_impermeability_name: str = "k") -> None: + k = sym.SpatialConstant(darcy_impermeability_name) + + d = make_sym_vector("d", dim) + r = sym.pymbolic_real_norm_2(d) + R = k * r # ruff:ignore[non-lowercase-variable-in-function] + delta_ij = 1 if icomp == jcomp else 0 + delta_ik = 1 if icomp == kcomp else 0 + delta_kj = 1 if jcomp == kcomp else 0 + + # NOTE: + # [1] C. Pozrikidis, A Practical Guide to Boundary Element Methods, + # CRC Press, 2002. + # [2] https://dlmf.nist.gov/10.27#E8 + # [3] https://doi.org/10.1080/00036811.2011.614604 + # [4] https://doi.org/10.1002/mana.200710797 + + if dim == 2: + # transforming Bessel functions to Hankel functions using [2] + K0 = var("pi") * var("I") / 2 * var("hankel_1")(0, var("I") * R) # ruff:ignore[non-lowercase-variable-in-function] + K1 = -var("pi") / 2 * var("hankel_1")(1, var("I") * R) # ruff:ignore[non-lowercase-variable-in-function] + + # [1] Equations 7.7.7 and 7.7.8 + # [3] Equations 5.4-5.6 (for the scaling we use here) + a = 2 * (2 / R**2 - 2 * K1 / R - K0) + b = 8 / R**2 - 4*K0 - 2*(R + 4 / R)*K1 + c = b + R * K1 + expr = ( + 2 * (a - 1) * d[jcomp] / r**2 * delta_ik + + b * (d[kcomp] * delta_ij + d[icomp] * delta_kj) / r**2 + - 4 * c * d[icomp] * d[jcomp] * d[kcomp] / r**4 + ) + scaling = -1 / (4 * var("pi")) + elif dim == 3: + # [4] Equations 4.4-4.6 + d1 = 2 * var("exp")(-R) * (1 + 3 / R + 3 / R**2) - 6 / R**2 + 1 + d2 = var("exp")(-R) * (R + 3 + 6 / R + 6 / R**2) - 6 / R**2 + d3 = var("exp")(-R) * (-2 * R - 12 - 30 / R - 30 / R**2) + 30 / R**2 + expr = ( + d1 * d[jcomp] / r**3 * delta_ik + + d2 * (d[kcomp] * delta_ij + d[icomp] * delta_kj) / r**3 + + d3 * d[icomp] * d[jcomp] * d[kcomp] / r**5 + ) + scaling = 1/(4*var("pi")) + else: + raise NotImplementedError(f"unsupported dimension: '{dim}'") + + super().__init__(dim, expression=expr, global_scaling_const=scaling, + viscosity_mu_name=viscosity_mu_name, + darcy_impermeability_name=darcy_impermeability_name) + + object.__setattr__(self, "icomp", icomp) + object.__setattr__(self, "jcomp", jcomp) + object.__setattr__(self, "kcomp", kcomp) + + @override + def __reduce__(self) -> tuple[object, ...]: + return ( + self.__class__, + (self.dim, self.icomp, self.jcomp, self.kcomp, + self.viscosity_mu_name, self.darcy_impermeability_name), + ) + + @override + def __str__(self) -> str: + return ( + f"BrinkmanStressKnl{self.dim}D_{self.icomp}{self.jcomp}{self.kcomp}" + f"({self.viscosity_mu_name}, {self.darcy_impermeability_name})") + + @override + @memoize_method + def get_pde_system_kernel(self) -> tuple[SystemKernel, tuple[int, ...]]: + return BrinkmanStressSystemKernel( + self.dim, + viscosity_mu_name=self.viscosity_mu_name, + darcy_impermeability_name=self.darcy_impermeability_name, + ), (self.icomp, self.jcomp, self.kcomp) + + +@dataclass(frozen=True, repr=False) +class HeatKernel(ExpressionKernel): + r"""The Green's function for the heat equation. + + .. math:: + + \frac{\partial}{\partial t} K(t, \mathbf{x}, \mathbf{y}) + - \alpha \Delta K(t, \mathbf{x}, \mathbf{y}) + = \delta(t) \delta(\mathbf{x} - \mathbf{y}) + + .. note:: + + This kernel cannot be used in an FMM yet and can only be used in + expansions and evaluations that occur forward in the time dimension. + """ + + mapper_method: ClassVar[str] = "map_heat_kernel" + + heat_alpha_name: str + + def __init__(self, spatial_dims: int, heat_alpha_name: str = "alpha"): + dim = spatial_dims + 1 + alpha = sym.SpatialConstant(heat_alpha_name) + + d = make_sym_vector("d", dim) + t = d[-1] + r = sym.pymbolic_real_norm_2(d[:-1]) + + expr = var("exp")(-r**2/(4 * alpha * t)) / var("sqrt")(t**(dim - 1)) + scaling = 1/var("sqrt")((4*var("pi")*alpha)**(dim - 1)) + + super().__init__(dim, expression=expr, global_scaling_const=scaling) + object.__setattr__(self, "heat_alpha_name", heat_alpha_name) + + @override + def __reduce__(self) -> tuple[object, ...]: + return (self.__class__, (self.dim - 1, self.heat_alpha_name)) + + @property + @override + def is_complex_valued(self) -> bool: + return False + + @override + def __str__(self): + return f"HeatKnl{self.dim - 1}D" + + @override + def get_args(self): + return [ + KernelArgument(loopy_arg=lp.ValueArg(self.heat_alpha_name, np.float64)) + ] + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import diff, laplacian, make_identity_diff_op + + alpha = sym.Symbol(self.heat_alpha_name) + w = make_identity_diff_op(self.dim - 1, time_dependent=True) + t_mi = (*([0] * (self.dim - 1)), 1) + + return diff(w, t_mi) - alpha * laplacian(w) + + +# }}} + + +# {{{ vector PDE kernels + + +@dataclass(frozen=True, repr=False) +class ElasticitySystemKernel(SystemKernel): + r"""The displacement kernel for the linear elasticity (Navier-Cauchy) equations + (see e.g. Section 2.2 in [Hsiao2008]_). + + This kernel uses :class:`ElasticityComponentKernel` for its components. + + .. autoattribute:: viscosity_mu_name + .. autoattribute:: poisson_ratio_name + """ + + mapper_method: ClassVar[str] = "map_elasticity_system_kernel" + + viscosity_mu_name: str = "mu" + r"""The argument name to use for the dynamic viscosity :math:`\mu` when + generating functions to evaluate this kernel. + """ + poisson_ratio_name: str = "nu" + r"""The argument name to use for Poisson's ratio :math:`\nu` when generating + functions to evaluate this kernel. + """ + + @override + def __str__(self) -> str: + return ( + f"ElasticityKnl{self.dim}D" + f"({self.viscosity_mu_name}, {self.poisson_ratio_name})") + + @override + def __reduce__(self): + return ( + type(self), + (self.dim, self.viscosity_mu_name, self.poisson_ratio_name)) + + @property + @override + def shape(self) -> tuple[int, ...]: + return (self.dim, self.dim) + + @override + @keyed_memoize_method(key=lambda i, j: tuple(sorted((i, j)))) + def get_scalar_component(self, i: int, j: int, /) -> ScalarKernel: + # NOTE: the kernel is (i, j) -> (j, i) symmetric + i, j = sorted([i, j]) + + return ElasticityComponentKernel( + self.dim, i, j, + viscosity_mu_name=self.viscosity_mu_name, + poisson_ratio_name=self.poisson_ratio_name, + ) + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import ( + divergence, + gradient, + laplacian, + make_identity_diff_op, + ) + + mu = sym.Symbol(self.viscosity_mu_name) + nu = sym.Symbol(self.poisson_ratio_name) + u = make_identity_diff_op(self.dim, self.dim) + + return mu * laplacian(u) + mu / (1 - 2 * nu) * gradient(divergence(u)) + + +@dataclass(frozen=True, repr=False) +class ElasticityStressSystemKernel(SystemKernel): + r"""The stress kernel for the linear elasticity (Navier-Cauchy) equations + (see e.g. Section 2.2 in [Hsiao2008]_). + + This kernel uses :class:`ElasticityStressComponentKernel` for its components. + + .. autoattribute:: viscosity_mu_name + .. autoattribute:: poisson_ratio_name + """ + + mapper_method: ClassVar[str] = "map_elasticity_stress_system_kernel" + + viscosity_mu_name: str = "mu" + r"""The argument name to use for the dynamic viscosity :math:`\mu` when + generating functions to evaluate this kernel. + """ + poisson_ratio_name: str = "nu" + r"""The argument name to use for Poisson's ratio :math:`\nu` when generating + functions to evaluate this kernel. + """ + + @override + def __str__(self) -> str: + return ( + f"ElasticityStressKnl{self.dim}D" + f"({self.viscosity_mu_name}, {self.poisson_ratio_name})") + + @override + def __reduce__(self): + return ( + type(self), + (self.dim, self.viscosity_mu_name, self.poisson_ratio_name)) + + @property + @override + def shape(self) -> tuple[int, ...]: + return (self.dim, self.dim, self.dim) + + @override + @keyed_memoize_method(key=lambda i, j, k: ((min(i, j), max(i, j), k))) + def get_scalar_component(self, i: int, j: int, k: int, /) -> ScalarKernel: + if i > j: + i, j = j, i + + return ElasticityStressComponentKernel( + self.dim, i, j, k, + viscosity_mu_name=self.viscosity_mu_name, + poisson_ratio_name=self.poisson_ratio_name, + ) + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import ( + divergence, + gradient, + laplacian, + make_identity_diff_op, + ) + + mu = sym.Symbol(self.viscosity_mu_name) + nu = sym.Symbol(self.poisson_ratio_name) + u = make_identity_diff_op(self.dim, self.dim) + + return mu * laplacian(u) + mu / (1 - 2 * nu) * gradient(divergence(u)) + + +@dataclass(frozen=True, repr=False) +class StokesletSystemKernel(SystemKernel): + r"""A kernel for the Stokes equations (see e.g. Chapter 2 in [Pozrikidis1992]_). + + This kernel uses :class:`StokesletComponentKernel` for its components. + + .. autoattribute:: viscosity_mu_name + """ + + mapper_method: ClassVar[str] = "map_stokeslet_system_kernel" + + viscosity_mu_name: str = "mu" + r"""The argument name to use for the dynamic viscosity :math:`\mu` when + generating functions to evaluate this kernel. + """ + + @override + def __str__(self) -> str: + return ( + f"StokesletKnl{self.dim}D({self.viscosity_mu_name})") + + @override + def __reduce__(self): + return (type(self), (self.dim, self.viscosity_mu_name)) + + @property + @override + def shape(self) -> tuple[int, ...]: + return (self.dim, self.dim) + + @override + @keyed_memoize_method(key=lambda i, j: tuple(sorted((i, j)))) + def get_scalar_component(self, i: int, j: int, /) -> ScalarKernel: + # NOTE: the kernel is (i, j) -> (j, i) symmetric + i, j = sorted([i, j]) + + return StokesletComponentKernel( + self.dim, i, j, + viscosity_mu_name=self.viscosity_mu_name, + ) + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import ( + concat, + divergence, + gradient, + laplacian, + make_identity_diff_op, + ) + + mu = sym.Symbol(self.viscosity_mu_name) + u_and_p = make_identity_diff_op(self.dim, self.dim + 1) + u = u_and_p[:self.dim] + p = u_and_p[self.dim] + + return concat(mu * laplacian(u) - gradient(p), divergence(u)) + + +@dataclass(frozen=True, repr=False) +class StressletSystemKernel(SystemKernel): + r"""A kernel for the Stokes equations (see e.g. Chapter 2 in [Pozrikidis1992]_). + + This kernel uses :class:`StressletComponentKernel` for its components. + + .. autoattribute:: viscosity_mu_name + """ + + mapper_method: ClassVar[str] = "map_stresslet_system_kernel" + + viscosity_mu_name: str = "mu" + r"""The argument name to use for the dynamic viscosity :math:`\mu` when + generating functions to evaluate this kernel. + """ + + @override + def __str__(self) -> str: + return ( + f"StressletKnl{self.dim}D({self.viscosity_mu_name})") + + @override + def __reduce__(self): + return (type(self), (self.dim, self.viscosity_mu_name)) + + @property + @override + def shape(self) -> tuple[int, ...]: + return (self.dim, self.dim, self.dim) + + @override + @keyed_memoize_method(key=lambda i, j, k: tuple(sorted((i, j, k)))) + def get_scalar_component(self, i: int, j: int, k: int, /) -> ScalarKernel: + # NOTE: the kernel is fully permutation symmetric + i, j, k = sorted([i, j, k]) + + return StressletComponentKernel( + self.dim, i, j, k, + viscosity_mu_name=self.viscosity_mu_name, + ) + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import ( + concat, + divergence, + gradient, + laplacian, + make_identity_diff_op, + ) + + mu = sym.Symbol(self.viscosity_mu_name) + u_and_p = make_identity_diff_op(self.dim, self.dim + 1) + u = u_and_p[:self.dim] + p = u_and_p[self.dim] + + return concat(mu * laplacian(u) - gradient(p), divergence(u)) + + +@dataclass(frozen=True, repr=False) +class BrinkmanletSystemKernel(SystemKernel): + r"""A kernel for the Brinkman equations. + + This kernel uses :class:`BrinkmanletComponentKernel` for its components. + + .. autoattribute:: viscosity_mu_name + .. autoattribute:: darcy_impermeability_name + """ + + mapper_method: ClassVar[str] = "map_brinkmanlet_system_kernel" + + viscosity_mu_name: str = "mu" + """The argument name to use for the dynamic viscosity when generating + functions to evaluate this kernel. + """ + darcy_impermeability_name: str = "k" + """The argument name to use for the Darcy impermeability when generating + functions to evaluate this kernel. + """ + + @override + def __str__(self) -> str: + return ( + f"BrinkmanletKnl{self.dim}D" + f"({self.viscosity_mu_name}, {self.darcy_impermeability_name})") + + @override + def __reduce__(self): + return ( + type(self), + (self.dim, self.viscosity_mu_name, self.darcy_impermeability_name)) + + @property + @override + def shape(self) -> tuple[int, ...]: + return (self.dim, self.dim) + + @override + @keyed_memoize_method(key=lambda i, j: tuple(sorted((i, j)))) + def get_scalar_component(self, i: int, j: int, /) -> ScalarKernel: + # NOTE: the kernel is (i, j) -> (j, i) symmetric + i, j = sorted([i, j]) + + return BrinkmanletComponentKernel( + self.dim, i, j, + viscosity_mu_name=self.viscosity_mu_name, + darcy_impermeability_name=self.darcy_impermeability_name, + ) + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import ( + concat, + divergence, + gradient, + laplacian, + make_identity_diff_op, + ) + + mu = sym.Symbol(self.viscosity_mu_name) + k = sym.Symbol(self.darcy_impermeability_name) + + u_and_p = make_identity_diff_op(self.dim, self.dim + 1) + u = u_and_p[:self.dim] + p = u_and_p[self.dim] + + return concat(mu * (laplacian(u) - k**2 * u) - gradient(p), divergence(u)) + + +@dataclass(frozen=True, repr=False) +class BrinkmanStressSystemKernel(SystemKernel): + r"""A kernel for the Brinkman equations. + + This kernel uses :class:`BrinkmanStressComponentKernel` for its components. + + .. autoattribute:: viscosity_mu_name + .. autoattribute:: darcy_impermeability_name + """ + + mapper_method: ClassVar[str] = "map_brinkman_stress_system_kernel" + + viscosity_mu_name: str = "mu" + """The argument name to use for the dynamic viscosity when generating + functions to evaluate this kernel. + """ + darcy_impermeability_name: str = "k" + """The argument name to use for the Darcy impermeability when generating + functions to evaluate this kernel. + """ + + @override + def __str__(self) -> str: + return ( + f"BrinkmanStressKnl{self.dim}D" + f"({self.viscosity_mu_name}, {self.darcy_impermeability_name})") + + @override + def __reduce__(self): + return ( + type(self), + (self.dim, self.viscosity_mu_name, self.darcy_impermeability_name)) + + @property + @override + def shape(self) -> tuple[int, ...]: + return (self.dim, self.dim, self.dim) + + @override + @keyed_memoize_method(key=lambda i, j, k: ((min(i, k), j, max(i, k)))) + def get_scalar_component(self, i: int, j: int, k: int, /) -> ScalarKernel: + # NOTE: the kernel is (i, j, k) -> (k, j, i) symmetric + if i > k: + i, k = k, i + + return BrinkmanStressComponentKernel( + self.dim, i, j, k, + viscosity_mu_name=self.viscosity_mu_name, + darcy_impermeability_name=self.darcy_impermeability_name, + ) + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + from sumpy.expansion.diff_op import ( + concat, + divergence, + gradient, + laplacian, + make_identity_diff_op, + ) + + mu = sym.Symbol(self.viscosity_mu_name) + k = sym.Symbol(self.darcy_impermeability_name) + + u_and_p = make_identity_diff_op(self.dim, self.dim + 1) + u = u_and_p[:self.dim] + p = u_and_p[self.dim] + + return concat(mu * (laplacian(u) - k**2 * u) - gradient(p), divergence(u)) + + +# }}} + + +# {{{ a kernel defined as wrapping another one--e.g., derivatives + +@dataclass(frozen=True) +class KernelWrapper(ScalarKernel, ABC): + inner_kernel: ScalarKernel + """The kernel that is being wrapped (to take a derivative of, etc.).""" + + def __init__(self, inner_kernel: ScalarKernel) -> None: + ScalarKernel.__init__(self, inner_kernel.dim) + object.__setattr__(self, "inner_kernel", inner_kernel) @property - def is_complex_valued(self): + @override + def is_complex_valued(self) -> bool: return self.inner_kernel.is_complex_valued - def get_expression(self, scaled_dist_vec): - return self.inner_kernel.get_expression(scaled_dist_vec) + @override + def get_base_kernel(self) -> ScalarKernel: + return self.inner_kernel.get_base_kernel() + + @override + def prepare_loopy_kernel(self, loopy_knl: lp.TranslationUnit) -> lp.TranslationUnit: + return self.inner_kernel.prepare_loopy_kernel(loopy_knl) + + @override + def get_expression(self, dist_vec: sym.Matrix) -> sym.Expr: + return self.inner_kernel.get_expression(dist_vec) - def get_derivative_coeff_dict_at_source(self, expr_dict): + @override + def get_derivative_coeff_dict_at_source( + self, expr_dict: DerivativeCoeffDict, + ) -> DerivativeCoeffDict: return self.inner_kernel.get_derivative_coeff_dict_at_source(expr_dict) - def postprocess_at_target(self, expr, bvec): + @overload + def postprocess_at_target( + self, expr: sym.Expr, bvec: sp.Matrix, + ) -> sym.Expr: ... + + @overload + def postprocess_at_target( + self, expr: ExprDerivativeTaker, bvec: sp.Matrix, + ) -> DifferentiatedExprDerivativeTaker: ... + + @override + def postprocess_at_target( + self, expr: sym.Expr | ExprDerivativeTaker, bvec: sp.Matrix, + ) -> sym.Expr | DifferentiatedExprDerivativeTaker: return self.inner_kernel.postprocess_at_target(expr, bvec) - def get_global_scaling_const(self): + @override + def get_global_scaling_const(self) -> sym.Expr: return self.inner_kernel.get_global_scaling_const() - def get_code_transformer(self): + @override + def get_code_transformer(self) -> Callable[[Expression], Expression]: return self.inner_kernel.get_code_transformer() - def get_args(self): + @override + def get_args(self) -> Sequence[KernelArgument]: return self.inner_kernel.get_args() - def get_source_args(self): + @override + def get_source_args(self) -> Sequence[KernelArgument]: return self.inner_kernel.get_source_args() - def replace_base_kernel(self, new_base_kernel): - raise NotImplementedError("replace_base_kernel is not implemented " - "for this wrapper.") - - def get_derivative_taker(self, dvec, rscale, sac): + @override + def replace_base_kernel(self, new_base_kernel: ScalarKernel) -> ScalarKernel: + raise NotImplementedError( + f"'replace_base_kernel' is not implemented for '{type(self).__name__}'") + + @override + def get_derivative_taker(self, + dvec: sym.Matrix, + rscale: sym.Expr, + sac: SymbolicAssignmentCollection | None, + ) -> ExprDerivativeTaker: return self.inner_kernel.get_derivative_taker(dvec, rscale, sac) # }}} @@ -966,415 +2265,491 @@ def get_derivative_taker(self, dvec, rscale, sac): # {{{ derivatives -class DerivativeBase(KernelWrapper): - pass +class DerivativeBase(KernelWrapper, ABC): + """Bases: :class:`ScalarKernel` + + .. autoattribute:: inner_kernel + .. automethod:: replace_inner_kernel + """ + + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + return self.inner_kernel.get_pde_as_diff_op() + + @abstractmethod + def replace_inner_kernel(self, new_inner_kernel: ScalarKernel) -> ScalarKernel: + """Replace the inner kernel of this wrapper. + + This is essentially the same as :meth:`ScalarKernel.replace_base_kernel`, + but it does not recurse. + """ +@dataclass(frozen=True) class AxisSourceDerivative(DerivativeBase): - init_arg_names = ("axis", "inner_kernel") + """ + .. autoattribute:: axis + """ - def __init__(self, axis, inner_kernel): - KernelWrapper.__init__(self, inner_kernel) - self.axis = axis + mapper_method: ClassVar[str] = "map_axis_source_derivative" - def __getinitargs__(self): - return (self.axis, self.inner_kernel) + axis: int + """Direction axis for the source derivative.""" - def __str__(self): - return f"d/dy{self.axis} {self.inner_kernel}" + def __init__(self, axis: int, inner_kernel: ScalarKernel) -> None: + super().__init__(inner_kernel) + object.__setattr__(self, "axis", axis) - def __repr__(self): - return f"AxisSourceDerivative({self.axis}, {self.inner_kernel!r})" + @override + def __str__(self) -> str: + return f"d/dy{self.axis} {self.inner_kernel}" - def get_derivative_coeff_dict_at_source(self, expr_dict): + @override + def get_derivative_coeff_dict_at_source( + self, expr_dict: DerivativeCoeffDict, + ) -> DerivativeCoeffDict: expr_dict = self.inner_kernel.get_derivative_coeff_dict_at_source( expr_dict) - result = dict() + result = {} for mi, coeff in expr_dict.items(): new_mi = list(mi) new_mi[self.axis] += 1 result[tuple(new_mi)] = -coeff return result - def replace_base_kernel(self, new_base_kernel): + @override + def replace_base_kernel(self, new_base_kernel: ScalarKernel) -> ScalarKernel: return type(self)(self.axis, self.inner_kernel.replace_base_kernel(new_base_kernel)) - def replace_inner_kernel(self, new_inner_kernel): + @override + def replace_inner_kernel(self, new_inner_kernel: ScalarKernel) -> ScalarKernel: return type(self)(self.axis, new_inner_kernel) - mapper_method = "map_axis_source_derivative" - +@dataclass(frozen=True) class AxisTargetDerivative(DerivativeBase): - init_arg_names = ("axis", "inner_kernel") - target_array_name = "targets" + """ + .. autoattribute:: axis + """ - def __init__(self, axis, inner_kernel): - KernelWrapper.__init__(self, inner_kernel) - self.axis = axis + mapper_method: ClassVar[str] = "map_axis_target_derivative" + target_array_name: ClassVar[str] = "targets" - def __getinitargs__(self): - return (self.axis, self.inner_kernel) + axis: int - def __str__(self): + def __init__(self, axis: int, inner_kernel: ScalarKernel) -> None: + super().__init__(inner_kernel) + object.__setattr__(self, "axis", axis) + + @override + def __str__(self) -> str: return f"d/dx{self.axis} {self.inner_kernel}" - def __repr__(self): - return f"AxisTargetDerivative({self.axis}, {self.inner_kernel!r})" + @overload + def postprocess_at_target( + self, expr: sym.Expr, bvec: sp.Matrix, + ) -> sym.Expr: ... - def postprocess_at_target(self, expr, bvec): - from sumpy.tools import (DifferentiatedExprDerivativeTaker, - diff_derivative_coeff_dict) - from sumpy.symbolic import make_sym_vector as make_sympy_vector + @overload + def postprocess_at_target( + self, expr: ExprDerivativeTaker, bvec: sp.Matrix, + ) -> DifferentiatedExprDerivativeTaker: ... - target_vec = make_sympy_vector(self.target_array_name, self.dim) + @override + def postprocess_at_target( + self, expr: sym.Expr | ExprDerivativeTaker, bvec: sp.Matrix, + ) -> sym.Expr | DifferentiatedExprDerivativeTaker: + target_vec = sym.make_sym_vector(self.target_array_name, self.dim) # bvec = tgt - ctr - expr = self.inner_kernel.postprocess_at_target(expr, bvec) - if isinstance(expr, DifferentiatedExprDerivativeTaker): - transformation = diff_derivative_coeff_dict(expr.derivative_coeff_dict, + inner_expr = self.inner_kernel.postprocess_at_target(expr, bvec) + if isinstance(inner_expr, DifferentiatedExprDerivativeTaker): + transformation = diff_derivative_coeff_dict( + inner_expr.derivative_coeff_dict, self.axis, target_vec) - return DifferentiatedExprDerivativeTaker(expr.taker, transformation) + return DifferentiatedExprDerivativeTaker(inner_expr.taker, transformation) else: # Since `bvec` and `tgt` are two different symbolic variables # need to differentiate by both to get the correct answer - return expr.diff(bvec[self.axis]) + expr.diff(target_vec[self.axis]) + return (inner_expr.diff(bvec[self.axis]) + + inner_expr.diff(target_vec[self.axis])) - def replace_base_kernel(self, new_base_kernel): + @override + def replace_base_kernel(self, new_base_kernel: ScalarKernel) -> ScalarKernel: return type(self)(self.axis, self.inner_kernel.replace_base_kernel(new_base_kernel)) - def replace_inner_kernel(self, new_inner_kernel): + @override + def replace_inner_kernel(self, new_inner_kernel: ScalarKernel) -> ScalarKernel: return type(self)(self.axis, new_inner_kernel) - mapper_method = "map_axis_target_derivative" +class _VectorIndexAdder(CSECachingMapperMixin[Expression, []], IdentityMapper[[]]): + vec_name: str + additional_indices: tuple[Expression, ...] -class _VectorIndexAdder(CSECachingMapperMixin, IdentityMapper): - def __init__(self, vec_name, additional_indices): + def __init__(self, + vec_name: str, + additional_indices: tuple[Expression, ...]) -> None: self.vec_name = vec_name self.additional_indices = additional_indices - def map_subscript(self, expr): - from pymbolic.primitives import CommonSubexpression - if expr.aggregate.name == self.vec_name \ - and isinstance(expr.index, int): - return CommonSubexpression(expr.aggregate.index( - (expr.index,) + self.additional_indices)) + @override + def map_subscript(self, expr: prim.Subscript) -> Expression: + from pymbolic.primitives import CommonSubexpression, cse_scope + name = getattr(expr.aggregate, "name", None) + + if name == self.vec_name and isinstance(expr.index, int): + return CommonSubexpression( + expr.aggregate[(expr.index, *self.additional_indices)], + prefix=None, scope=cse_scope.EVALUATION) else: return IdentityMapper.map_subscript(self, expr) - map_common_subexpression_uncached = IdentityMapper.map_common_subexpression + @override + def map_common_subexpression_uncached(self, + expr: prim.CommonSubexpression) -> Expression: + result = self.rec(expr.child) + if result is expr.child: + return expr + + return type(expr)( + result, expr.prefix, expr.scope, **expr.get_extra_properties()) +@dataclass(frozen=True) class DirectionalDerivative(DerivativeBase): - init_arg_names = ("inner_kernel", "dir_vec_name") + """ + .. autoattribute:: dir_vec_name + """ + directional_kind: ClassVar[Literal["src", "tgt"]] + """The kind of this directional derivative (can only be a source or target).""" + + dir_vec_name: str + """Name of the vector used for the direction.""" - def __init__(self, inner_kernel, dir_vec_name=None): + def __init__(self, + inner_kernel: ScalarKernel, + dir_vec_name: str | None = None) -> None: if dir_vec_name is None: dir_vec_name = f"{self.directional_kind}_derivative_dir" KernelWrapper.__init__(self, inner_kernel) - self.dir_vec_name = dir_vec_name + object.__setattr__(self, "dir_vec_name", dir_vec_name) - def __getinitargs__(self): - return (self.inner_kernel, self.dir_vec_name) + @override + def __str__(self) -> str: + d = "y" if self.directional_kind == "src" else "x" + return fr"{self.dir_vec_name}·∇_{d} {self.inner_kernel}" - def update_persistent_hash(self, key_hash, key_builder): - key_hash.update(type(self).__name__.encode("utf8")) - key_builder.rec(key_hash, self.inner_kernel) - key_builder.rec(key_hash, self.dir_vec_name) - - def replace_base_kernel(self, new_base_kernel): - return type(self)(self.inner_kernel.replace_base_kernel(new_base_kernel), + @override + def replace_base_kernel(self, new_base_kernel: ScalarKernel) -> ScalarKernel: + return type(self)( + self.inner_kernel.replace_base_kernel(new_base_kernel), dir_vec_name=self.dir_vec_name) - def __str__(self): - return r"{} . \/_{} {}".format( - self.dir_vec_name, self.directional_kind[0], self.inner_kernel) - - def __repr__(self): - return "{}({!r}, {})".format( - type(self).__name__, - self.inner_kernel, - self.dir_vec_name) - - -class DirectionalTargetDerivative(DirectionalDerivative): - directional_kind = "tgt" - target_array_name = "targets" - - def get_code_transformer(self): - from sumpy.codegen import VectorComponentRewriter - vcr = VectorComponentRewriter([self.dir_vec_name]) - from pymbolic.primitives import Variable - via = _VectorIndexAdder(self.dir_vec_name, (Variable("itgt"),)) - - inner_transform = self.inner_kernel.get_code_transformer() - - def transform(expr): - return via(vcr(inner_transform(expr))) - - return transform - - def postprocess_at_target(self, expr, bvec): - from sumpy.tools import (DifferentiatedExprDerivativeTaker, - diff_derivative_coeff_dict) - - from sumpy.symbolic import make_sym_vector as make_sympy_vector - dir_vec = make_sympy_vector(self.dir_vec_name, self.dim) - target_vec = make_sympy_vector(self.target_array_name, self.dim) - - expr = self.inner_kernel.postprocess_at_target(expr, bvec) - - # bvec = tgt - center - if not isinstance(expr, DifferentiatedExprDerivativeTaker): - result = 0 - for axis in range(self.dim): - # Since `bvec` and `tgt` are two different symbolic variables - # need to differentiate by both to get the correct answer - result += (expr.diff(bvec[axis]) + expr.diff(target_vec[axis])) \ - * dir_vec[axis] - return result - - new_transformation = defaultdict(lambda: 0) - for axis in range(self.dim): - axis_transformation = diff_derivative_coeff_dict( - expr.derivative_coeff_dict, axis, target_vec) - for mi, coeff in axis_transformation.items(): - new_transformation[mi] += coeff * dir_vec[axis] - - return DifferentiatedExprDerivativeTaker(expr.taker, - dict(new_transformation)) - - def get_source_args(self): - return [ - KernelArgument( - loopy_arg=lp.GlobalArg( - self.dir_vec_name, - None, - shape=(self.dim, "ntargets"), - dim_tags="sep,C", - offset=lp.auto), - ) - ] + self.inner_kernel.get_source_args() - - mapper_method = "map_directional_target_derivative" + @override + def replace_inner_kernel(self, new_inner_kernel: ScalarKernel) -> ScalarKernel: + return type(self)(new_inner_kernel, dir_vec_name=self.dir_vec_name) class DirectionalSourceDerivative(DirectionalDerivative): - directional_kind = "src" + mapper_method: ClassVar[str] = "map_directional_source_derivative" + directional_kind: ClassVar[Literal["src", "tgt"]] = "src" - def get_code_transformer(self): + @override + def get_code_transformer(self) -> Callable[[Expression], Expression]: inner = self.inner_kernel.get_code_transformer() from sumpy.codegen import VectorComponentRewriter - vcr = VectorComponentRewriter([self.dir_vec_name]) - from pymbolic.primitives import Variable - via = _VectorIndexAdder(self.dir_vec_name, (Variable("isrc"),)) + vcr = VectorComponentRewriter(frozenset([self.dir_vec_name])) + via = _VectorIndexAdder(self.dir_vec_name, (prim.Variable("isrc"),)) - def transform(expr): + def transform(expr: Expression) -> Expression: return via(vcr(inner(expr))) return transform - def get_derivative_coeff_dict_at_source(self, expr_dict): - from sumpy.symbolic import make_sym_vector as make_sympy_vector - dir_vec = make_sympy_vector(self.dir_vec_name, self.dim) + @override + def get_derivative_coeff_dict_at_source( + self, expr_dict: DerivativeCoeffDict, + ) -> DerivativeCoeffDict: + dir_vec = sym.make_sym_vector(self.dir_vec_name, self.dim) expr_dict = self.inner_kernel.get_derivative_coeff_dict_at_source( expr_dict) # avec = center-src -> minus sign from chain rule - result = defaultdict(lambda: 0) + result: DerivativeCoeffDict = defaultdict(lambda: 0) for mi, coeff in expr_dict.items(): for axis in range(self.dim): new_mi = list(mi) new_mi[axis] += 1 result[tuple(new_mi)] += -coeff * dir_vec[axis] - return result - def get_source_args(self): + return dict(result) + + @override + def get_source_args(self) -> Sequence[KernelArgument]: return [ KernelArgument( loopy_arg=lp.GlobalArg( self.dir_vec_name, None, shape=(self.dim, "nsources"), - dim_tags="sep,C", offset=lp.auto), - ) - ] + self.inner_kernel.get_source_args() + ), + *self.inner_kernel.get_source_args()] - mapper_method = "map_directional_source_derivative" + @override + def prepare_loopy_kernel(self, loopy_knl: lp.TranslationUnit) -> lp.TranslationUnit: + loopy_knl = self.inner_kernel.prepare_loopy_kernel(loopy_knl) + return lp.tag_array_axes(loopy_knl, self.dir_vec_name, "sep,C") +@dataclass(frozen=True) class TargetPointMultiplier(KernelWrapper): - """Wraps a kernel :math:`G(x, y)` and outputs :math:`x_j G(x, y)` + """Bases: :class:`ScalarKernel` + + Wraps a kernel :math:`G(x, y)` and outputs :math:`x_j G(x, y)` where :math:`x, y` are targets and sources respectively. + + .. autoattribute:: axis """ - init_arg_names = ("axis", "inner_kernel") - target_array_name = "targets" + mapper_method: ClassVar[str] = "map_target_point_multiplier" + target_array_name: ClassVar[str] = "targets" - def __init__(self, axis, inner_kernel): - KernelWrapper.__init__(self, inner_kernel) - self.axis = axis + axis: int + """Coordinate axis with which to multiply the kernel.""" - def __getinitargs__(self): - return (self.axis, self.inner_kernel) + def __init__(self, axis: int, inner_kernel: ScalarKernel) -> None: + super().__init__(inner_kernel) + object.__setattr__(self, "axis", axis) - def __str__(self): + @override + def __str__(self) -> str: return f"x{self.axis} {self.inner_kernel}" - def __repr__(self): - return f"TargetPointMultiplier({self.axis}, {self.inner_kernel!r})" + @overload + def postprocess_at_target( + self, expr: sym.Expr, bvec: sp.Matrix, + ) -> sym.Expr: ... - def replace_base_kernel(self, new_base_kernel): - return type(self)(self.axis, - self.inner_kernel.replace_base_kernel(new_base_kernel)) + @overload + def postprocess_at_target( + self, expr: ExprDerivativeTaker, bvec: sp.Matrix, + ) -> DifferentiatedExprDerivativeTaker: ... - def replace_inner_kernel(self, new_inner_kernel): - return type(self)(self.axis, new_inner_kernel) + @override + def postprocess_at_target( + self, expr: sym.Expr | ExprDerivativeTaker, bvec: sp.Matrix, + ) -> sym.Expr | DifferentiatedExprDerivativeTaker: + inner_expr = self.inner_kernel.postprocess_at_target(expr, bvec) + target_vec = sym.make_sym_vector(self.target_array_name, self.dim) - def postprocess_at_target(self, expr, avec): - from sumpy.symbolic import make_sym_vector as make_sympy_vector - from sumpy.tools import (ExprDerivativeTaker, - DifferentiatedExprDerivativeTaker) + zeros = tuple([0]*self.dim) + mult = cast("sym.Symbol", target_vec[self.axis]) - expr = self.inner_kernel.postprocess_at_target(expr, avec) - target_vec = make_sympy_vector(self.target_array_name, self.dim) + if isinstance(inner_expr, DifferentiatedExprDerivativeTaker): + transform: DerivativeCoeffDict = { + mi: coeff * mult for mi, coeff in + inner_expr.derivative_coeff_dict.items()} - zeros = tuple([0]*self.dim) - mult = target_vec[self.axis] - - if isinstance(expr, DifferentiatedExprDerivativeTaker): - transform = {mi: coeff * mult for mi, coeff in - expr.derivative_coeff_dict.items()} - return DifferentiatedExprDerivativeTaker(expr.taker, transform) - elif isinstance(expr, ExprDerivativeTaker): - return DifferentiatedExprDerivativeTaker({zeros: mult}) + return DifferentiatedExprDerivativeTaker(inner_expr.taker, transform) + elif isinstance(inner_expr, ExprDerivativeTaker): + return DifferentiatedExprDerivativeTaker(expr.orig_expr, {zeros: mult}) else: - return mult * expr + return mult * inner_expr - def get_code_transformer(self): + @override + def get_code_transformer(self) -> Callable[[Expression], Expression]: from sumpy.codegen import VectorComponentRewriter - vcr = VectorComponentRewriter([self.target_array_name]) - from pymbolic.primitives import Variable - via = _VectorIndexAdder(self.target_array_name, (Variable("itgt"),)) + vcr = VectorComponentRewriter(frozenset([self.target_array_name])) + via = _VectorIndexAdder(self.target_array_name, (prim.Variable("itgt"),)) inner_transform = self.inner_kernel.get_code_transformer() - def transform(expr): + def transform(expr: Expression) -> Expression: return via(vcr(inner_transform(expr))) return transform - mapper_method = "map_target_point_multiplier" + @override + def get_pde_as_diff_op(self) -> LinearPDESystemOperator: + raise NotImplementedError("no PDE is known") + + @override + def replace_base_kernel(self, new_base_kernel: ScalarKernel) -> ScalarKernel: + return type(self)(self.axis, + self.inner_kernel.replace_base_kernel(new_base_kernel)) + + def replace_inner_kernel(self, new_inner_kernel: ScalarKernel) -> ScalarKernel: + return type(self)(self.axis, new_inner_kernel) # }}} # {{{ kernel mappers -class KernelMapper: - def rec(self, kernel): +ResultT = TypeVar("ResultT") + + +class KernelMapper(Generic[ResultT]): + """ + .. automethod:: __call__ + """ + def rec(self, kernel: ScalarKernel) -> ResultT: try: - method = getattr(self, kernel.mapper_method) - except AttributeError: - raise RuntimeError("{} cannot handle {}".format( - type(self), type(kernel))) + method = cast( + "Callable[[ScalarKernel], ResultT]", + getattr(self, kernel.mapper_method)) + except AttributeError as err: + raise RuntimeError(f"{type(self)} cannot handle {type(kernel)}") from err else: return method(kernel) - __call__ = rec + def __call__(self, kernel: ScalarKernel) -> ResultT: + return self.rec(kernel) -class KernelCombineMapper(KernelMapper): - def map_difference_kernel(self, kernel): - return self.combine([ - self.rec(kernel.kernel_plus), - self.rec(kernel.kernel_minus)]) +class KernelCombineMapper(KernelMapper[ResultT], ABC): + """ + .. automethod:: combine + """ + + @abstractmethod + def combine(self, values: Iterable[ResultT]) -> ResultT: + raise NotImplementedError - def map_axis_target_derivative(self, kernel): + def map_axis_target_derivative( + self, kernel: AxisTargetDerivative) -> ResultT: return self.rec(kernel.inner_kernel) - map_directional_target_derivative = map_axis_target_derivative - map_directional_source_derivative = map_axis_target_derivative - map_axis_source_derivative = map_axis_target_derivative - map_target_point_multiplier = map_axis_target_derivative + def map_axis_source_derivative( + self, kernel: AxisSourceDerivative) -> ResultT: + return self.rec(kernel.inner_kernel) + def map_directional_source_derivative( + self, kernel: DirectionalSourceDerivative) -> ResultT: + return self.rec(kernel.inner_kernel) -class KernelIdentityMapper(KernelMapper): - def map_expression_kernel(self, kernel): - return kernel + def map_target_point_multiplier( + self, kernel: TargetPointMultiplier) -> ResultT: + return self.rec(kernel.inner_kernel) - map_laplace_kernel = map_expression_kernel - map_biharmonic_kernel = map_expression_kernel - map_helmholtz_kernel = map_expression_kernel - map_yukawa_kernel = map_expression_kernel - map_elasticity_kernel = map_expression_kernel - map_line_of_compression_kernel = map_expression_kernel - map_stresslet_kernel = map_expression_kernel - def map_axis_target_derivative(self, kernel): +class KernelIdentityMapper(KernelMapper[ScalarKernel]): + def map_expression_kernel(self, kernel: ExpressionKernel) -> ScalarKernel: + return kernel + + map_laplace_kernel: Callable[[Self, LaplaceKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_biharmonic_kernel: Callable[[Self, BiharmonicKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_helmholtz_kernel: Callable[[Self, HelmholtzKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_yukawa_kernel: Callable[[Self, YukawaKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_elasticity_kernel: Callable[[Self, ElasticityComponentKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_elasticity_stress_kernel: Callable[[Self, ElasticityStressComponentKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_line_of_compression_kernel: Callable[[Self, LineOfCompressionKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_stokeslet_kernel: Callable[[Self, StokesletComponentKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_stresslet_kernel: Callable[[Self, StressletComponentKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_brinkmanlet_kernel: Callable[[Self, BrinkmanletComponentKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_brinkman_stress_kernel: Callable[[Self, BrinkmanStressComponentKernel], ScalarKernel] = map_expression_kernel # ruff:ignore[line-too-long] + map_heat_kernel: Callable[[Self, HeatKernel], ScalarKernel] = map_expression_kernel + + def map_axis_target_derivative(self, kernel: AxisTargetDerivative) -> ScalarKernel: return type(kernel)(kernel.axis, self.rec(kernel.inner_kernel)) - map_axis_source_derivative = map_axis_target_derivative - map_target_point_multiplier = map_axis_target_derivative + def map_axis_source_derivative(self, kernel: AxisSourceDerivative) -> ScalarKernel: + return type(kernel)(kernel.axis, self.rec(kernel.inner_kernel)) - def map_directional_target_derivative(self, kernel): - return type(kernel)( - self.rec(kernel.inner_kernel), - kernel.dir_vec_name) + def map_target_point_multiplier(self, kernel: TargetPointMultiplier) -> ScalarKernel: # ruff:ignore[line-too-long] + return type(kernel)(kernel.axis, self.rec(kernel.inner_kernel)) - map_directional_source_derivative = map_directional_target_derivative + def map_directional_source_derivative( + self, kernel: DirectionalSourceDerivative) -> ScalarKernel: + return type(kernel)(self.rec(kernel.inner_kernel), + dir_vec_name=kernel.dir_vec_name) class AxisSourceDerivativeRemover(KernelIdentityMapper): - def map_axis_source_derivative(self, kernel): + """Removes all axis source derivatives from the kernel.""" + + @override + def map_axis_source_derivative(self, kernel: AxisSourceDerivative) -> ScalarKernel: return self.rec(kernel.inner_kernel) class AxisTargetDerivativeRemover(KernelIdentityMapper): - def map_axis_target_derivative(self, kernel): + """Removes all axis target derivatives from the kernel.""" + + @override + def map_axis_target_derivative(self, kernel: AxisTargetDerivative) -> ScalarKernel: return self.rec(kernel.inner_kernel) class TargetDerivativeRemover(AxisTargetDerivativeRemover): - def map_directional_target_derivative(self, kernel): - return self.rec(kernel.inner_kernel) + """Removes all target derivatives from the kernel.""" class SourceDerivativeRemover(AxisSourceDerivativeRemover): - def map_directional_source_derivative(self, kernel): + """Removes all source derivatives from the kernel.""" + + @override + def map_directional_source_derivative( + self, kernel: DirectionalSourceDerivative) -> ScalarKernel: return self.rec(kernel.inner_kernel) class TargetTransformationRemover(TargetDerivativeRemover): - def map_target_point_multiplier(self, kernel): + """Removes all target transformations from the kernel.""" + + @override + def map_target_point_multiplier( + self, kernel: TargetPointMultiplier) -> ScalarKernel: return self.rec(kernel.inner_kernel) SourceTransformationRemover = SourceDerivativeRemover -class DerivativeCounter(KernelCombineMapper): - def combine(self, values): - return max(values) +class DerivativeCounter(KernelCombineMapper[int]): + """Counts the number of derivatives in the kernel.""" - def map_expression_kernel(self, kernel): - return 0 + @override + def combine(self, values: Iterable[int]) -> int: + return sum(values) - map_laplace_kernel = map_expression_kernel - map_biharmonic_kernel = map_expression_kernel - map_helmholtz_kernel = map_expression_kernel - map_yukawa_kernel = map_expression_kernel - map_line_of_compression_kernel = map_expression_kernel - map_stresslet_kernel = map_expression_kernel + def map_expression_kernel(self, kernel: ExpressionKernel) -> int: + return 0 - def map_axis_target_derivative(self, kernel): - return 1 + self.rec(kernel.inner_kernel) + map_laplace_kernel: \ + Callable[[Self, LaplaceKernel], int] = map_expression_kernel + map_biharmonic_kernel: \ + Callable[[Self, BiharmonicKernel], int] = map_expression_kernel + map_helmholtz_kernel: \ + Callable[[Self, HelmholtzKernel], int] = map_expression_kernel + map_yukawa_kernel: \ + Callable[[Self, YukawaKernel], int] = map_expression_kernel + map_elasticity_kernel: \ + Callable[[Self, ElasticityComponentKernel], int] = map_expression_kernel + map_elasticity_stress_kernel: \ + Callable[[Self, ElasticityStressComponentKernel], int] = map_expression_kernel + map_line_of_compression_kernel: \ + Callable[[Self, LineOfCompressionKernel], int] = map_expression_kernel + map_stokeslet_kernel: \ + Callable[[Self, StokesletComponentKernel], int] = map_expression_kernel + map_stresslet_kernel: \ + Callable[[Self, StressletComponentKernel], int] = map_expression_kernel + map_brinkmanlet_kernel: \ + Callable[[Self, BrinkmanletComponentKernel], int] = map_expression_kernel + map_brinkman_stress_kernel: \ + Callable[[Self, BrinkmanStressComponentKernel], int] = map_expression_kernel + map_heat_kernel: \ + Callable[[Self, HeatKernel], int] = map_expression_kernel + + @override + def map_axis_target_derivative(self, kernel: AxisTargetDerivative) -> int: + return self.combine([1, self.rec(kernel.inner_kernel)]) map_directional_target_derivative = map_axis_target_derivative map_directional_source_derivative = map_axis_target_derivative @@ -1383,24 +2758,35 @@ def map_axis_target_derivative(self, kernel): # }}} -def to_kernel_and_args(kernel_like): - if (isinstance(kernel_like, tuple) - and len(kernel_like) == 2 - and isinstance(kernel_like[0], Kernel)): - # already gone through to_kernel_and_args - return kernel_like +# {{{ deprecations + +# TODO: once these deprecations expire, rename +# ElasticitySystemKernel -> ElasticityKernel +# ... +_DEPRECATED_CLASSES = { + "BrinkmanStressKernel": (BrinkmanStressComponentKernel, 2027), + "BrinkmanletKernel": (BrinkmanletComponentKernel, 2027), + "ElasticityKernel": (ElasticityComponentKernel, 2027), + "Kernel": (ScalarKernel, 2027), + "StokesletKernel": (StokesletComponentKernel, 2027), + "StressletKernel": (StressletComponentKernel, 2027), +} - if not isinstance(kernel_like, Kernel): - if kernel_like == 0: - return LaplaceKernel(), {} - elif isinstance(kernel_like, str): - return HelmholtzKernel(None), {"k": var(kernel_like)} - else: - raise ValueError("Only Kernel instances, 0 (for Laplace) and " - "variable names (strings) " - "for the Helmholtz parameter are allowed as kernels.") - return kernel_like, {} +def __getattr__(name: str) -> Any: + result = _DEPRECATED_CLASSES.get(name) + if result is not None: + cls, year = result + from warnings import warn + warn(f"'sumpy.kernel.{name}' is deprecated. " + f"Use 'sumpy.kernel.{cls.__name__}' instead. " + f"'sumpy.kernel.{name}' will continue to work until {year}.", + DeprecationWarning, stacklevel=2) + return cls + else: + raise AttributeError(name) + +# }}} # vim: fdm=marker diff --git a/sumpy/p2e.py b/sumpy/p2e.py index 177d0b586..4ae9be98b 100644 --- a/sumpy/p2e.py +++ b/sumpy/p2e.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2013 Andreas Kloeckner" __license__ = """ @@ -20,13 +23,21 @@ THE SOFTWARE. """ +import logging +from typing import TYPE_CHECKING + import numpy as np + import loopy as lp -from loopy.version import MOST_RECENT_LANGUAGE_VERSION -from sumpy.tools import KernelCacheWrapper, KernelComputation +from sumpy.array_context import make_loopy_program +from sumpy.tools import KernelCacheMixin, KernelComputation + + +if TYPE_CHECKING: + from arraycontext import ArrayContext + -import logging logger = logging.getLogger(__name__) @@ -41,16 +52,15 @@ """ -# {{{ P2E base class +# {{{ P2EBase: base class -class P2EBase(KernelComputation, KernelCacheWrapper): +class P2EBase(KernelCacheMixin, KernelComputation): """Common input processing for kernel computations. .. automethod:: __init__ """ - def __init__(self, ctx, expansion, kernels=None, - name=None, device=None, strength_usage=None): + def __init__(self, expansion, kernels=None, name=None, strength_usage=None): """ :arg expansion: a subclass of :class:`sumpy.expansion.ExpansionBase` :arg kernels: if not provided, the kernel of the *expansion* is used. @@ -62,61 +72,43 @@ def __init__(self, ctx, expansion, kernels=None, number of strength arrays that need to be passed in. By default all kernels use the same strength. """ - from sumpy.kernel import (TargetTransformationRemover, - SourceTransformationRemover) + from sumpy.kernel import ( + SourceTransformationRemover, + TargetTransformationRemover, + ) txr = TargetTransformationRemover() sxr = SourceTransformationRemover() - if kernels is None: - kernels = [txr(expansion.kernel)] - else: - kernels = kernels - + kernels = [txr(expansion.kernel)] if kernels is None else kernels expansion = expansion.with_kernel(sxr(txr(expansion.kernel))) for knl in kernels: assert txr(knl) == knl assert sxr(knl) == expansion.kernel - KernelComputation.__init__(self, ctx=ctx, target_kernels=[], + KernelComputation.__init__(self, target_kernels=[], source_kernels=kernels, strength_usage=strength_usage, value_dtypes=None, - name=name, device=device) + name=name) self.expansion = expansion self.dim = expansion.dim - def get_loopy_instructions(self): - from sumpy.symbolic import make_sym_vector - avec = make_sym_vector("a", self.dim) - - import sumpy.symbolic as sp - rscale = sp.Symbol("rscale") - - from sumpy.assignment_collection import SymbolicAssignmentCollection - sac = SymbolicAssignmentCollection() - - strengths = [sp.Symbol(f"strength_{i}") for i in self.strength_usage] - coeffs = self.expansion.coefficients_from_source_vec(self.source_kernels, - avec, None, rscale, strengths, sac=sac) - - coeff_names = [] - for i, coeff in enumerate(coeffs): - sac.add_assignment(f"coeff{i}", coeff) - coeff_names.append(f"coeff{i}") - - sac.run_global_cse() - - code_transformers = [self.expansion.get_code_transformer()] \ - + [kernel.get_code_transformer() for kernel in self.source_kernels] - - from sumpy.codegen import to_loopy_insns - return to_loopy_insns( - sac.assignments.items(), - vector_names={"a"}, - pymbolic_expr_maps=code_transformers, - retain_names=coeff_names, - ) + def add_loopy_form_callable( + self, loopy_knl: lp.TranslationUnit) -> lp.TranslationUnit: + inner_knl = self.expansion.loopy_expansion_formation( + self.source_kernels, self.strength_usage, self.strength_count) + loopy_knl = lp.merge([loopy_knl, inner_knl]) + loopy_knl = lp.inline_callable_kernel(loopy_knl, "p2e") + loopy_knl = lp.remove_unused_inames(loopy_knl) + for kernel in self.source_kernels: + loopy_knl = kernel.prepare_loopy_kernel(loopy_knl) + return lp.tag_array_axes(loopy_knl, "strengths", "sep,C") + + def get_loopy_args(self): + from sumpy.tools import gather_loopy_source_arguments + return gather_loopy_source_arguments( + (self.expansion, *tuple(self.source_kernels))) def get_cache_key(self): return (type(self).__name__, self.name, self.expansion, @@ -131,44 +123,54 @@ def get_optimized_kernel(self, sources_is_obj_array, centers_is_obj_array): knl = lp.tag_array_axes(knl, "centers", "sep,C") knl = self._allow_redundant_execution_of_knl_scaling(knl) - knl = lp.set_options(knl, + return lp.set_options(knl, enforce_variable_access_ordered="no_check") - return knl - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): from sumpy.tools import is_obj_array_like sources = kwargs.pop("sources") centers = kwargs.pop("centers") - knl = self.get_cached_optimized_kernel( - sources_is_obj_array=is_obj_array_like(sources), - centers_is_obj_array=is_obj_array_like(centers)) # "1" may be passed for rscale, which won't have its type # meaningfully inferred. Make the type of rscale explicit. dtype = centers[0].dtype if is_obj_array_like(centers) else centers.dtype rscale = dtype.type(kwargs.pop("rscale")) - return knl(queue, sources=sources, centers=centers, rscale=rscale, **kwargs) + knl = self.get_cached_kernel( + sources_is_obj_array=is_obj_array_like(sources), + centers_is_obj_array=is_obj_array_like(centers)) + + result = actx.call_loopy( + knl, + sources=sources, centers=centers, rscale=rscale, + **kwargs) + + return result["tgt_expansions"] # }}} -# {{{ P2E from single box (P2M, likely) +# {{{ P2EFromSingleBox: P2E from single box (P2M, likely) class P2EFromSingleBox(P2EBase): """ .. automethod:: __call__ """ - default_name = "p2e_from_single_box" + @property + def default_name(self): + return "p2e_from_single_box" def get_kernel(self): ncoeffs = len(self.expansion) + loopy_args = self.get_loopy_args() - from sumpy.tools import gather_loopy_source_arguments - loopy_knl = lp.make_kernel([ + loopy_knl = make_loopy_program([ "{[isrc_box]: 0 <= isrc_box < nsrc_boxes}", - "{[isrc, idim]: isrc_start <= isrc < isrc_end and 0 <= idim < dim}", + "{[isrc]: isrc_start <= isrc < isrc_end}", + "{[idim]: 0 <= idim < dim}", + "{[icoeff]: 0 <= icoeff < ncoeffs}", + "{[istrength]: 0 <= istrength < nstrengths}", ], [""" for isrc_box <> src_ibox = source_boxes[isrc_box] @@ -177,25 +179,35 @@ def get_kernel(self): <> center[idim] = centers[idim, src_ibox] {id=fetch_center} + <> coeffs[icoeff] = 0 {id=init_coeffs,dup=icoeff} for isrc - <> a[idim] = center[idim] - sources[idim, isrc] {dup=idim} - """] + [ - f"<> strength_{i} = strengths[{i}, isrc]" - for i in set(self.strength_usage) - ] + self.get_loopy_instructions() + [""" + <> source[idim] = sources[idim, isrc] \ + {dup=idim,id=fetch_src} + <> strength[istrength] = strengths[istrength, isrc] \ + {dup=istrength,id=fetch_strength} + [icoeff]: coeffs[icoeff] = p2e( + [icoeff]: coeffs[icoeff], + [idim]: center[idim], + [idim]: source[idim], + [istrength]: strength[istrength], + rscale, + isrc, + nsources, + sources, + """ + ",".join(arg.name for arg in loopy_args) + """ + ) {id=update_result, \ + dep=fetch_center:fetch_src:init_coeffs} end - """] + [f""" - tgt_expansions[src_ibox - tgt_base_ibox, {coeffidx}] = \ - simul_reduce(sum, isrc, coeff{coeffidx}) \ - {{id_prefix=write_expn}} - """ for coeffidx in range(ncoeffs)] + [""" + tgt_expansions[src_ibox - tgt_base_ibox, icoeff] = \ + coeffs[icoeff] {id=write_expn,dup=icoeff,\ + dep=update_result:init_coeffs} end """], [ lp.GlobalArg("sources", None, shape=(self.dim, "nsources"), order="C"), lp.GlobalArg("strengths", None, - shape=("strength_count", "nsources"), dim_tags="sep,C"), + shape=(self.strength_count, "nsources")), lp.GlobalArg("box_source_starts, box_source_counts_nonchild", None, shape=None), lp.GlobalArg("centers", None, shape="dim, aligned_nboxes"), @@ -204,22 +216,19 @@ def get_kernel(self): shape=("nboxes", ncoeffs), offset=lp.auto), lp.ValueArg("nboxes, aligned_nboxes, tgt_base_ibox", np.int32), lp.ValueArg("nsources", np.int32), + *loopy_args, ... - ] + gather_loopy_source_arguments( - self.source_kernels + (self.expansion,)), + ], name=self.name, assumptions="nsrc_boxes>=1", silenced_warnings="write_race(write_expn*)", - default_offset=lp.auto, - fixed_parameters=dict(dim=self.dim, - strength_count=self.strength_count), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + fixed_parameters={ + "dim": self.dim, "nstrengths": self.strength_count, + "ncoeffs": ncoeffs}) - for knl in self.source_kernels: - loopy_knl = knl.prepare_loopy_kernel(loopy_knl) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - - return loopy_knl + loopy_knl = lp.tag_inames(loopy_knl, "istrength*:unr") + return self.add_loopy_form_callable(loopy_knl) def get_optimized_kernel(self, sources_is_obj_array, centers_is_obj_array): knl = super().get_optimized_kernel( @@ -227,10 +236,9 @@ def get_optimized_kernel(self, sources_is_obj_array, centers_is_obj_array): centers_is_obj_array=centers_is_obj_array) # FIXME - knl = lp.split_iname(knl, "isrc_box", 16, outer_tag="g.0") - return knl + return lp.split_iname(knl, "isrc_box", 16, outer_tag="g.0") - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): """ :arg source_boxes: an array of integer indices into *box_source_starts* and *box_source_counts_nonchild*. @@ -250,30 +258,32 @@ def __call__(self, queue, **kwargs): :returns: an array of *tgt_expansions*. """ - return super().__call__(queue, **kwargs) + return super().__call__(actx, **kwargs) # }}} -# {{{ P2E from CSR-like interaction list +# {{{ P2EFromCSR: P2E from CSR-like interaction list class P2EFromCSR(P2EBase): """ .. automethod:: __call__ """ - default_name = "p2e_from_csr" + @property + def default_name(self): + return "p2e_from_csr" def get_kernel(self): ncoeffs = len(self.expansion) + loopy_args = self.get_loopy_args() - from sumpy.tools import gather_loopy_source_arguments arguments = ( [ lp.GlobalArg("sources", None, shape=(self.dim, "nsources"), order="C"), lp.GlobalArg("strengths", None, - shape=("strength_count", "nsources"), dim_tags="sep,C"), + shape=(self.strength_count, "nsources")), lp.GlobalArg("source_box_starts,source_box_lists", None, shape=None, offset=lp.auto), lp.GlobalArg("box_source_starts,box_source_counts_nonchild", @@ -284,16 +294,18 @@ def get_kernel(self): lp.ValueArg("naligned_boxes,ntgt_level_boxes,tgt_base_ibox", np.int32), lp.ValueArg("nsources", np.int32), + *loopy_args, ... - ] + gather_loopy_source_arguments( - self.source_kernels + (self.expansion,))) + ]) - loopy_knl = lp.make_kernel( + loopy_knl = make_loopy_program( [ "{[itgt_box]: 0 <= itgt_box < ntgt_boxes}", "{[isrc_box]: isrc_box_start <= isrc_box < isrc_box_stop}", "{[isrc]: isrc_start <= isrc < isrc_end}", "{[idim]: 0 <= idim < dim}", + "{[icoeff]: 0 <= icoeff < ncoeffs}", + "{[istrength]: 0 <= istrength < nstrengths}", ], [""" for itgt_box @@ -303,6 +315,7 @@ def get_kernel(self): <> isrc_box_start = source_box_starts[itgt_box] <> isrc_box_stop = source_box_starts[itgt_box + 1] + <> coeffs[icoeff] = 0 {id=init_coeffs,dup=icoeff} for isrc_box <> src_ibox = source_box_lists[isrc_box] <> isrc_start = box_source_starts[src_ibox] @@ -310,35 +323,40 @@ def get_kernel(self): + box_source_counts_nonchild[src_ibox] for isrc - <> a[idim] = center[idim] - sources[idim, isrc] \ - {dup=idim} - """] + [ - f""" - <> strength_{i} = strengths[{i}, isrc] - """ for i in set(self.strength_usage) - ] + self.get_loopy_instructions() + [""" + <> source[idim] = sources[idim, isrc] \ + {dup=idim,id=fetch_src} + <> strength[istrength] = strengths[istrength, isrc] \ + {dup=istrength,id=fetch_strength} + [icoeff]: coeffs[icoeff] = p2e( + [icoeff]: coeffs[icoeff], + [idim]: center[idim], + [idim]: source[idim], + [istrength]: strength[istrength], + rscale, + isrc, + nsources, + sources, + """ + ",".join(arg.name for arg in loopy_args) + """ + ) {id=update_result, \ + dep=fetch_center:fetch_src:init_coeffs} end - end"""] + [f""" - tgt_expansions[tgt_ibox - tgt_base_ibox, {coeffidx}] = \ - simul_reduce(sum, (isrc_box, isrc), coeff{coeffidx}) \ - {{id_prefix=write_expn}} - """ for coeffidx in range(ncoeffs)] + [""" + end + tgt_expansions[tgt_ibox - tgt_base_ibox, icoeff] = \ + coeffs[icoeff] {id=write_expn,dup=icoeff, \ + dep=update_result:init_coeffs} end """], - arguments, + kernel_data=arguments, name=self.name, assumptions="ntgt_boxes>=1", silenced_warnings="write_race(write_expn*)", - default_offset=lp.auto, - fixed_parameters=dict(dim=self.dim, - strength_count=self.strength_count), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + fixed_parameters={"dim": self.dim, + "nstrengths": self.strength_count, + "ncoeffs": ncoeffs}) - for knl in self.source_kernels: - loopy_knl = knl.prepare_loopy_kernel(loopy_knl) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - - return loopy_knl + loopy_knl = lp.tag_inames(loopy_knl, "istrength*:unr") + return self.add_loopy_form_callable(loopy_knl) def get_optimized_kernel(self, sources_is_obj_array, centers_is_obj_array): knl = super().get_optimized_kernel( @@ -346,10 +364,9 @@ def get_optimized_kernel(self, sources_is_obj_array, centers_is_obj_array): centers_is_obj_array=centers_is_obj_array) # FIXME - knl = lp.split_iname(knl, "itgt_box", 16, outer_tag="g.0") - return knl + return lp.split_iname(knl, "itgt_box", 16, outer_tag="g.0") - def __call__(self, queue, **kwargs): + def __call__(self, actx: ArrayContext, **kwargs): """ :arg target_boxes: array of integer indices into *source_box_starts* and *centers*. @@ -368,7 +385,7 @@ def __call__(self, queue, **kwargs): :arg tgt_base_ibox: see :meth:`P2EFromSingleBox.__call__`. :arg tgt_expansion: see :meth:`P2EFromSingleBox.__call__`. """ - return super().__call__(queue, **kwargs) + return super().__call__(actx, **kwargs) # }}} diff --git a/sumpy/p2p.py b/sumpy/p2p.py index 381918462..b606737af 100644 --- a/sumpy/p2p.py +++ b/sumpy/p2p.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = """ Copyright (C) 2012 Andreas Kloeckner Copyright (C) 2018 Alexandru Fikl @@ -23,14 +26,29 @@ THE SOFTWARE. """ +import logging +from typing import TYPE_CHECKING, Any + import numpy as np +from typing_extensions import override + import loopy as lp -from loopy.version import MOST_RECENT_LANGUAGE_VERSION +from arraycontext import PyOpenCLArrayContext +from pytools import obj_array + +from sumpy.array_context import make_loopy_program +from sumpy.tools import KernelCacheMixin, KernelComputation, is_obj_array_like + + +if TYPE_CHECKING: + from collections.abc import Sequence -from sumpy.tools import ( - KernelComputation, KernelCacheWrapper, is_obj_array_like) + from arraycontext import Array, ArrayContext + from pytools.obj_array import ObjectArray1D +logger = logging.getLogger(__name__) + __doc__ = """ Particle-to-particle @@ -48,15 +66,15 @@ # LATER: # - Optimization for source == target (postpone) -# {{{ p2p base class +# {{{ P2PBase: base class -class P2PBase(KernelComputation, KernelCacheWrapper): - def __init__(self, ctx, target_kernels, exclude_self, strength_usage=None, - value_dtypes=None, name=None, device=None, source_kernels=None): +class P2PBase(KernelCacheMixin, KernelComputation): + def __init__(self, target_kernels, exclude_self, strength_usage=None, + value_dtypes=None, name=None, source_kernels=None): """ - :arg target_kernels: list of :class:`sumpy.kernel.Kernel` instances + :arg target_kernels: list of :class:`~sumpy.kernel.ScalarKernel` instances with only target derivatives. - :arg source_kernels: list of :class:`sumpy.kernel.Kernel` instances + :arg source_kernels: list of :class:`~sumpy.kernel.ScalarKernel` instances with only source derivatives. :arg strength_usage: A list of integers indicating which expression uses which source strength indicator. This implicitly specifies the @@ -64,8 +82,11 @@ def __init__(self, ctx, target_kernels, exclude_self, strength_usage=None, Default: all kernels use the same strength. """ from pytools import single_valued - from sumpy.kernel import (TargetTransformationRemover, - SourceTransformationRemover) + + from sumpy.kernel import ( + SourceTransformationRemover, + TargetTransformationRemover, + ) txr = TargetTransformationRemover() sxr = SourceTransformationRemover() @@ -74,33 +95,33 @@ def __init__(self, ctx, target_kernels, exclude_self, strength_usage=None, target_kernels = [sxr(knl) for knl in target_kernels] else: for knl in source_kernels: - assert(txr(knl) == knl) + assert txr(knl) == knl for knl in target_kernels: - assert(sxr(knl) == knl) + assert sxr(knl) == knl base_source_kernel = single_valued(sxr(knl) for knl in source_kernels) base_target_kernel = single_valued(txr(knl) for knl in target_kernels) assert base_source_kernel == base_target_kernel - KernelComputation.__init__(self, ctx=ctx, target_kernels=target_kernels, + KernelComputation.__init__(self, target_kernels=target_kernels, source_kernels=source_kernels, strength_usage=strength_usage, - value_dtypes=value_dtypes, name=name, device=device) + value_dtypes=value_dtypes, name=name) self.exclude_self = exclude_self - - self.dim = single_valued(knl.dim for knl in - list(self.target_kernels) + list(self.source_kernels)) + self.dim = single_valued([ + knl.dim for knl in self.target_kernels + self.source_kernels + ]) def get_cache_key(self): return (type(self).__name__, tuple(self.target_kernels), self.exclude_self, tuple(self.strength_usage), tuple(self.value_dtypes), - tuple(self.source_kernels), - self.device.hashable_model_and_version_identifier) + tuple(self.source_kernels)) def get_loopy_insns_and_result_names(self): - from sumpy.symbolic import make_sym_vector from pymbolic import var + from sumpy.symbolic import make_sym_vector + dvec = make_sym_vector("d", self.dim) from sumpy.assignment_collection import SymbolicAssignmentCollection @@ -120,17 +141,13 @@ def get_loopy_insns_and_result_names(self): expr_sum = out_knl.postprocess_at_target(expr_sum, dvec) exprs.append(expr_sum) - if self.exclude_self: - result_name_prefix = "pair_result_tmp" - else: - result_name_prefix = "pair_result" - + result_name_prefix = "pair_result_tmp" if self.exclude_self else "pair_result" result_names = [ sac.add_assignment(f"{result_name_prefix}_{i}", expr) for i, expr in enumerate(exprs) ] - sac.run_global_cse() + sac = sac.run_global_cse() from sumpy.codegen import to_loopy_insns loopy_insns = to_loopy_insns(sac.assignments.items(), @@ -173,10 +190,10 @@ def get_default_src_tgt_arguments(self): if self.exclude_self else []) + gather_loopy_source_arguments(self.source_kernels)) - def get_kernel(self): - raise NotImplementedError - - def get_optimized_kernel(self, targets_is_obj_array, sources_is_obj_array): + def get_optimized_kernel(self, *, + targets_is_obj_array: bool = False, + sources_is_obj_array: bool = False, + **kwargs: Any) -> lp.TranslationUnit: # FIXME knl = self.get_kernel() @@ -187,34 +204,32 @@ def get_optimized_kernel(self, targets_is_obj_array, sources_is_obj_array): knl = lp.split_iname(knl, "itgt", 1024, outer_tag="g.0") knl = self._allow_redundant_execution_of_knl_scaling(knl) - knl = lp.set_options(knl, - enforce_variable_access_ordered="no_check") - - return knl + return lp.set_options(knl, enforce_variable_access_ordered="no_check") # }}} -# {{{ P2P point-interaction calculation +# {{{ P2P: point-interaction calculation class P2P(P2PBase): """Direct applier for P2P interactions.""" - default_name = "p2p_apply" + @property + def default_name(self): + return "p2p_apply" def get_kernel(self): - loopy_insns, result_names = self.get_loopy_insns_and_result_names() - arguments = ( - self.get_default_src_tgt_arguments() - + [ + loopy_insns, _result_names = self.get_loopy_insns_and_result_names() + arguments = [ + *self.get_default_src_tgt_arguments(), lp.GlobalArg("strength", None, shape="nstrengths, nsources", dim_tags="sep,C"), lp.GlobalArg("result", None, shape="nresults, ntargets", dim_tags="sep,C") - ]) + ] - loopy_knl = lp.make_kernel([""" + loopy_knl = make_loopy_program([""" {[itgt, isrc, idim]: \ 0 <= itgt < ntargets and \ 0 <= isrc < nsources and \ @@ -233,46 +248,59 @@ def get_kernel(self): simul_reduce(sum, isrc, pair_result_{iknl}) {{inames=itgt}} """ for iknl in range(len(self.target_kernels))] + ["end"], - arguments, + kernel_data=arguments, assumptions="nsources>=1 and ntargets>=1", name=self.name, - default_offset=lp.auto, - fixed_parameters=dict( - dim=self.dim, - nstrengths=self.strength_count, - nresults=len(self.target_kernels)), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + fixed_parameters={ + "dim": self.dim, + "nstrengths": self.strength_count, + "nresults": len(self.target_kernels)}, + ) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - for knl in self.target_kernels + self.source_kernels: + for knl in [*self.target_kernels, *self.source_kernels]: loopy_knl = knl.prepare_loopy_kernel(loopy_knl) return loopy_knl - def __call__(self, queue, targets, sources, strength, **kwargs): - knl = self.get_cached_optimized_kernel( + def __call__(self, + actx: ArrayContext, + targets: ObjectArray1D[Array] | Array, + sources: ObjectArray1D[Array] | Array, + strength: Sequence[Array], + **kwargs: Any, + ) -> ObjectArray1D[Array]: + knl = self.get_cached_kernel( targets_is_obj_array=is_obj_array_like(targets), sources_is_obj_array=is_obj_array_like(sources)) - return knl(queue, sources=sources, targets=targets, strength=strength, - **kwargs) + result = actx.call_loopy( + knl, + sources=sources, + targets=targets, + strength=strength, + **kwargs) + + return obj_array.new_1d([result[f"result_s{i}"] for i in range(self.nresults)]) # }}} -# {{{ P2P matrix writer +# {{{ P2PMatrixGenerator: matrix writer class P2PMatrixGenerator(P2PBase): """Generator for P2P interaction matrix entries.""" - default_name = "p2p_matrix" + @property + def default_name(self): + return "p2p_matrix" def get_strength_or_not(self, isrc, kernel_idx): return 1 def get_kernel(self): - loopy_insns, result_names = self.get_loopy_insns_and_result_names() + loopy_insns, _result_names = self.get_loopy_insns_and_result_names() arguments = ( self.get_default_src_tgt_arguments() + [ @@ -280,7 +308,7 @@ def get_kernel(self): for i, dtype in enumerate(self.value_dtypes) ]) - loopy_knl = lp.make_kernel([""" + loopy_knl = make_loopy_program([""" {[itgt, isrc, idim]: \ 0 <= itgt < ntargets and \ 0 <= isrc < nsources and \ @@ -300,27 +328,33 @@ def get_kernel(self): arguments, assumptions="nsources>=1 and ntargets>=1", name=self.name, - fixed_parameters=dict(dim=self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + fixed_parameters={"dim": self.dim}, + ) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - for knl in self.target_kernels + self.source_kernels: + for knl in [*self.target_kernels, *self.source_kernels]: loopy_knl = knl.prepare_loopy_kernel(loopy_knl) return loopy_knl - def __call__(self, queue, targets, sources, **kwargs): - knl = self.get_cached_optimized_kernel( + def __call__(self, + actx: ArrayContext, + targets: ObjectArray1D[Array] | Array, + sources: ObjectArray1D[Array] | Array, + **kwargs: Any, + ) -> ObjectArray1D[Array]: + knl = self.get_cached_kernel( targets_is_obj_array=is_obj_array_like(targets), sources_is_obj_array=is_obj_array_like(sources)) - return knl(queue, sources=sources, targets=targets, **kwargs) + result = actx.call_loopy(knl, sources=sources, targets=targets, **kwargs) + return obj_array.new_1d([result[f"result_{i}"] for i in range(self.nresults)]) # }}} -# {{{ P2P matrix subset generator +# {{{ P2PMatrixSubsetGenerator: matrix subset generator class P2PMatrixSubsetGenerator(P2PBase): """Generator for a subset of P2P interaction matrix entries. @@ -331,13 +365,15 @@ class P2PMatrixSubsetGenerator(P2PBase): .. automethod:: __call__ """ - default_name = "p2p_subset" + @property + def default_name(self): + return "p2p_subset" def get_strength_or_not(self, isrc, kernel_idx): return 1 def get_kernel(self): - loopy_insns, result_names = self.get_loopy_insns_and_result_names() + loopy_insns, _result_names = self.get_loopy_insns_and_result_names() arguments = ( self.get_default_src_tgt_arguments() + [ @@ -350,7 +386,7 @@ def get_kernel(self): for i, dtype in enumerate(self.value_dtypes) ]) - loopy_knl = lp.make_kernel( + loopy_knl = make_loopy_program( "{[imat, idim]: 0 <= imat < nresult and 0 <= idim < dim}", self.get_kernel_scaling_assignments() # NOTE: itgt, isrc need to always be defined in case a statement @@ -377,14 +413,14 @@ def get_kernel(self): assumptions="nresult>=1", silenced_warnings="write_race(write_p2p*)", name=self.name, - fixed_parameters=dict(dim=self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + fixed_parameters={"dim": self.dim}, + ) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - loopy_knl = lp.add_dtypes(loopy_knl, - dict(nsources=np.int32, ntargets=np.int32)) + loopy_knl = lp.add_dtypes( + loopy_knl, {"nsources": np.int32, "ntargets": np.int32}) - for knl in self.target_kernels + self.source_kernels: + for knl in [*self.target_kernels, *self.source_kernels]: loopy_knl = knl.prepare_loopy_kernel(loopy_knl) return loopy_knl @@ -400,11 +436,18 @@ def get_optimized_kernel(self, targets_is_obj_array, sources_is_obj_array): knl = lp.split_iname(knl, "imat", 1024, outer_tag="g.0") knl = self._allow_redundant_execution_of_knl_scaling(knl) - knl = lp.set_options(knl, + return lp.set_options(knl, enforce_variable_access_ordered="no_check") - return knl - def __call__(self, queue, targets, sources, tgtindices, srcindices, **kwargs): + def __call__(self, + actx: ArrayContext, + targets: ObjectArray1D[Array] | Array, + sources: ObjectArray1D[Array] | Array, + *, + tgtindices: Array, + srcindices: Array, + **kwargs: Any, + ) -> ObjectArray1D[Array]: """Evaluate a subset of the P2P matrix interactions. :arg targets: target point coordinates, which can be an object @@ -420,29 +463,39 @@ def __call__(self, queue, targets, sources, tgtindices, srcindices, **kwargs): :returns: a one-dimensional array of interactions, for each index pair in (*srcindices*, *tgtindices*) """ - knl = self.get_cached_optimized_kernel( + knl = self.get_cached_kernel( targets_is_obj_array=is_obj_array_like(targets), sources_is_obj_array=is_obj_array_like(sources)) - return knl(queue, - targets=targets, - sources=sources, - tgtindices=tgtindices, - srcindices=srcindices, **kwargs) + result = actx.call_loopy( + knl, + targets=targets, + sources=sources, + tgtindices=tgtindices, + srcindices=srcindices, **kwargs) + + return obj_array.new_1d([result[f"result_{i}"] for i in range(self.nresults)]) # }}} -# {{{ P2P from CSR-like interaction list +# {{{ P2PFromCSR: P2P from CSR-like interaction list class P2PFromCSR(P2PBase): - default_name = "p2p_from_csr" - - def get_kernel(self, max_nsources_in_one_box, max_ntargets_in_one_box, - gpu=False, nsplit=32): - loopy_insns, result_names = self.get_loopy_insns_and_result_names() - arguments = self.get_default_src_tgt_arguments() \ - + [ + @property + @override + def default_name(self): + return "p2p_from_csr" + + @override + def get_kernel(self, *, + max_nsources_in_one_box: int = 32, + max_ntargets_in_one_box: int = 32, + work_items_per_group: int = 32, + is_gpu: bool = False, **kwargs: Any) -> lp.TranslationUnit: + loopy_insns, _result_names = self.get_loopy_insns_and_result_names() + arguments = [ + *self.get_default_src_tgt_arguments(), lp.GlobalArg("box_target_starts", None, shape=None), lp.GlobalArg("box_target_counts_nonchild", @@ -460,7 +513,7 @@ def get_kernel(self, max_nsources_in_one_box, max_ntargets_in_one_box, lp.GlobalArg("result", None, shape="noutputs, ntargets", dim_tags="sep,C"), lp.TemporaryVariable("tgt_center", shape=(self.dim,)), - "..." + ... ] domains = [ @@ -468,14 +521,11 @@ def get_kernel(self, max_nsources_in_one_box, max_ntargets_in_one_box, "{[iknl]: 0 <= iknl < noutputs}", "{[isrc_box]: isrc_box_start <= isrc_box < isrc_box_end}", "{[idim]: 0 <= idim < dim}", - "{[istrength]: 0 <= istrength < nstrengths}", - "{[isrc]: isrc_start <= isrc < isrc_end}" ] - src_outer_limit = (max_nsources_in_one_box - 1) // nsplit - tgt_outer_limit = (max_ntargets_in_one_box - 1) // nsplit + tgt_outer_limit = (max_ntargets_in_one_box - 1) // work_items_per_group - if gpu: + if is_gpu: arguments += [ lp.TemporaryVariable("local_isrc", shape=(self.dim, max_nsources_in_one_box)), @@ -483,78 +533,90 @@ def get_kernel(self, max_nsources_in_one_box, max_ntargets_in_one_box, shape=(self.strength_count, max_nsources_in_one_box)), ] domains += [ - "{[inner]: 0 <= inner < nsplit}", + "{[istrength]: 0 <= istrength < nstrengths}", + "{[inner]: 0 <= inner < work_items_per_group}", "{[itgt_offset_outer]: 0 <= itgt_offset_outer <= tgt_outer_limit}", - "{[isrc_offset_outer]: 0 <= isrc_offset_outer <= src_outer_limit}", + "{[isrc_prefetch]: 0 <= isrc_prefetch < max_nsources_in_one_box}", + ("{[isrc_offset]: 0 <= isrc_offset < max_nsources_in_one_box" + " and isrc_offset < isrc_end - isrc_start}"), ] else: domains += [ "{[itgt]: itgt_start <= itgt < itgt_end}", + "{[isrc]: isrc_start <= isrc < isrc_end}" ] # There are two algorithms here because pocl-pthread 1.9 miscompiles # the "gpu" kernel with prefetching. - if gpu: + if is_gpu: instructions = (self.get_kernel_scaling_assignments() + [""" for itgt_box - <> tgt_ibox = target_boxes[itgt_box] - <> itgt_start = box_target_starts[tgt_ibox] - <> itgt_end = itgt_start + box_target_counts_nonchild[tgt_ibox] - - <> isrc_box_start = source_box_starts[itgt_box] - <> isrc_box_end = source_box_starts[itgt_box+1] + <> tgt_ibox = target_boxes[itgt_box] {id=init_0} + <> itgt_start = box_target_starts[tgt_ibox] {id=init_1} + <> itgt_end = itgt_start + box_target_counts_nonchild[tgt_ibox] \ + {id=init_2} + <> isrc_box_start = source_box_starts[itgt_box] {id=init_3} + <> isrc_box_end = source_box_starts[itgt_box+1] {id=init_4} for itgt_offset_outer - <> itgt_offset = itgt_offset_outer * nsplit + inner - <> itgt = itgt_offset + itgt_start - <> cond_itgt = itgt < itgt_end - <> acc[iknl] = 0 {id=init_acc} - if cond_itgt - tgt_center[idim] = targets[idim, itgt] {id=prefetch_tgt,dup=idim} + for inner + <> itgt_offset = itgt_offset_outer * work_items_per_group + inner + <> itgt = itgt_offset + itgt_start + <> cond_itgt = itgt < itgt_end + <> acc[iknl] = 0 {id=init_acc} + if cond_itgt + tgt_center[idim] = targets[idim, itgt] {id=set_tgt,dup=idim} + end end for isrc_box <> src_ibox = source_box_lists[isrc_box] {id=src_box_insn_0} <> isrc_start = box_source_starts[src_ibox] {id=src_box_insn_1} <> isrc_end = isrc_start + box_source_counts_nonchild[src_ibox] \ {id=src_box_insn_2} - for isrc_offset_outer - <> isrc_offset = isrc_offset_outer * nsplit + inner - <> cond_isrc = isrc_offset < isrc_end - isrc_start - if cond_isrc - local_isrc[idim, isrc_offset] = sources[idim, - isrc_offset + isrc_start] {id=prefetch_src, dup=idim} - local_isrc_strength[istrength, isrc_offset] = strength[ - istrength, isrc_offset + isrc_start] {id=prefetch_charge} + for isrc_prefetch + <> cond_isrc_prefetch = isrc_prefetch < isrc_end - isrc_start \ + {id=cond_isrc_prefetch} + if cond_isrc_prefetch + local_isrc[idim, isrc_prefetch] = sources[idim, + isrc_prefetch + isrc_start] {id=prefetch_src, dup=idim} + local_isrc_strength[istrength, isrc_prefetch] = strength[ + istrength, isrc_prefetch + isrc_start] {id=prefetch_charge} end end - if cond_itgt - for isrc - <> d[idim] = (tgt_center[idim] - local_isrc[idim, - isrc - isrc_start]) {dep=prefetch_src:prefetch_tgt} + for inner + if cond_itgt + for isrc_offset + <> isrc = isrc_offset + isrc_start + <> d[idim] = (tgt_center[idim] - local_isrc[idim, + isrc_offset]) \ + {id=set_d,dep=prefetch_src:set_tgt} """] + [""" - <> is_self = (isrc == target_to_source[itgt]) + <> is_self = (isrc == target_to_source[itgt]) """ if self.exclude_self else ""] + [f""" - <> strength_{i} = local_isrc_strength[{i}, isrc - isrc_start] \ - {{dep=prefetch_charge}} + <> strength_{i} = local_isrc_strength[{i}, isrc_offset] \ + {{id=set_strength{i},dep=prefetch_charge}} """ for i in set(self.strength_usage)] + loopy_insns + [f""" - acc[{iknl}] = acc[{iknl}] + \ - pair_result_{iknl} \ - {{id=update_acc_{iknl}, dep=init_acc}} + acc[{iknl}] = acc[{iknl}] + \ + pair_result_{iknl} \ + {{id=update_acc_{iknl}, dep=init_acc}} """ for iknl in range(len(self.target_kernels))] + [""" + end end end end """] + [f""" + for inner if cond_itgt result[{iknl}, itgt] = knl_{iknl}_scaling * acc[{iknl}] \ - {{id_prefix=write_csr,dep=update_acc_{iknl}}} + {{id_prefix=write_csr,dep=update_acc_{iknl} }} + end end """ for iknl in range(len(self.target_kernels))] + [""" @@ -600,74 +662,175 @@ def get_kernel(self, max_nsources_in_one_box, max_ntargets_in_one_box, """] + [f""" result[{iknl}, itgt] = knl_{iknl}_scaling * acc[{iknl}] \ - {{id_prefix=write_csr,dep=update_acc_{iknl}}} + {{id_prefix=write_csr,dep=update_acc_{iknl} }} """ for iknl in range(len(self.target_kernels))] + [""" end end """]) - loopy_knl = lp.make_kernel( + loopy_knl = make_loopy_program( domains, instructions, - arguments, + kernel_data=arguments, assumptions="ntgt_boxes>=1", name=self.name, - silenced_warnings=["write_race(write_csr*)", "write_race(prefetch_src)", + silenced_warnings=[ + "write_race(write_csr*)", + "write_race(prefetch_src)", "write_race(prefetch_charge)"], - fixed_parameters=dict( - dim=self.dim, - nstrengths=self.strength_count, - nsplit=nsplit, - src_outer_limit=src_outer_limit, - tgt_outer_limit=tgt_outer_limit, - noutputs=len(self.target_kernels)), - lang_version=MOST_RECENT_LANGUAGE_VERSION) - - loopy_knl = lp.add_dtypes(loopy_knl, - dict(nsources=np.int32, ntargets=np.int32)) + fixed_parameters={ + "dim": self.dim, + "nstrengths": self.strength_count, + "max_nsources_in_one_box": max_nsources_in_one_box, + "max_ntargets_in_one_box": max_ntargets_in_one_box, + "work_items_per_group": work_items_per_group, + "tgt_outer_limit": tgt_outer_limit, + "noutputs": len(self.target_kernels)}, + ) + + loopy_knl = lp.add_dtypes(loopy_knl, { + "nsources": np.dtype(np.int32), + "ntargets": np.dtype(np.int32), + }) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") loopy_knl = lp.tag_inames(loopy_knl, "istrength*:unr") loopy_knl = lp.tag_array_axes(loopy_knl, "targets", "sep,C") loopy_knl = lp.tag_array_axes(loopy_knl, "sources", "sep,C") - for knl in self.target_kernels + self.source_kernels: + for knl in [*self.target_kernels, *self.source_kernels]: loopy_knl = knl.prepare_loopy_kernel(loopy_knl) return loopy_knl - def get_optimized_kernel(self, max_nsources_in_one_box, - max_ntargets_in_one_box): - import pyopencl as cl - dev = self.context.devices[0] - if dev.type & cl.device_type.CPU: - knl = self.get_kernel(max_nsources_in_one_box, - max_ntargets_in_one_box, gpu=False) + @override + def get_optimized_kernel(self, *, + max_nsources_in_one_box: int = 32, + max_ntargets_in_one_box: int = 32, + strength_dtype: np.dtype[Any] | None = None, + source_dtype: np.dtype[Any] | None = None, + local_mem_size: int = 32, + is_gpu: bool = False, **kwargs) -> lp.TranslationUnit: + if not is_gpu: + knl = self.get_kernel( + max_nsources_in_one_box=max_nsources_in_one_box, + max_ntargets_in_one_box=max_ntargets_in_one_box, + is_gpu=is_gpu) knl = lp.split_iname(knl, "itgt_box", 4, outer_tag="g.0") + knl = self._allow_redundant_execution_of_knl_scaling(knl) else: - knl = self.get_kernel(max_nsources_in_one_box, - max_ntargets_in_one_box, gpu=True, nsplit=32) + assert strength_dtype is not None + assert source_dtype is not None + + dtype_size = np.dtype(strength_dtype).alignment + work_items_per_group = min(256, max_ntargets_in_one_box) + total_local_mem = max_nsources_in_one_box * \ + (self.dim + self.strength_count) * dtype_size + # multiplying by 2 here to make sure at least 2 work groups + # can be scheduled at the same time for latency hiding + nprefetch = (2 * total_local_mem - 1) // local_mem_size + 1 + + knl = self.get_kernel( + max_nsources_in_one_box=max_nsources_in_one_box, + max_ntargets_in_one_box=max_ntargets_in_one_box, + work_items_per_group=work_items_per_group, + is_gpu=is_gpu) knl = lp.tag_inames(knl, {"itgt_box": "g.0", "inner": "l.0"}) knl = lp.set_temporary_address_space(knl, ["local_isrc", "local_isrc_strength"], lp.AddressSpace.LOCAL) - knl = lp.add_inames_for_unused_hw_axes(knl) - # knl = lp.set_options(knl, write_code=True) - - knl = self._allow_redundant_execution_of_knl_scaling(knl) - knl = lp.set_options(knl, - enforce_variable_access_ordered="no_check") - return knl + local_arrays = ["local_isrc", "local_isrc_strength"] + local_array_isrc_axis = [1, 1] + local_array_sizes = [self.dim, self.strength_count] + local_array_dtypes = [source_dtype, strength_dtype] + # By having a concatenated memory layout of the temporaries + # and marking the first axis as vec, we are transposing the + # the arrays and also making the access of the source + # co-ordinates and the strength for each source a coalesced + # access of 256 bits (assuming double precision) which is + # optimized for NVIDIA GPUs. On an NVIDIA Titan V, this + # optimization led to a 8% speedup in the performance. + if strength_dtype == source_dtype: + knl = lp.concatenate_arrays(knl, local_arrays, "local_isrc") + local_arrays = ["local_isrc"] + local_array_sizes = [self.dim + self.strength_count] + local_array_dtypes = [source_dtype] + # We try to mark the local arrays (sources, strengths) + # as vec for the first dimension + for i, (array_name, array_size, array_dtype) in \ + enumerate(zip(local_arrays, local_array_sizes, + local_array_dtypes, strict=True)): + if issubclass(array_dtype.type, np.complexfloating): + # pyopencl does not support complex data type vectors + continue + if array_size in [2, 3, 4, 8, 16]: + knl = lp.tag_array_axes(knl, array_name, "vec,C") + else: + # FIXME: check if CUDA + n = 16 // dtype_size + if n in [1, 2, 4, 8]: + knl = lp.split_array_axis(knl, array_name, 0, n) + knl = lp.tag_array_axes(knl, array_name, "C,vec,C") + local_array_isrc_axis[i] = 2 + + # We need to split isrc_prefetch and isrc_offset into chunks. + nsources = (max_nsources_in_one_box + nprefetch - 1) // nprefetch + for local_array, axis in zip(local_arrays, local_array_isrc_axis, + strict=True): + knl = lp.split_array_axis(knl, local_array, axis, nsources) + knl = lp.split_iname(knl, "isrc_prefetch", nsources, + outer_iname="iprefetch") + knl = lp.split_iname(knl, "isrc_prefetch_inner", work_items_per_group) + knl = lp.tag_inames(knl, {"isrc_prefetch_inner_inner": "l.0"}) + knl = lp.split_iname(knl, "isrc_offset", nsources, + outer_iname="iprefetch") + + # After splitting, the temporary array local_isrc need not + # be as large as before. Need to simplify before unprivatizing + knl = lp.simplify_indices(knl) + knl = lp.unprivatize_temporaries_with_inames(knl, + "iprefetch", only_var_names=frozenset(local_arrays)) + + knl = lp.add_inames_to_insn(knl, + "inner", "id:init_* or id:*_scaling or id:src_box_insn_*") + knl = lp.add_inames_to_insn(knl, "itgt_box", "id:*_scaling") + + return lp.set_options(knl, enforce_variable_access_ordered="no_check") + + def __call__(self, + actx: ArrayContext, + targets: ObjectArray1D[Array] | Array, + sources: ObjectArray1D[Array] | Array, + *, + max_nsources_in_one_box: int, + max_ntargets_in_one_box: int, + **kwargs: Any, + ) -> ObjectArray1D[Array]: + from sumpy.array_context import is_cl_cpu + + is_gpu = not is_cl_cpu(actx) + if is_gpu: + source_dtype = kwargs["sources"][0].dtype + strength_dtype = kwargs["strength"].dtype + else: + # these are unused for not GPU and defeats the caching + # set them to None to keep the caching across dtypes + source_dtype = None + strength_dtype = None - def __call__(self, queue, **kwargs): - max_nsources_in_one_box = kwargs.pop("max_nsources_in_one_box") - max_ntargets_in_one_box = kwargs.pop("max_ntargets_in_one_box") - knl = self.get_cached_optimized_kernel( + assert isinstance(actx, PyOpenCLArrayContext) + knl = self.get_cached_kernel( max_nsources_in_one_box=max_nsources_in_one_box, - max_ntargets_in_one_box=max_ntargets_in_one_box) - - return knl(queue, **kwargs) + max_ntargets_in_one_box=max_ntargets_in_one_box, + local_mem_size=actx.queue.device.local_mem_size, + is_gpu=is_gpu, + source_dtype=source_dtype, + strength_dtype=strength_dtype, + ) + + result = actx.call_loopy(knl, targets=targets, sources=sources, **kwargs) + return obj_array.new_1d([result[f"result_s{i}"] for i in range(self.nresults)]) # }}} diff --git a/sumpy/point_calculus.py b/sumpy/point_calculus.py index b80ab51f0..54a151de2 100644 --- a/sumpy/point_calculus.py +++ b/sumpy/point_calculus.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2017 Andreas Kloeckner" __license__ = """ @@ -19,30 +22,51 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. """ +from typing import TYPE_CHECKING, Any, Literal, TypeAlias, TypeVar, overload import numpy as np import numpy.linalg as la -from pytools import memoize_method + +from pytools import memoize_method, obj_array + +from sumpy.visualization import FieldPlotter + + +if TYPE_CHECKING: + from collections.abc import Callable, Sequence + + from optype.numpy import Array1D, Array2D, ArrayND, ToArray1D + __doc__ = """ +.. autodata:: NodesKind + :noindex: + +.. autoclass:: InexactT +.. class:: NodesKind + .. autoclass:: CalculusPatch .. autofunction:: frequency_domain_maxwell """ +InexactT = TypeVar("InexactT", bound=np.inexact[Any]) +NodesKind: TypeAlias = Literal["chebyshev", "equispaced", "legendre"] + + class CalculusPatch: """Sets up a grid of points on which derivatives can be calculated. Useful to verify that an evaluated potential actually solves a PDE. - .. attribute:: dim - - .. attribute:: points - - shape: ``(dim, npoints_total)`` + .. autoattribute:: dim + .. autoattribute:: npoints + .. autoattribute:: points + .. autoattribute:: center .. automethod:: weights .. automethod:: basis + .. automethod:: diff .. automethod:: dx .. automethod:: dy @@ -51,15 +75,36 @@ class CalculusPatch: .. automethod:: div .. automethod:: curl .. automethod:: eval_at_center - .. autoattribute:: x - .. autoattribute:: y - .. autoattribute:: z + + .. autoproperty:: x + .. autoproperty:: y + .. autoproperty:: z + .. automethod:: norm .. automethod:: plot_nodes .. automethod:: plot """ - def __init__(self, center, h=1e-1, order=4, nodes="chebyshev"): - self.center = center + + dim: int + npoints: int + points: Array2D[np.floating[Any]] + """Shape: ``(dim, npoints)``.""" + center: Array1D[np.floating[Any]] + + h: float + _points_1d: Array1D[np.floating[Any]] + _weights_1d: Array1D[np.floating[Any]] | None + _points_shaped: ArrayND[np.floating[Any]] + """An array of shape ``(dim, nnodes, ...)``.""" + _pshape: tuple[int, ...] + + def __init__(self, + center: ToArray1D[np.floating[Any]], + h: float = 1e-1, + order: int = 4, + nodes: NodesKind = "chebyshev") -> None: + center = np.asarray(center) + assert center.ndim == 1 npoints = order + 1 if nodes == "equispaced": @@ -67,13 +112,14 @@ def __init__(self, center, h=1e-1, order=4, nodes="chebyshev"): weights_1d = None elif nodes == "chebyshev": - a = np.arange(npoints, dtype=np.float64) + a = np.arange(npoints) points_1d = (h/2)*np.cos((2*(a+1)-1)/(2*npoints)*np.pi) weights_1d = None elif nodes == "legendre": - from scipy.special import legendre - points_1d, weights_1d, _ = legendre(npoints).weights.T + from numpy.polynomial.legendre import leggauss + + points_1d, weights_1d = leggauss(npoints) points_1d = points_1d * (h/2) weights_1d = weights_1d * (h/2) @@ -85,8 +131,8 @@ def __init__(self, center, h=1e-1, order=4, nodes="chebyshev"): self._points_1d = points_1d self._weights_1d = weights_1d - self.dim = dim = len(self.center) self.center = center + self.dim = dim = len(self.center) points_shaped = np.array(np.meshgrid( *[center[i] + points_1d for i in range(dim)], @@ -98,7 +144,7 @@ def __init__(self, center, h=1e-1, order=4, nodes="chebyshev"): self._pshape = points_shaped.shape[1:] @memoize_method - def _vandermonde_1d(self): + def _vandermonde_1d(self) -> Array2D[np.floating[Any]]: points_1d = self._points_1d npoints = len(self._points_1d) @@ -109,24 +155,34 @@ def _vandermonde_1d(self): return vandermonde @memoize_method - def _zero_eval_vec_1d(self): + def _zero_eval_vec_1d(self) -> Array1D[np.floating[Any]]: # The zeroth coefficient--all others involve x=0. return self._vandermonde_1d()[0] - def basis(self): + def basis(self) -> Sequence[ + Callable[[Array2D[np.floating[Any]]], Array1D[np.floating[Any]]] + ]: """ :returns: a :class:`list` containing functions that realize a high-order interpolation basis on the :py:attr:`points`. """ from pytools import indices_in_shape - from scipy.special import eval_chebyt - def eval_basis(ind, x): - result = 1 + def eval_chebyt(n: int, + x: Array1D[np.floating[Any]]) -> Array1D[np.floating[Any]]: + # T_n(x) = cos(n * arccos(x)), valid for x in [-1, 1] + return np.cos(n * np.arccos(x)) + + def eval_basis( + ind: tuple[int, ...], + x: Array2D[np.floating[Any]] + ) -> Array1D[np.floating[Any]]: + result = np.ones(x[0].shape, dtype=x.dtype) for i in range(self.dim): coord = (x[i] - self.center[i])/(self.h/2) result *= eval_chebyt(ind[i], coord) + return result from functools import partial @@ -135,8 +191,8 @@ def eval_basis(ind, x): for ind in indices_in_shape((self.npoints,)*self.dim)] @memoize_method - def weights(self): - """" + def weights(self) -> ArrayND[np.floating[Any]]: + """ :returns: a vector of high-order quadrature weights on the :attr:`points` """ @@ -152,7 +208,7 @@ def weights(self): return result.reshape(-1) @memoize_method - def _diff_mat_1d(self, nderivs): + def _diff_mat_1d(self, nderivs: int) -> Array2D[np.floating[Any]]: npoints = len(self._points_1d) vandermonde = self._vandermonde_1d() @@ -163,22 +219,33 @@ def _diff_mat_1d(self, nderivs): n_diff_mat = n_diff_mat.dot(coeff_diff_mat) deriv_coeffs_mat = la.solve(vandermonde.T, n_diff_mat.T).T - return vandermonde.dot(deriv_coeffs_mat) + return vandermonde @ deriv_coeffs_mat + + @overload + def diff(self, axis: int, f_values: np.number[Any] | complex, + nderivs: int = 1) -> Literal[0]: ... - def diff(self, axis, f_values, nderivs=1): + @overload + def diff(self, axis: int, f_values: Array1D[InexactT], + nderivs: int = 1) -> Array1D[InexactT]: ... + + def diff(self, + axis: int, + f_values: np.number[Any] | complex | Array1D[InexactT], + nderivs: int = 1 + ) -> Array1D[InexactT] | Literal[0]: """Return the derivative along *axis* of *f_values*. - :arg f_values: an array of shape ``(npoints_total,)`` - :returns: an array of shape ``(npoints_total,)`` + :arg f_values: an array of shape ``(npoints,)`` + :returns: an array of shape ``(npoints,)`` """ from numbers import Number - if isinstance(f_values, (np.number, Number)): + if isinstance(f_values, (int, float, complex, np.number, Number)): # constants differentiate to 0 return 0 dim = len(self.center) - assert axis < dim axes = "klmno" @@ -190,76 +257,92 @@ def diff(self, axis, f_values, nderivs=1): self._diff_mat_1d(nderivs), f_values.reshape(*self._pshape)).reshape(-1) - def dx(self, f_values): + def dx(self, f_values: Array1D[InexactT]) -> Array1D[InexactT]: return self.diff(0, f_values) - def dy(self, f_values): + def dy(self, f_values: Array1D[InexactT]) -> Array1D[InexactT]: return self.diff(1, f_values) - def dz(self, f_values): + def dz(self, f_values: Array1D[InexactT]) -> Array1D[InexactT]: return self.diff(2, f_values) - def laplace(self, f_values): + def laplace(self, f_values: Array1D[InexactT]) -> Array1D[InexactT]: """Return the Laplacian of *f_values*. - :arg f_values: an array of shape ``(npoints_total,)`` - :returns: an array of shape ``(npoints_total,)`` + :arg f_values: an array of shape ``(npoints,)`` + :returns: an array of shape ``(npoints,)`` representing the application + of the Laplacian to *f_values*. """ return sum(self.diff(iaxis, f_values, 2) for iaxis in range(self.dim)) - def div(self, arg): + def div(self, + arg: obj_array.ObjectArray1D[Array1D[InexactT]] + ) -> Array1D[InexactT]: r""" :arg arg: an object array containing - :class:`numpy.ndarray`\ s with shape ``(npoints_total,)``. + :class:`numpy.ndarray`\ s with shape ``(npoints,)``. """ - result = 0 + result: Array1D[InexactT] | int = 0 for i, arg_i in enumerate(arg): result = result + self.diff(i, arg_i) return result - def curl(self, arg): + def curl(self, + arg: obj_array.ObjectArray1D[Array1D[InexactT]] + ) -> obj_array.ObjectArray1D[Array1D[InexactT]]: r"""Take the curl of the vector quantity *arg*. :arg arg: an object array of shape ``(3,)`` containing - :class:`numpy.ndarray`\ s with shape ``(npoints_total,)``. + :class:`numpy.ndarray`\ s with shape ``(npoints,)``. """ + if arg.size != 3: + raise ValueError(f"can only take the curl of a 3d vector: {arg.shape}") + from pytools import levi_civita - from pytools.obj_array import make_obj_array - return make_obj_array([ + return obj_array.new_1d([ sum( levi_civita((k, m, n)) * self.diff(m, arg[n]) for m in range(3) for n in range(3)) for k in range(3)]) - def eval_at_center(self, f_values): + def eval_at_center( + self, f_values: Array1D[InexactT] + ) -> InexactT: """Interpolate *f_values* to the center point. - :arg f_values: an array of shape ``(npoints_total,)`` + :arg f_values: an array of shape ``(npoints,)`` :returns: a scalar. """ f_values = f_values.reshape(*self._pshape) + zero_eval_vec_1d = self._zero_eval_vec_1d + for _ in range(self.dim): - f_values = self._zero_eval_vec_1d.dot(f_values) + f_values = zero_eval_vec_1d @ f_values + assert f_values.ndim == 0 return f_values @property - def x(self): + def x(self) -> Array1D[np.floating[Any]]: return self.points[0] @property - def y(self): + def y(self) -> Array1D[np.floating[Any]]: return self.points[1] @property - def z(self): + def z(self) -> Array1D[np.floating[Any]]: return self.points[2] - def norm(self, arg, p): + def norm(self, + arg: ( + obj_array.ObjectArray1D[ArrayND[InexactT]] + | ArrayND[InexactT]), + p: float) -> np.floating[Any]: if p == np.inf: - if arg.dtype == np.object: + if arg.dtype == object: return max( la.norm(x_i, p) for x_i in arg) @@ -268,7 +351,13 @@ def norm(self, arg, p): else: raise ValueError("unsupported norm") - def plot_nodes(self): + def make_field_plotter(self) -> FieldPlotter: + return FieldPlotter(self.center, self.h, points=self._points_shaped) + + def plot_nodes(self) -> None: + if self.dim != 2: + raise ValueError(f"cannot plot {self.dim}d fields") + import matplotlib.pyplot as plt plt.gca().set_aspect("equal") plt.plot( @@ -276,7 +365,15 @@ def plot_nodes(self): self._points_shaped[1].reshape(-1), "o") - def plot(self, f): + def plot(self, f: Array1D[np.floating[Any]]) -> None: + from warnings import warn + warn(f"Calling '{type(self).__name__}.plot' is deprecated. Use " + f"'{type(self).__name__}.make_field_plotter' instead, which also " + "supports 3d fields.", DeprecationWarning, stacklevel=2) + + if self.dim != 2: + raise ValueError(f"cannot plot {self.dim}d fields") + f = f.reshape(*self._pshape) import matplotlib.pyplot as plt @@ -284,10 +381,18 @@ def plot(self, f): plt.contourf(self._points_1d, self._points_1d, f) -def frequency_domain_maxwell(cpatch, e, h, k): +def frequency_domain_maxwell( + cpatch: CalculusPatch, + e: obj_array.ObjectArray1D[Array1D[np.complexfloating[Any]]], + h: obj_array.ObjectArray1D[Array1D[np.complexfloating[Any]]], + k: complex + ) -> tuple[obj_array.ObjectArray1D[Array1D[np.complexfloating[Any]]], + obj_array.ObjectArray1D[Array1D[np.complexfloating[Any]]], + Array1D[np.complexfloating[Any]], + Array1D[np.complexfloating[Any]]]: mu = 1 epsilon = 1 - c = 1/np.sqrt(mu*epsilon) + c = np.float64(1/np.sqrt(mu*epsilon)) omega = k*c b = mu*h @@ -295,7 +400,7 @@ def frequency_domain_maxwell(cpatch, e, h, k): # https://en.wikipedia.org/w/index.php?title=Maxwell%27s_equations&oldid=798940325#Macroscopic_formulation # assumed time dependence exp(-1j*omega*t) - # Agrees with Jackson, Third Ed., (8.16) + # This agrees with Jackson, Third Ed., (8.16) resid_faraday = cpatch.curl(e) - 1j * omega * b resid_ampere = cpatch.curl(h) + 1j * omega * d diff --git a/sumpy/py.typed b/sumpy/py.typed new file mode 100644 index 000000000..e69de29bb diff --git a/sumpy/qbx.py b/sumpy/qbx.py index b479e1db3..edbe5519c 100644 --- a/sumpy/qbx.py +++ b/sumpy/qbx.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = """ Copyright (C) 2012 Andreas Kloeckner Copyright (C) 2018 Alexandru Fikl @@ -23,18 +26,31 @@ THE SOFTWARE. """ +import logging +from abc import ABC +from typing import TYPE_CHECKING, Any import numpy as np +from typing_extensions import override + import loopy as lp -from loopy.version import MOST_RECENT_LANGUAGE_VERSION +from pymbolic import parse +from pytools import memoize_method, obj_array + import sumpy.symbolic as sym -from pytools import memoize_method -from pymbolic import parse, var +from sumpy.array_context import is_cl_cpu, make_loopy_program +from sumpy.tools import KernelCacheMixin, KernelComputation, is_obj_array_like -from sumpy.tools import ( - KernelComputation, KernelCacheWrapper, is_obj_array_like) -import logging +if TYPE_CHECKING: + + from arraycontext import ArrayContext + + from sumpy.expansion.local import ( + LineTaylorLocalExpansion, + LocalExpansionBase, + ) + logger = logging.getLogger(__name__) @@ -51,7 +67,7 @@ """ -def stringify_expn_index(i): +def stringify_expn_index(i: tuple[int, ...] | int) -> str: if isinstance(i, tuple): return "_".join(stringify_expn_index(i_i) for i_i in i) else: @@ -64,18 +80,27 @@ def stringify_expn_index(i): # {{{ layer potential computation -# {{{ base class +# {{{ LayerPotentialBase: base class -class LayerPotentialBase(KernelComputation, KernelCacheWrapper): - def __init__(self, ctx, expansion, strength_usage=None, - value_dtypes=None, name=None, device=None, - source_kernels=None, target_kernels=None): +class LayerPotentialBase(KernelCacheMixin, KernelComputation, ABC): + expansion: LineTaylorLocalExpansion | LocalExpansionBase + def __init__(self, + expansion: LineTaylorLocalExpansion | LocalExpansionBase, + strength_usage=None, + value_dtypes=None, + name=None, + device=None, + source_kernels=None, + target_kernels=None): from pytools import single_valued - KernelComputation.__init__(self, ctx=ctx, target_kernels=target_kernels, - strength_usage=strength_usage, source_kernels=source_kernels, - value_dtypes=value_dtypes, name=name, device=device) + KernelComputation.__init__(self, + target_kernels=target_kernels, + source_kernels=source_kernels, + strength_usage=strength_usage, + value_dtypes=value_dtypes, + name=name) self.dim = single_valued(knl.dim for knl in self.target_kernels) self.expansion = expansion @@ -83,29 +108,26 @@ def __init__(self, ctx, expansion, strength_usage=None, def get_cache_key(self): return (type(self).__name__, self.expansion, tuple(self.target_kernels), tuple(self.source_kernels), tuple(self.strength_usage), - tuple(self.value_dtypes), - self.device.hashable_model_and_version_identifier) + tuple(self.value_dtypes)) def _expand(self, sac, avec, bvec, rscale, isrc): from sumpy.symbolic import PymbolicToSympyMapper conv = PymbolicToSympyMapper() - strengths = [conv(self.get_strength_or_not(isrc, idx)) + strengths = [conv.to_expr(self.get_strength_or_not(isrc, idx)) for idx in range(len(self.source_kernels))] - coefficients = self.expansion.coefficients_from_source_vec( + return self.expansion.coefficients_from_source_vec( self.source_kernels, avec, bvec, rscale=rscale, weights=strengths, sac=sac) - return coefficients - def _evaluate(self, sac, avec, bvec, rscale, expansion_nr, coefficients): from sumpy.expansion.local import LineTaylorLocalExpansion tgt_knl = self.target_kernels[expansion_nr] - if isinstance(tgt_knl, LineTaylorLocalExpansion): + if isinstance(self.expansion, LineTaylorLocalExpansion): # In LineTaylorLocalExpansion.evaluate, we can't run # postprocess_at_target because the coefficients are assigned # symbols and postprocess with a derivative will make them zero. - # Instead run postprocess here before the coeffients are assigned. - coefficients = [tgt_knl.postprocess_at_target(coeff, bvec) for + # Instead run postprocess here before the coefficients are assigned. + coefficients = [tgt_knl.postprocess_at_target(coeff, avec) for coeff in coefficients] assigned_coeffs = [ @@ -129,19 +151,20 @@ def get_loopy_insns_and_result_names(self): logger.info("compute expansion expressions: start") + import pymbolic as prim rscale = sym.Symbol("rscale") - isrc_sym = var("isrc") + isrc_sym = prim.var("isrc") coefficients = self._expand(sac, avec, bvec, rscale, isrc_sym) result_names = [self._evaluate(sac, avec, bvec, rscale, i, coefficients) - for i in range(len(self.target_kernels))] + for i in range(self.nresults)] logger.info("compute expansion expressions: done") - sac.run_global_cse() + sac = sac.run_global_cse() - pymbolic_expr_maps = [knl.get_code_transformer() for knl in ( - self.target_kernels + self.source_kernels)] + pymbolic_expr_maps = [knl.get_code_transformer() for knl in [ + *self.target_kernels, *self.source_kernels]] from sumpy.codegen import to_loopy_insns loopy_insns = to_loopy_insns( @@ -155,10 +178,12 @@ def get_loopy_insns_and_result_names(self): return loopy_insns, result_names def get_strength_or_not(self, isrc, kernel_idx): - return var(f"strength_{self.strength_usage[kernel_idx]}_isrc") + import pymbolic as prim + return prim.var(f"strength_{self.strength_usage[kernel_idx]}_isrc") def get_kernel_exprs(self, result_names): - exprs = [var(name) for i, name in enumerate(result_names)] + import pymbolic as prim + exprs = [prim.var(name) for i, name in enumerate(result_names)] return [lp.Assignment(id=None, assignee=f"pair_result_{i}", @@ -168,7 +193,7 @@ def get_kernel_exprs(self, result_names): def get_default_src_tgt_arguments(self): from sumpy.tools import gather_loopy_source_arguments - return ([ + return [ lp.GlobalArg("sources", None, shape=(self.dim, "nsources"), order="C"), lp.GlobalArg("targets", None, @@ -178,18 +203,19 @@ def get_default_src_tgt_arguments(self): lp.GlobalArg("expansion_radii", None, shape="ntargets"), lp.ValueArg("nsources", None), - lp.ValueArg("ntargets", None)] - + gather_loopy_source_arguments(self.source_kernels)) - - def get_kernel(self): - raise NotImplementedError + lp.ValueArg("ntargets", None), + *gather_loopy_source_arguments(self.source_kernels) + ] - def get_optimized_kernel(self, - targets_is_obj_array, sources_is_obj_array, centers_is_obj_array, + def get_optimized_kernel(self, *, + is_cpu: bool = True, + targets_is_obj_array: bool = False, + sources_is_obj_array: bool = False, + centers_is_obj_array: bool = False, # Used by pytential to override the name of the loop to be # parallelized. In the case of QBX, that's the loop over QBX # targets (not global targets). - itgt_name="itgt"): + itgt_name: str = "itgt", **kwargs: Any) -> lp.TranslationUnit: # FIXME specialize/tune for GPU/CPU loopy_knl = self.get_kernel() @@ -200,9 +226,7 @@ def get_optimized_kernel(self, if centers_is_obj_array: loopy_knl = lp.tag_array_axes(loopy_knl, "center", "sep,C") - import pyopencl as cl - dev = self.context.devices[0] - if dev.type & cl.device_type.CPU: + if is_cpu: loopy_knl = lp.split_iname(loopy_knl, itgt_name, 16, outer_tag="g.0", inner_tag="l.0") loopy_knl = lp.split_iname(loopy_knl, "isrc", 256) @@ -210,16 +234,18 @@ def get_optimized_kernel(self, ["isrc_outer", f"{itgt_name}_inner"]) else: from warnings import warn - warn(f"don't know how to tune layer potential computation for '{dev}'") + warn( + "Do not know how to tune layer potential computation for " + "non-CPU targets", stacklevel=1) loopy_knl = lp.split_iname(loopy_knl, itgt_name, 128, outer_tag="g.0") - loopy_knl = self._allow_redundant_execution_of_knl_scaling(loopy_knl) - return loopy_knl + return self._allow_redundant_execution_of_knl_scaling(loopy_knl) + # }}} -# {{{ direct applier +# {{{ LayerPotential: direct applier class LayerPotential(LayerPotentialBase): """Direct applier for the layer potential. @@ -227,7 +253,10 @@ class LayerPotential(LayerPotentialBase): .. automethod:: __call__ """ - default_name = "qbx_apply" + @property + @override + def default_name(self): + return "qbx_apply" @memoize_method def get_kernel(self): @@ -244,7 +273,7 @@ def get_kernel(self): for i in range(len(self.target_kernels)) ]) - loopy_knl = lp.make_kernel([""" + loopy_knl = make_loopy_program([""" {[itgt, isrc, idim]: \ 0 <= itgt < ntargets and \ 0 <= isrc < nsources and \ @@ -258,35 +287,36 @@ def get_kernel(self): + [f"<> strength_{i}_isrc = strength_{i}[isrc]" for i in range(self.strength_count)] + loopy_insns + kernel_exprs - + [""" - result_{i}[itgt] = knl_{i}_scaling * \ - simul_reduce(sum, isrc, pair_result_{i}) \ + + [f""" + result_{iknl}[itgt] = knl_{iknl}_scaling * \ + simul_reduce(sum, isrc, pair_result_{iknl}) \ {{id_prefix=write_lpot,inames=itgt}} - """.format(i=iknl) + """ for iknl in range(len(self.target_kernels))] + ["end"], - arguments, + kernel_data=arguments, name=self.name, assumptions="ntargets>=1 and nsources>=1", - default_offset=lp.auto, silenced_warnings="write_race(write_lpot*)", - fixed_parameters=dict(dim=self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + fixed_parameters={"dim": self.dim}, + ) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - for knl in self.target_kernels + self.source_kernels: + for knl in [*self.target_kernels, *self.source_kernels]: loopy_knl = knl.prepare_loopy_kernel(loopy_knl) return loopy_knl - def __call__(self, queue, targets, sources, centers, strengths, expansion_radii, + def __call__(self, actx: ArrayContext, + targets, sources, centers, strengths, expansion_radii, **kwargs): """ :arg strengths: are required to have area elements and quadrature weights already multiplied in. """ - knl = self.get_cached_optimized_kernel( + knl = self.get_cached_kernel( + is_cpu=is_cl_cpu(actx), targets_is_obj_array=is_obj_array_like(targets), sources_is_obj_array=is_obj_array_like(sources), centers_is_obj_array=is_obj_array_like(centers)) @@ -294,18 +324,27 @@ def __call__(self, queue, targets, sources, centers, strengths, expansion_radii, for i, dens in enumerate(strengths): kwargs[f"strength_{i}"] = dens - return knl(queue, sources=sources, targets=targets, center=centers, - expansion_radii=expansion_radii, **kwargs) + result = actx.call_loopy( + knl, + sources=sources, + targets=targets, + center=centers, + expansion_radii=expansion_radii, + **kwargs) + + return obj_array.new_1d([result[f"result_{i}"] for i in range(self.nresults)]) # }}} -# {{{ matrix writer +# {{{ LayerPotentialMatrixGenerator: matrix writer class LayerPotentialMatrixGenerator(LayerPotentialBase): """Generator for layer potential matrix entries.""" - default_name = "qbx_matrix" + @property + def default_name(self): + return "qbx_matrix" def get_strength_or_not(self, isrc, kernel_idx): return 1 @@ -322,7 +361,7 @@ def get_kernel(self): for i, dtype in enumerate(self.value_dtypes) ]) - loopy_knl = lp.make_kernel([""" + loopy_knl = make_loopy_program([""" {[itgt, isrc, idim]: \ 0 <= itgt < ntargets and \ 0 <= isrc < nsources and \ @@ -334,39 +373,47 @@ def get_kernel(self): + ["<> b[idim] = targets[idim, itgt] - center[idim, itgt] {dup=idim}"] + ["<> rscale = expansion_radii[itgt]"] + loopy_insns + kernel_exprs - + [""" - result_{i}[itgt, isrc] = \ - knl_{i}_scaling * pair_result_{i} \ + + [f""" + result_{iknl}[itgt, isrc] = \ + knl_{iknl}_scaling * pair_result_{iknl} \ {{inames=isrc:itgt}} - """.format(i=iknl) + """ for iknl in range(len(self.target_kernels))] + ["end"], - arguments, + kernel_data=arguments, name=self.name, assumptions="ntargets>=1 and nsources>=1", - default_offset=lp.auto, - fixed_parameters=dict(dim=self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + fixed_parameters={"dim": self.dim}, + ) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - for expn in self.source_kernels + self.target_kernels: + for expn in [*self.source_kernels, *self.target_kernels]: loopy_knl = expn.prepare_loopy_kernel(loopy_knl) return loopy_knl - def __call__(self, queue, targets, sources, centers, expansion_radii, **kwargs): - knl = self.get_cached_optimized_kernel( + def __call__(self, actx: ArrayContext, + targets, sources, centers, expansion_radii, **kwargs): + knl = self.get_cached_kernel( + is_cpu=is_cl_cpu(actx), targets_is_obj_array=is_obj_array_like(targets), sources_is_obj_array=is_obj_array_like(sources), centers_is_obj_array=is_obj_array_like(centers)) - return knl(queue, sources=sources, targets=targets, center=centers, - expansion_radii=expansion_radii, **kwargs) + result = actx.call_loopy( + knl, + sources=sources, + targets=targets, + center=centers, + expansion_radii=expansion_radii, + **kwargs) + + return obj_array.new_1d([result[f"result_{i}"] for i in range(self.nresults)]) # }}} -# {{{ matrix subset generator +# {{{ LayerPotentialMatrixSubsetGenerator: matrix subset generator class LayerPotentialMatrixSubsetGenerator(LayerPotentialBase): """Generator for a subset of the layer potential matrix entries. @@ -374,7 +421,9 @@ class LayerPotentialMatrixSubsetGenerator(LayerPotentialBase): .. automethod:: __call__ """ - default_name = "qbx_subset" + @property + def default_name(self): + return "qbx_subset" def get_strength_or_not(self, isrc, kernel_idx): return 1 @@ -395,7 +444,7 @@ def get_kernel(self): for i, dtype in enumerate(self.value_dtypes) ]) - loopy_knl = lp.make_kernel([ + loopy_knl = make_loopy_program([ "{[imat, idim]: 0 <= imat < nresult and 0 <= idim < dim}" ], self.get_kernel_scaling_assignments() @@ -412,29 +461,29 @@ def get_kernel(self): <> rscale = expansion_radii[itgt] """] + loopy_insns + kernel_exprs - + [""" - result_{i}[imat] = knl_{i}_scaling * pair_result_{i} \ + + [f""" + result_{iknl}[imat] = knl_{iknl}_scaling * pair_result_{iknl} \ {{id_prefix=write_lpot}} - """.format(i=iknl) + """ for iknl in range(len(self.target_kernels))] + ["end"], - arguments, + kernel_data=arguments, name=self.name, assumptions="nresult>=1", - default_offset=lp.auto, silenced_warnings="write_race(write_lpot*)", - fixed_parameters=dict(dim=self.dim), - lang_version=MOST_RECENT_LANGUAGE_VERSION) + fixed_parameters={"dim": self.dim}, + ) loopy_knl = lp.tag_inames(loopy_knl, "idim*:unr") - loopy_knl = lp.add_dtypes(loopy_knl, - dict(nsources=np.int32, ntargets=np.int32)) + loopy_knl = lp.add_dtypes( + loopy_knl, {"nsources": np.int32, "ntargets": np.int32}) - for knl in self.source_kernels + self.target_kernels: + for knl in [*self.source_kernels, *self.target_kernels]: loopy_knl = knl.prepare_loopy_kernel(loopy_knl) return loopy_knl + @override def get_optimized_kernel(self, targets_is_obj_array, sources_is_obj_array, centers_is_obj_array): loopy_knl = self.get_kernel() @@ -447,11 +496,11 @@ def get_optimized_kernel(self, loopy_knl = lp.tag_array_axes(loopy_knl, "center", "sep,C") loopy_knl = lp.split_iname(loopy_knl, "imat", 1024, outer_tag="g.0") - loopy_knl = self._allow_redundant_execution_of_knl_scaling(loopy_knl) - return loopy_knl + return self._allow_redundant_execution_of_knl_scaling(loopy_knl) - def __call__(self, queue, targets, sources, centers, expansion_radii, - tgtindices, srcindices, **kwargs): + def __call__(self, actx: ArrayContext, + targets, sources, centers, expansion_radii, + tgtindices, srcindices, **kwargs): """Evaluate a subset of the QBX matrix interactions. :arg targets: target point coordinates, which can be an object @@ -473,18 +522,21 @@ def __call__(self, queue, targets, sources, centers, expansion_radii, in (*srcindices*, *tgtindices*) """ - knl = self.get_cached_optimized_kernel( + knl = self.get_cached_kernel( targets_is_obj_array=is_obj_array_like(targets), sources_is_obj_array=is_obj_array_like(sources), centers_is_obj_array=is_obj_array_like(centers)) - return knl(queue, - sources=sources, - targets=targets, - center=centers, - expansion_radii=expansion_radii, - tgtindices=tgtindices, - srcindices=srcindices, **kwargs) + result = actx.call_loopy( + knl, + sources=sources, + targets=targets, + center=centers, + expansion_radii=expansion_radii, + tgtindices=tgtindices, + srcindices=srcindices, **kwargs) + + return obj_array.new_1d([result[f"result_{i}"] for i in range(self.nresults)]) # }}} @@ -495,11 +547,11 @@ def __call__(self, queue, targets, sources, centers, expansion_radii, def find_jump_term(kernel, arg_provider): from sumpy.kernel import ( - AxisSourceDerivative, - AxisTargetDerivative, - DirectionalSourceDerivative, - DirectionalTargetDerivative, - DerivativeBase) + AxisSourceDerivative, + AxisTargetDerivative, + DerivativeBase, + DirectionalSourceDerivative, + ) tgt_derivatives = [] src_derivatives = [] @@ -508,9 +560,6 @@ def find_jump_term(kernel, arg_provider): if isinstance(kernel, AxisTargetDerivative): tgt_derivatives.append(kernel.axis) kernel = kernel.kernel - elif isinstance(kernel, DirectionalTargetDerivative): - tgt_derivatives.append(kernel.dir_vec_name) - kernel = kernel.kernel elif isinstance(kernel, AxisSourceDerivative): src_derivatives.append(kernel.axis) kernel = kernel.kernel @@ -568,7 +617,7 @@ def find_jump_term(kernel, arg_provider): elif tgt_count == 1: from warnings import warn warn("jump terms for mixed derivatives (1 src+1 tgt) only available " - "for the double-layer potential") + "for the double-layer potential", stacklevel=1) i, = tgt_derivatives assert isinstance(i, int) return ( @@ -589,6 +638,7 @@ class _JumpTermSymbolicArgumentProvider: data was requested. This tracking allows assembling the argument list of the resulting computational kernel. """ + dim: int def __init__(self, data_args, dim, density_var_name, density_dtype, geometry_dtype): @@ -602,26 +652,29 @@ def __init__(self, data_args, dim, density_var_name, @property @memoize_method def density(self): + import pymbolic as prim self.arguments[self.density_var_name] = \ lp.GlobalArg(self.density_var_name, self.density_dtype, shape="ntargets", order="C") - return parse(f"{self.density_var_name}[itgt]") + return prim.parse(f"{self.density_var_name}[itgt]") @property @memoize_method def density_prime(self): + import pymbolic as prim prime_var_name = f"{self.density_var_name}_prime" self.arguments[prime_var_name] = ( lp.GlobalArg(prime_var_name, self.density_dtype, shape="ntargets", order="C")) - return parse(f"{prime_var_name}[itgt]") + return prim.parse(f"{prime_var_name}[itgt]") @property @memoize_method def side(self): + import pymbolic as prim self.arguments["side"] = ( lp.GlobalArg("side", self.geometry_dtype, shape="ntargets")) - return parse("side[itgt]") + return prim.parse("side[itgt]") @property @memoize_method @@ -629,8 +682,7 @@ def normal(self): self.arguments["normal"] = ( lp.GlobalArg("normal", self.geometry_dtype, shape=("ntargets", self.dim), order="C")) - from pytools.obj_array import make_obj_array - return make_obj_array([ + return obj_array.new_1d([ parse(f"normal[itgt, {i}]") for i in range(self.dim)]) @@ -640,19 +692,19 @@ def tangent(self): self.arguments["tangent"] = ( lp.GlobalArg("tangent", self.geometry_dtype, shape=("ntargets", self.dim), order="C")) - from pytools.obj_array import make_obj_array - return make_obj_array([ + return obj_array.new_1d([ parse(f"tangent[itgt, {i}]") for i in range(self.dim)]) @property @memoize_method def mean_curvature(self): + import pymbolic as prim self.arguments["mean_curvature"] = ( lp.GlobalArg("mean_curvature", self.geometry_dtype, shape="ntargets", order="C")) - return parse("mean_curvature[itgt]") + return prim.parse("mean_curvature[itgt]") @property @memoize_method @@ -661,8 +713,7 @@ def src_derivative_dir(self): lp.GlobalArg("src_derivative_dir", self.geometry_dtype, shape=("ntargets", self.dim), order="C")) - from pytools.obj_array import make_obj_array - return make_obj_array([ + return obj_array.new_1d([ parse(f"src_derivative_dir[itgt, {i}]") for i in range(self.dim)]) @@ -673,8 +724,7 @@ def tgt_derivative_dir(self): lp.GlobalArg("tgt_derivative_dir", self.geometry_dtype, shape=("ntargets", self.dim), order="C")) - from pytools.obj_array import make_obj_array - return make_obj_array([ + return obj_array.new_1d([ parse(f"tgt_derivative_dir[itgt, {i}]") for i in range(self.dim)]) diff --git a/sumpy/symbolic.py b/sumpy/symbolic.py index 53f73e3c0..06b44723d 100644 --- a/sumpy/symbolic.py +++ b/sumpy/symbolic.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2012 Andreas Kloeckner" __license__ = """ @@ -20,14 +23,39 @@ THE SOFTWARE. """ +__doc__ = """ + + Symbolic Tools + ============== + + .. class:: Basic + + The expression base class for the "heavy-duty" computer algebra toolkit + in use. Either :class:`sympy.core.basic.Basic` or :class:`symengine.Basic`. + + .. autoclass:: SpatialConstant +""" -import numpy as np -from pymbolic.mapper import IdentityMapper as IdentityMapperBase -import pymbolic.primitives as prim import logging +import math +from typing import TYPE_CHECKING, ClassVar, cast + +from typing_extensions import override + +import pymbolic.primitives as prim +from pymbolic.mapper import IdentityMapper as IdentityMapperBase + + +if TYPE_CHECKING: + from pymbolic.typing import ArithmeticExpression, Expression + from pytools import T + from pytools.obj_array import ObjectArray1D + logger = logging.getLogger(__name__) +USE_SYMENGINE = False + # {{{ symbolic backend @@ -35,51 +63,58 @@ def _find_symbolic_backend(): global USE_SYMENGINE try: - import symengine # noqa + import symengine # ruff:ignore[unused-import] symengine_found = True + symengine_error = None except ImportError as import_error: symengine_found = False symengine_error = import_error - ALLOWED_BACKENDS = ("sympy", "symengine") # noqa - BACKEND_ENV_VAR = "SUMPY_FORCE_SYMBOLIC_BACKEND" # noqa + allowed_backends = ("sympy", "symengine") + backend_env_var = "SUMPY_FORCE_SYMBOLIC_BACKEND" import os - backend = os.environ.get(BACKEND_ENV_VAR) + backend = os.environ.get(backend_env_var) if backend is not None: - if backend not in ALLOWED_BACKENDS: + if backend not in allowed_backends: raise RuntimeError( - f"{BACKEND_ENV_VAR} value is unrecognized: '{backend}' " + f"{backend_env_var} value is unrecognized: '{backend}' " "(allowed values are {})".format( - ", ".join(f"'{val}'" for val in ALLOWED_BACKENDS)) + ", ".join(f"'{val}'" for val in allowed_backends)) ) if backend == "symengine" and not symengine_found: raise RuntimeError(f"could not find SymEngine: {symengine_error}") - USE_SYMENGINE = (backend == "symengine") + USE_SYMENGINE = (backend == "symengine") # pyright: ignore[reportConstantRedefinition] else: - USE_SYMENGINE = symengine_found + USE_SYMENGINE = symengine_found # pyright: ignore[reportConstantRedefinition] _find_symbolic_backend() # }}} -if USE_SYMENGINE: - import symengine as sym - from pymbolic.interop.symengine import ( - PymbolicToSymEngineMapper as PymbolicToSympyMapper, - SymEngineToPymbolicMapper as SympyToPymbolicMapper) -else: +if TYPE_CHECKING or not USE_SYMENGINE: import sympy as sym + from pymbolic.interop.sympy import ( - PymbolicToSympyMapper, SympyToPymbolicMapper) + PymbolicToSympyMapper as PymbolicToSympyMapperBase, + SympyToPymbolicMapper as SympyToPymbolicMapperBase, + ) +else: + import symengine as sym + + from pymbolic.interop.symengine import ( + PymbolicToSymEngineMapper as PymbolicToSympyMapperBase, + SymEngineToPymbolicMapper as SympyToPymbolicMapperBase, + ) # Symbolic API common to SymEngine and sympy. # Before adding a function here, make sure it's present in both modules. Add = sym.Add Basic = sym.Basic +Expr = sym.Expr Mul = sym.Mul Pow = sym.Pow exp = sym.exp @@ -94,71 +129,80 @@ def _find_symbolic_backend(): Symbol = sym.Symbol Derivative = sym.Derivative Integer = sym.Integer +Rational = sym.Rational Matrix = sym.Matrix Subs = sym.Subs -I = sym.I # noqa: E741 -pi = sym.pi +I = cast("Expr", sym.I) # ruff:ignore[ambiguous-variable-name] +pi = cast("Expr", sym.pi) functions = sym.functions Number = sym.Number Float = sym.Float -def _coeff_isneg(a): +def _coeff_isneg(a: Basic) -> bool: if a.is_Mul: a = a.args[0] - return a.is_Number and a.is_negative + + return a.is_Number and bool(a.is_negative) -have_unevaluated_expr = False -if not USE_SYMENGINE: +if TYPE_CHECKING or USE_SYMENGINE: + def UnevaluatedExpr(x: T) -> T: # ruff:ignore[invalid-function-name] + return x +else: try: from sympy import UnevaluatedExpr - have_unevaluated_expr = True except ImportError: - pass - -if not have_unevaluated_expr: - def UnevaluatedExpr(x): # noqa - return x + def UnevaluatedExpr(x): # ruff:ignore[invalid-function-name] + return x if USE_SYMENGINE: - def unevaluated_pow(a, b): - return sym.Pow(a, b) + def doit(expr: Expr) -> Expr: + return expr + + def unevaluated_pow(a: Expr, b: complex | Expr) -> Expr: + return Pow(a, b) else: - def unevaluated_pow(a, b): - return sym.Pow(a, b, evaluate=False) + def doit(expr: Expr) -> Expr: + return expr.doit() + + def unevaluated_pow(a: Expr, b: complex | Expr) -> Expr: + return Pow(a, b, evaluate=False) # {{{ debugging of sympy CSE via Maxima -class _DerivativeKiller(IdentityMapperBase): - def map_derivative(self, expr): - from pymbolic import var - return var("d_{}".format("_".join(expr.variables)))(expr.child) +class _DerivativeKiller(IdentityMapperBase[[]]): + @override + def map_derivative(self, expr: prim.Derivative) -> Expression: + return prim.Variable("d_{}".format("_".join(expr.variables)))(expr.child) - def map_substitution(self, expr): + @override + def map_substitution(self, expr: prim.Substitution) -> Expression: return self.rec(expr.child) -def _get_assignments_in_maxima(assignments, prefix=""): - my_variable_names = set(assignments.keys()) - written_assignments = set() +def _get_assignments_in_maxima( + assignments: dict[str, Basic], + prefix: str = "", + ) -> str: + variable_names = set(assignments.keys()) + written_assignments: set[str] = set() + prefix_subst_dict = {vn: f"{prefix}{vn}" for vn in variable_names} - prefix_subst_dict = { - vn: prefix+vn for vn in my_variable_names} + from pymbolic.interop.maxima import MaximaStringifyMapper - from pymbolic.maxima import MaximaStringifyMapper mstr = MaximaStringifyMapper() s2p = SympyToPymbolicMapper() dkill = _DerivativeKiller() - result = [] + result: list[str] = [] - def write_assignment(name): + def write_assignment(name: str) -> None: symbols = [atm for atm in assignments[name].atoms() - if isinstance(atm, sym.Symbol) - and atm.name in my_variable_names] + if isinstance(atm, Symbol) + and atm.name in variable_names] for symb in symbols: if symb.name not in written_assignments: @@ -169,7 +213,7 @@ def write_assignment(name): assignments[name].subs(prefix_subst_dict)))))) written_assignments.add(name) - for name in assignments.keys(): + for name in assignments: if name not in written_assignments: write_assignment(name) @@ -186,12 +230,12 @@ def checked_cse(exprs, symbols=None): max_old = _get_assignments_in_maxima({ f"old_expr{i}": expr for i, expr in enumerate(exprs)}) - new_ass_dict = { + new_assign_dict = { f"new_expr{i}": expr for i, expr in enumerate(new_exprs)} for name, val in new_assignments: - new_ass_dict[name.name] = val - max_new = _get_assignments_in_maxima(new_ass_dict) + new_assign_dict[name.name] = val + max_new = _get_assignments_in_maxima(new_assign_dict) with open("check.mac", "w") as outf: outf.write("ratprint:false;\n") @@ -209,82 +253,135 @@ def checked_cse(exprs, symbols=None): # }}} -def sym_real_norm_2(x): - return sym.sqrt((x.T*x)[0, 0]) +def sym_real_norm_2(x: Matrix) -> Expr: + return sqrt((x.T*x)[0, 0]) + + +def pymbolic_real_norm_2( + x: ObjectArray1D[ArithmeticExpression]) -> ArithmeticExpression: + return prim.Variable("sqrt")(x @ x) + + +def make_sym_vector(name: str, components: int) -> Matrix: + return Matrix([Symbol(f"{name}{i}") for i in range(components)]) -def pymbolic_real_norm_2(x): - from pymbolic import var - return var("sqrt")(np.dot(x, x)) +@prim.expr_dataclass() +class SpatialConstant(prim.Variable): + """A symbolic constant to represent a symbolic variable that is spatially constant. + For example the wave-number :math:`k` in the setting of a constant-coefficient + Helmholtz problem. For use in :attr:`sumpy.kernel.ExpressionKernel.expression`. + Any variable occurring there that is not a :class:`~sumpy.symbolic.SpatialConstant` + is assumed to have a spatial dependency. + + .. autoattribute:: prefix + .. automethod:: as_sympy + .. automethod:: from_sympy + """ + + prefix: ClassVar[str] = "_spatial_constant_" + """Prefix used in code generation for variables of this type.""" + + def as_sympy(self) -> Symbol: + """Convert this variable to a :mod:`sympy` expression.""" + return Symbol(f"{self.prefix}{self.name}") + + @classmethod + def from_sympy(cls, expr: Symbol) -> SpatialConstant: + """Convert a :mod:`sympy` expression to a constant.""" + if isinstance(expr, Symbol) and expr.name.startswith(cls.prefix): + return cls(expr.name[len(cls.prefix):]) + + raise ValueError(f"expression is not a spatial constant: {expr!r}") -def make_sym_vector(name, components): - return sym.Matrix([sym.Symbol(f"{name}{i}") for i in range(components)]) +class PymbolicToSympyMapper(PymbolicToSympyMapperBase): + def map_spatial_constant(self, expr: SpatialConstant) -> Basic: + return expr.as_sympy() -def vector_xreplace(expr, from_vec, to_vec): - substs = {} - assert (from_vec.rows, from_vec.cols) == (to_vec.rows, to_vec.cols) - for irow in range(from_vec.rows): - for icol in range(from_vec.cols): - substs[from_vec[irow, icol]] = to_vec[irow, icol] - return expr.xreplace(substs) +class SympyToPymbolicMapper(SympyToPymbolicMapperBase): + @override + def map_Symbol(self, expr: Symbol) -> Expression: + try: + return SpatialConstant.from_sympy(expr) + except ValueError: + return SympyToPymbolicMapperBase.map_Symbol(self, expr) + @override + def map_Pow(self, expr: Pow) -> Expression: + if expr.exp == -1: + return 1 / self.rec_arith(expr.base) + else: + return SympyToPymbolicMapperBase.map_Pow(self, expr) + + @override + def map_Mul(self, expr: Mul) -> Expression: + num_args: list[ArithmeticExpression] = [] + den_args: list[ArithmeticExpression] = [] + for child in expr.args: + if (isinstance(child, Pow) + and isinstance(child.exp, Integer) + and child.exp < 0): + den_args.append(self.rec_arith(child.base)**(-self.rec_arith(child.exp))) + else: + num_args.append(self.rec_arith(child)) -def find_power_of(base, prod): - remdr = sym.Wild("remdr") - power = sym.Wild("power") - result = prod.match(remdr*base**power) - if result is None: - return 0 - return result[power] + return math.prod(num_args) / math.prod(den_args) class PymbolicToSympyMapperWithSymbols(PymbolicToSympyMapper): - def map_variable(self, expr): + @override + def map_variable(self, expr: prim.Variable) -> Basic: if expr.name == "I": - return sym.I + return I elif expr.name == "pi": - return sym.pi + return pi else: return PymbolicToSympyMapper.map_variable(self, expr) - def map_subscript(self, expr): + @override + def map_subscript(self, expr: prim.Subscript) -> sym.Basic: if isinstance(expr.aggregate, prim.Variable) and isinstance(expr.index, int): - return sym.Symbol(f"{expr.aggregate.name}{expr.index}") + return Symbol(f"{expr.aggregate.name}{expr.index}") else: self.raise_conversion_error(expr) - def map_call(self, expr): - if expr.function.name == "hankel_1": - args = [self.rec(param) for param in expr.parameters] - args.append(0) - return Hankel1(*args) - elif expr.function.name == "bessel_j": - args = [self.rec(param) for param in expr.parameters] - args.append(0) - return BesselJ(*args) - else: - return PymbolicToSympyMapper.map_call(self, expr) + @override + def map_call(self, expr: prim.Call) -> sym.Basic: + function = expr.function + if isinstance(function, prim.Variable): + if function.name == "hankel_1": + args = [self.rec(param) for param in expr.parameters] + args.append(sympify(0)) + return Hankel1(*args) + elif function.name == "bessel_j": + args = [self.rec(param) for param in expr.parameters] + args.append(sympify(0)) + return BesselJ(*args) + return PymbolicToSympyMapper.map_call(self, expr) -import sympy +from sympy import Function as SympyFunction -class _BesselOrHankel(sympy.Function): + +class _BesselOrHankel(SympyFunction): """A symbolic function for BesselJ or Hankel1 functions that keeps track of the derivatives taken of the function. Arguments are ``(order, z, nderivs)``. """ - nargs = (3,) + nargs: ClassVar[tuple[int, ...]] = (3,) - def fdiff(self, argindex=1): + @override + def fdiff(self, argindex: int = 1) -> Basic: if argindex in (1, 3): # we are not differentiating w.r.t order or nderivs - raise ValueError() + raise ValueError(f"invalid argindex: {argindex}") + order, z, nderivs = self.args - return self.func(order, z, nderivs+1) + return self.func(order, z, nderivs + 1) class BesselJ(_BesselOrHankel): @@ -298,11 +395,11 @@ class Hankel1(_BesselOrHankel): _SympyBesselJ = BesselJ _SympyHankel1 = Hankel1 -if USE_SYMENGINE: - def BesselJ(*args): # noqa: N802 - return sym.sympify(_SympyBesselJ(*args)) +if not TYPE_CHECKING and USE_SYMENGINE: + def BesselJ(*args): # ruff:ignore[invalid-function-name] + return sympify(_SympyBesselJ(*args)) - def Hankel1(*args): # noqa: N802 - return sym.sympify(_SympyHankel1(*args)) + def Hankel1(*args): # ruff:ignore[invalid-function-name] + return sympify(_SympyHankel1(*args)) # vim: fdm=marker diff --git a/sumpy/test/__init__.py b/sumpy/test/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sumpy/test/coeff_test_tools.py b/sumpy/test/coeff_test_tools.py new file mode 100644 index 000000000..fa1d49af4 --- /dev/null +++ b/sumpy/test/coeff_test_tools.py @@ -0,0 +1,68 @@ +from __future__ import annotations + + +__copyright__ = """ +Copyright (C) 2026 Shawn/Chaoqi Lin +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import numpy as np +import sympy as sp + + +def to_scalar(val): + """Convert symbolic or array value to scalar.""" + if hasattr(val, "evalf"): + val = val.evalf() + if hasattr(val, "item"): + val = val.item() + return complex(val) + + +class NumericMatVecOperator: + """Wrapper for symbolic matrix-vector operator with numeric + substitution.""" + + def __init__(self, symbolic_op, repl_dict): + self.symbolic_op = symbolic_op + self.repl_dict = repl_dict + self.shape = symbolic_op.shape + + def matvec(self, vec): + result = self.symbolic_op.matvec(vec) + out = [] + for expr in result: + if hasattr(expr, "xreplace"): + out.append(complex(expr.xreplace(self.repl_dict).evalf())) + else: + out.append(complex(expr)) + return np.array(out) + + +def get_repl_dict(kernel, extra_kwargs): + """Numeric substitution for symbolic kernel parameters.""" + repl_dict = {} + if "lam" in extra_kwargs: + repl_dict[sp.Symbol("lam")] = extra_kwargs["lam"] + if "k" in extra_kwargs: + repl_dict[sp.Symbol("k")] = extra_kwargs["k"] + return repl_dict diff --git a/sumpy/test/curve.py b/sumpy/test/curve.py new file mode 100644 index 000000000..ce2536cea --- /dev/null +++ b/sumpy/test/curve.py @@ -0,0 +1,67 @@ +from __future__ import annotations + + +__copyright__ = "Copyright (C) 2012 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +from typing import TYPE_CHECKING, Any, final + +import numpy as np + + +if TYPE_CHECKING: + import optype.numpy as onp + + +def fftdiff(x: onp.Array1D[np.floating[Any]], *, + period: float = 1.0) -> onp.Array1D[np.floating[Any]]: + n = len(x) + return np.fft.ifft( + 2j * np.pi * np.fft.fftfreq(n, d=period/n) * np.fft.fft(x) + ).real + + +@final +class CurveGrid: + pos: onp.Array2D[np.floating[Any]] + mean_curvature: onp.Array1D[np.floating[Any]] + normal: onp.Array2D[np.floating[Any]] + + def __init__(self, + x: onp.Array1D[np.floating[Any]], + y: onp.Array1D[np.floating[Any]]) -> None: + self.pos = np.vstack([x, y]).copy() + xp = self.xp = fftdiff(x, period=1) + yp = self.yp = fftdiff(y, period=1) + xpp = self.xpp = fftdiff(xp, period=1) + ypp = self.ypp = fftdiff(yp, period=1) + self.mean_curvature = (xp*ypp-yp*xpp)/((xp**2+yp**2)**(3/2)) + + speed = self.speed = np.sqrt(xp**2+yp**2) + self.normal = (np.vstack([yp, -xp])/speed).copy() + + def __len__(self) -> int: + return len(self.pos) + + def plot(self) -> None: + import matplotlib.pyplot as pt + pt.plot(self.pos[:, 0], self.pos[:, 1]) diff --git a/sumpy/test/geometries.py b/sumpy/test/geometries.py new file mode 100644 index 000000000..a4b4b004e --- /dev/null +++ b/sumpy/test/geometries.py @@ -0,0 +1,142 @@ +from __future__ import annotations + + +__copyright__ = "Copyright (C) 2025 University of Illinois Board of Trustees" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +import numpy as np +import numpy.linalg as la + + +if TYPE_CHECKING: + import optype.numpy as onp + + +@dataclass(frozen=True) +class Geometry: + nodes: onp.ArrayND[np.floating[Any]] + normals: onp.ArrayND[np.floating[Any]] + weights: onp.Array1D[np.floating[Any]] + area_elements: onp.Array1D[np.floating[Any]] + + +def _geometry_from_curve_nodes( + x: onp.Array1D[np.floating[Any]], + y: onp.Array1D[np.floating[Any]] + ): + npts: int + npts, = x.shape + weights = np.ones(npts, dtype=np.float64)/npts + + from sumpy.test.curve import CurveGrid + curve = CurveGrid(x, y) + + return Geometry( + nodes=curve.pos, + normals=curve.normal, + weights=weights, + area_elements=curve.speed, + ) + + +def make_ellipsoid(a: float = 1, b: float = 0.5, npoints: int = 100): + t = np.linspace(0, 1, npoints, endpoint=False) + x = a*np.cos(2*np.pi*t) + y = b*np.sin(2*np.pi*t) + + return _geometry_from_curve_nodes(x, y) + + +def make_starfish(n_arms: int = 5, amplitude: float = 0.25, npoints: int = 100): + t = np.linspace(0, 1, npoints, endpoint=False) + theta = 2 * np.pi * t + + r = 1 + amplitude * np.sin(n_arms * theta) + + x = r * np.cos(theta) + y = r * np.sin(theta) + return _geometry_from_curve_nodes(x, y) + + +def make_torus( + r_major: float = 1, + r_minor: float = 0.5, + n_major: int = 200, + n_minor: int = 200 + ): + u = np.linspace(0.0, 2.0 * np.pi, n_major, endpoint=False) + v = np.linspace(0.0, 2.0 * np.pi, n_minor, endpoint=False) + u, v = np.meshgrid(u, v, copy=False) + nodes = np.stack([ + np.cos(u) * (r_major + r_minor * np.cos(v)), + np.sin(u) * (r_major + r_minor * np.cos(v)), + r_minor * np.sin(v) + ]) + + def fftdiff(x: onp.Array1D[np.floating[Any]], *, + period: float = 2.0 * np.pi) -> onp.Array1D[np.floating[Any]]: + n = len(x) + return np.fft.ifft( + 2j * np.pi * np.fft.fftfreq(n, d=period/n) * np.fft.fft(x) + ).real + + def diff2d(ary: onp.Array2D[np.floating[Any]]) -> onp.Array2D[np.floating[Any]]: + return np.array([fftdiff(ary[idx]) for idx in np.ndindex(ary.shape[:-1])]) + + dnodes_du = np.array( + [diff2d(nodes[i].T).T for i in range(3)]) + dnodes_dv = np.array( + [diff2d(nodes[i]) for i in range(3)]) + + normal = -np.cross(dnodes_du, dnodes_dv, axis=0) + area_el = la.norm(normal, axis=0) + normal /= area_el + + weights = np.zeros_like(area_el) + (2*np.pi)**2/n_major/n_minor + + if 0: + import matplotlib.pyplot as plt + plt.figure().add_subplot(projection="3d") + plt.gca().quiver( + nodes[0], nodes[1], nodes[2], + normal[0], normal[1], normal[2], + length=0.1 + ) + plt.show() + 1/0 # ruff:ignore[useless-expression] + + geo = Geometry( + nodes=nodes.reshape(3, -1).copy(), + normals=normal.reshape(3, -1).copy(), + area_elements=area_el.reshape(-1).copy(), + weights=weights.reshape(-1).copy(), + ) + + surface_area_ref = 4*np.pi**2*r_major*r_minor + surface_area = geo.area_elements @ geo.weights + surface_area_err = abs(surface_area - surface_area_ref)/surface_area_ref + assert surface_area_err < 1e-14 + + return geo diff --git a/test/test_codegen.py b/sumpy/test/test_codegen.py similarity index 89% rename from test/test_codegen.py rename to sumpy/test/test_codegen.py index 815b92dd9..4eb45b094 100644 --- a/test/test_codegen.py +++ b/sumpy/test/test_codegen.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2017 Matt Wala" __license__ = """ @@ -20,13 +23,17 @@ THE SOFTWARE. """ - +import logging import sys -import logging +import pytest + + logger = logging.getLogger(__name__) +# {{{ test_symbolic_assignment_name_uniqueness + def test_symbolic_assignment_name_uniqueness(): # https://gitlab.tiker.net/inducer/sumpy/issues/13 from sumpy.assignment_collection import SymbolicAssignmentCollection @@ -43,16 +50,20 @@ def test_symbolic_assignment_name_uniqueness(): assert len(sac.assignments) == 3 +# }}} + + +# {{{ test_line_taylor_coeff_growth def test_line_taylor_coeff_growth(): # Regression test for LineTaylorLocalExpansion. # See https://gitlab.tiker.net/inducer/pytential/merge_requests/12 - from sumpy.kernel import LaplaceKernel - from sumpy.expansion.local import LineTaylorLocalExpansion - from sumpy.symbolic import make_sym_vector, SympyToPymbolicMapper - import numpy as np + from sumpy.expansion.local import LineTaylorLocalExpansion + from sumpy.kernel import LaplaceKernel + from sumpy.symbolic import SympyToPymbolicMapper, make_sym_vector + order = 10 expn = LineTaylorLocalExpansion(LaplaceKernel(2), order) avec = make_sym_vector("a", 2) @@ -70,15 +81,16 @@ def test_line_taylor_coeff_growth(): max_order = 2 assert np.polyfit(np.log(indices), np.log(counts), deg=1)[0] < max_order +# }}} + # You can test individual routines by typing -# $ python test_fmm.py "test_sumpy_fmm(cl.create_some_context)" +# $ python test_codegen.py 'test_line_taylor_coeff_growth()' if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) else: - from pytest import main - main([__file__]) + pytest.main([__file__]) # vim: fdm=marker diff --git a/test/test_cse.py b/sumpy/test/test_cse.py similarity index 70% rename from test/test_cse.py rename to sumpy/test/test_cse.py index ba9986bed..ad531cdf8 100644 --- a/test/test_cse.py +++ b/sumpy/test/test_cse.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = """ Copyright (C) 2017 Matt Wala Copyright (C) 2006-2016 SymPy Development Team @@ -64,29 +67,30 @@ # }}} -import pytest import sys +from typing import TYPE_CHECKING + +import pytest -from sumpy.symbolic import ( - Add, Pow, exp, sqrt, symbols, sympify, cos, sin, Function, USE_SYMENGINE) +import sumpy.symbolic as sym +from sumpy.cse import cse, postprocess_for_cse, preprocess_for_cse -if not USE_SYMENGINE: - from sympy.simplify.cse_opts import sub_pre, sub_post + +if TYPE_CHECKING or not sym.USE_SYMENGINE: from sympy.functions.special.hyper import meijerg from sympy.simplify import cse_opts + from sympy.simplify.cse_opts import sub_post, sub_pre -from sumpy.cse import ( - cse, preprocess_for_cse, postprocess_for_cse) - +import logging -w, x, y, z = symbols("w,x,y,z") -x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12 = symbols("x:13") -sympyonly = ( - pytest.mark.skipif(USE_SYMENGINE, reason="uses a sympy-only feature")) +logger = logging.getLogger(__name__) +w, x, y, z = sym.symbols("w,x,y,z") +x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12 = sym.symbols("x:13") -# Dummy "optimization" functions for testing. +sympyonly = ( + pytest.mark.skipif(sym.USE_SYMENGINE, reason="uses a sympy-only feature")) def opt1(expr): @@ -97,6 +101,8 @@ def opt2(expr): return expr*z +# {{{ test_preprocess_for_cse + def test_preprocess_for_cse(): assert preprocess_for_cse(x, [(opt1, None)]) == x + y assert preprocess_for_cse(x, [(None, opt1)]) == x @@ -105,6 +111,10 @@ def test_preprocess_for_cse(): assert preprocess_for_cse( x, [(opt1, None), (opt2, None)]) == (x + y)*z +# }}} + + +# {{{ test_postprocess_for_cse def test_postprocess_for_cse(): assert postprocess_for_cse(x, [(opt1, None)]) == x @@ -115,56 +125,78 @@ def test_postprocess_for_cse(): assert postprocess_for_cse( x, [(None, opt1), (None, opt2)]) == x*z + y +# }}} + + +# {{{ test_cse_single def test_cse_single(): # Simple substitution. - e = Add(Pow(x + y, 2), sqrt(x + y)) + e = sym.Add(sym.Pow(x + y, 2), sym.sqrt(x + y)) substs, reduced = cse([e]) assert substs == [(x0, x + y)] - assert reduced == [sqrt(x0) + x0**2] + assert reduced == [sym.sqrt(x0) + x0**2] + +# }}} +# {{{ + @sympyonly def test_cse_not_possible(): # No substitution possible. - e = Add(x, y) + e = sym.Add(x, y) substs, reduced = cse([e]) assert substs == [] assert reduced == [x + y] # issue 6329 - eq = (meijerg((1, 2), (y, 4), (5,), [], x) - + meijerg((1, 3), (y, 4), (5,), [], x)) + eq = ( + meijerg((1, 2), (y, 4), (5,), [], x) + + meijerg((1, 3), (y, 4), (5,), [], x)) assert cse(eq) == ([], [eq]) +# }}} + + +# {{{ test_nested_substitution def test_nested_substitution(): # Substitution within a substitution. - e = Add(Pow(w*x + y, 2), sqrt(w*x + y)) + e = sym.Add(sym.Pow(w*x + y, 2), sym.sqrt(w*x + y)) substs, reduced = cse([e]) assert substs == [(x0, w*x + y)] - assert reduced == [sqrt(x0) + x0**2] + assert reduced == [sym.sqrt(x0) + x0**2] +# }}} + + +# {{{ test_subtraction_opt @sympyonly def test_subtraction_opt(): # Make sure subtraction is optimized. - e = (x - y)*(z - y) + exp((x - y)*(z - y)) + e = (x - y)*(z - y) + sym.exp((x - y)*(z - y)) substs, reduced = cse( [e], optimizations=[(cse_opts.sub_pre, cse_opts.sub_post)]) assert substs == [(x0, (x - y)*(y - z))] - assert reduced == [-x0 + exp(-x0)] - e = -(x - y)*(z - y) + exp(-(x - y)*(z - y)) + assert reduced == [-x0 + sym.exp(-x0)] + e = -(x - y)*(z - y) + sym.exp(-(x - y)*(z - y)) substs, reduced = cse( [e], optimizations=[(cse_opts.sub_pre, cse_opts.sub_post)]) assert substs == [(x0, (x - y)*(y - z))] - assert reduced == [x0 + exp(x0)] + assert reduced == [x0 + sym.exp(x0)] # issue 4077 n = -1 + 1/x e = n/x/(-n)**2 - 1/n/x - assert cse(e, optimizations=[(cse_opts.sub_pre, cse_opts.sub_post)]) == \ - ([], [0]) + assert cse(e, optimizations=[ + (cse_opts.sub_pre, cse_opts.sub_post)] + ) == ([], [0]) + +# }}} +# {{{ test_multiple_expressions + def test_multiple_expressions(): e1 = (x + y)*z e2 = (x + y)*w @@ -181,7 +213,7 @@ def test_multiple_expressions(): rsubsts, _ = cse(reversed(l_)) assert substs == rsubsts assert reduced == [x1, x1 + z, x0] - f = Function("f") + f = sym.Function("f") l_ = [f(x - z, y - z), x - z, y - z] substs, reduced = cse(l_) rsubsts, _ = cse(reversed(l_)) @@ -195,31 +227,46 @@ def test_multiple_expressions(): assert cse([x*y, z + x*y, x*y*z + 3]) == \ ([(x0, x*y)], [x0, z + x0, 3 + x0*z]) +# }}} + + +# {{{ test_issue_4203 def test_issue_4203(): - assert cse(sin(x**x)/x**x) == ([(x0, x**x)], [sin(x0)/x0]) + assert cse(sym.sin(x**x)/x**x) == ([(x0, x**x)], [sym.sin(x0)/x0]) +# }}} + + +# {{{ test_dont_cse_subs def test_dont_cse_subs(): - f = Function("f") - g = Function("g") + f = sym.Function("f") + g = sym.Function("g") - name_val, (expr,) = cse(f(x+y).diff(x) + g(x+y).diff(x)) + name_val, (_expr,) = cse(f(x+y).diff(x) + g(x+y).diff(x)) assert name_val == [] +# }}} + + +# {{{ test_dont_cse_derivative def test_dont_cse_derivative(): - from sumpy.symbolic import Derivative - f = Function("f") + f = sym.Function("f") - deriv = Derivative(f(x+y), x) + deriv = sym.Derivative(f(x+y), x) name_val, (expr,) = cse(x + y + deriv) assert name_val == [] assert expr == x + y + deriv +# }}} + + +# {{{ test_pow_invpow def test_pow_invpow(): assert cse(1/x**2 + x**2) == \ @@ -228,39 +275,64 @@ def test_pow_invpow(): ([(x0, x**2), (x1, 1/x0)], [x0 + x1*(x1 + 1)]) assert cse(1/x**2 + (1 + 1/x**2)*x**2) == \ ([(x0, x**2), (x1, 1/x0)], [x0*(x1 + 1) + x1]) - assert cse(cos(1/x**2) + sin(1/x**2)) == \ - ([(x0, x**(-2))], [sin(x0) + cos(x0)]) - assert cse(cos(x**2) + sin(x**2)) == \ - ([(x0, x**2)], [sin(x0) + cos(x0)]) + assert cse(sym.cos(1/x**2) + sym.sin(1/x**2)) == \ + ([(x0, x**(-2))], [sym.sin(x0) + sym.cos(x0)]) + assert cse(sym.cos(x**2) + sym.sin(x**2)) == \ + ([(x0, x**2)], [sym.sin(x0) + sym.cos(x0)]) assert cse(y/(2 + x**2) + z/x**2/y) == \ ([(x0, x**2)], [y/(x0 + 2) + z/(x0*y)]) - assert cse(exp(x**2) + x**2*cos(1/x**2)) == \ - ([(x0, x**2)], [x0*cos(1/x0) + exp(x0)]) + assert cse(sym.exp(x**2) + x**2*sym.cos(1/x**2)) == \ + ([(x0, x**2)], [x0*sym.cos(1/x0) + sym.exp(x0)]) assert cse((1 + 1/x**2)/x**2) == \ ([(x0, x**(-2))], [x0*(x0 + 1)]) assert cse(x**(2*y) + x**(-2*y)) == \ ([(x0, x**(2*y))], [x0 + 1/x0]) +# }}} + + +# {{{ test_issue_4499 @sympyonly def test_issue_4499(): # previously, this gave 16 constants + from sympy import S, Tuple from sympy.abc import a, b - from sympy import Tuple, S - B = Function("B") # noqa - G = Function("G") # noqa - t = Tuple( - *(a, a + S(1)/2, 2*a, b, 2*a - b + 1, (sqrt(z)/2)**(-2*a + 1)*B(2*a - - b, sqrt(z))*B(b - 1, sqrt(z))*G(b)*G(2*a - b + 1), # noqa - sqrt(z)*(sqrt(z)/2)**(-2*a + 1)*B(b, sqrt(z))*B(2*a - b, # noqa - sqrt(z))*G(b)*G(2*a - b + 1), sqrt(z)*(sqrt(z)/2)**(-2*a + 1)*B(b - 1, - sqrt(z))*B(2*a - b + 1, sqrt(z))*G(b)*G(2*a - b + 1), - (sqrt(z)/2)**(-2*a + 1)*B(b, sqrt(z))*B(2*a - b + 1, # noqa - sqrt(z))*G(b)*G(2*a - b + 1), 1, 0, S(1)/2, z/2, -b + 1, -2*a + b, # noqa - -2*a)) # noqa + + B = sym.Function("B") # ruff:ignore[non-lowercase-variable-in-function] + G = sym.Function("G") # ruff:ignore[non-lowercase-variable-in-function] + t = Tuple(*( + a, + a + S(1)/2, + 2*a, + b, + 2*a - b + 1, + (sym.sqrt(z)/2)**(-2*a + 1) + * B(2*a-b, sym.sqrt(z)) + * B(b - 1, sym.sqrt(z))*G(b)*G(2*a - b + 1), + sym.sqrt(z)*(sym.sqrt(z)/2)**(-2*a + 1) + * B(b, sym.sqrt(z)) + * B(2*a - b, sym.sqrt(z))*G(b)*G(2*a - b + 1), + sym.sqrt(z)*(sym.sqrt(z)/2)**(-2*a + 1) + * B(b - 1, sym.sqrt(z)) + * B(2*a - b + 1, sym.sqrt(z))*G(b)*G(2*a - b + 1), + (sym.sqrt(z)/2)**(-2*a + 1) + * B(b, sym.sqrt(z)) + * B(2*a - b + 1, sym.sqrt(z))*G(b)*G(2*a - b + 1), + 1, + 0, + S(1)/2, + z/2, + -b + 1, + -2*a + b, + -2*a)) c = cse(t) assert len(c[0]) == 11 +# }}} + + +# {{{ test_issue_6169 @sympyonly def test_issue_6169(): @@ -271,66 +343,89 @@ def test_issue_6169(): # mechanism assert sub_post(sub_pre((-x - y)*z - x - y)) == -z*(x + y) - x - y +# }}} + + +# {{{ test_cse_Indexed @sympyonly -def test_cse_Indexed(): # noqa - from sympy import IndexedBase, Idx +def test_cse_indexed(): + from sympy import Idx, IndexedBase len_y = 5 y = IndexedBase("y", shape=(len_y,)) x = IndexedBase("x", shape=(len_y,)) - Dy = IndexedBase("Dy", shape=(len_y-1,)) # noqa i = Idx("i", len_y-1) expr1 = (y[i+1]-y[i])/(x[i+1]-x[i]) expr2 = 1/(x[i+1]-x[i]) - replacements, reduced_exprs = cse([expr1, expr2]) + replacements, _reduced_exprs = cse([expr1, expr2]) assert len(replacements) > 0 +# }}} + + +# {{{ test_Piecewise @sympyonly -def test_Piecewise(): # noqa - from sympy import Piecewise, Eq +def test_piecewise(): + from sympy import Eq, Piecewise f = Piecewise((-z + x*y, Eq(y, 0)), (-z - x*y, True)) ans = cse(f) actual_ans = ([(x0, -z), (x1, x*y)], [Piecewise((x0+x1, Eq(y, 0)), (x0 - x1, True))]) assert ans == actual_ans +# }}} + + +# {{{ test_name_conflict def test_name_conflict(): z1 = x0 + y z2 = x2 + x3 - l_ = [cos(z1) + z1, cos(z2) + z2, x0 + x2] + l_ = [sym.cos(z1) + z1, sym.cos(z2) + z2, x0 + x2] substs, reduced = cse(l_) assert [e.subs(dict(substs)) for e in reduced] == l_ +# }}} + + +# {{{ test_name_conflict_cust_symbols def test_name_conflict_cust_symbols(): z1 = x0 + y z2 = x2 + x3 - l_ = [cos(z1) + z1, cos(z2) + z2, x0 + x2] - substs, reduced = cse(l_, symbols("x:10")) + l_ = [sym.cos(z1) + z1, sym.cos(z2) + z2, x0 + x2] + substs, reduced = cse(l_, sym.symbols("x:10")) assert [e.subs(dict(substs)) for e in reduced] == l_ +# }}} + + +# {{{ test_symbols_exhausted_error def test_symbols_exhausted_error(): - l_ = cos(x+y)+x+y+cos(w+y)+sin(w+y) - sym = [x, y, z] + l_ = sym.cos(x+y)+x+y+sym.cos(w+y)+sym.sin(w+y) + s = [x, y, z] with pytest.raises(ValueError): - print(cse(l_, symbols=sym)) + logger.info("cse:\n%s", cse(l_, symbols=s)) +# }}} + + +# {{{ test_issue_7840 @sympyonly def test_issue_7840(): # daveknippers' example - C393 = sympify( # noqa + C393 = sym.sympify( # ruff:ignore[non-lowercase-variable-in-function] "Piecewise((C391 - 1.65, C390 < 0.5), (Piecewise((C391 - 1.65, \ C391 > 2.35), (C392, True)), True))" ) - C391 = sympify( # noqa + C391 = sym.sympify( # ruff:ignore[non-lowercase-variable-in-function] "Piecewise((2.05*C390**(-1.03), C390 < 0.5), (2.5*C390**(-0.625), True))" ) - C393 = C393.subs("C391",C391) # noqa + C393 = C393.subs("C391", C391) # ruff:ignore[non-lowercase-variable-in-function] # simple substitution sub = {} sub["C390"] = 0.703451854 @@ -345,7 +440,7 @@ def test_issue_7840(): assert ss_answer == cse_answer # GitRay's example - expr = sympify( + expr = sym.sympify( "Piecewise((Symbol('ON'), Equality(Symbol('mode'), Symbol('ON'))), \ (Piecewise((Piecewise((Symbol('OFF'), StrictLessThan(Symbol('x'), \ Symbol('threshold'))), (Symbol('ON'), S.true)), Equality(Symbol('mode'), \ @@ -357,6 +452,10 @@ def test_issue_7840(): # there should not be any replacements assert len(substitutions) < 1 +# }}} + + +# {{{ test_recursive_matching def test_recursive_matching(): assert cse([x+y, 2+x+y, x+y+z, 3+x+y+z]) == \ @@ -372,12 +471,16 @@ def test_recursive_matching(): assert cse([2*x*x, x*x*y, x*x*y*w, x*x*y*w*x0, x*x*y*w*x2]) == \ ([(x1, x**2), (x3, x1*y), (x4, w*x3)], [2*x1, x3, x4, x0*x4, x2*x4]) +# }}} + + +# You can test individual routines by typing +# $ python test_cse.py 'test_recursive_matching()' if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) else: - from pytest import main - main([__file__]) + pytest.main([__file__]) # vim: fdm=marker diff --git a/sumpy/test/test_distributed.py b/sumpy/test/test_distributed.py new file mode 100644 index 000000000..29f339bbf --- /dev/null +++ b/sumpy/test/test_distributed.py @@ -0,0 +1,197 @@ +from __future__ import annotations + + +__copyright__ = "Copyright (C) 2022 Hao Gao" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import logging +import os +from functools import partial + +import numpy as np +import pytest + +from arraycontext import pytest_generate_tests_for_array_contexts + +from sumpy.array_context import PytestPyOpenCLArrayContextFactory, _acf + + +logger = logging.getLogger(__name__) + +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) + + +# Note: Do not import mpi4py.MPI object at the module level, because OpenMPI does not +# support recursive invocations. + + +def set_cache_dir(mpirank): + """Make each rank use a different cache location to avoid conflict.""" + import platformdirs + cache_dir = platformdirs.user_cache_dir("sumpy", "sumpy") + + # FIXME: should clean up this directory after running the tests + os.environ["XDG_CACHE_HOME"] = os.path.join(cache_dir, str(mpirank)) + + +# {{{ _test_against_single_rank + +def _test_against_single_rank( + dims, nsources, ntargets, dtype, communicate_mpoles_via_allreduce=False): + from mpi4py import MPI + + # Get the current rank + comm = MPI.COMM_WORLD + mpi_rank = comm.Get_rank() + set_cache_dir(mpi_rank) + + # Configure array context + actx = _acf() + + def fmm_level_to_order(base_kernel, kernel_arg_set, tree, level): + return max(level, 3) + + from boxtree.traversal import FMMTraversalBuilder + traversal_builder = FMMTraversalBuilder(actx, well_sep_is_n_away=2) + + from sumpy.expansion import DefaultExpansionFactory + from sumpy.kernel import LaplaceKernel + kernel = LaplaceKernel(dims) + expansion_factory = DefaultExpansionFactory() + local_expansion_factory = expansion_factory.get_local_expansion_class(kernel) + local_expansion_factory = partial(local_expansion_factory, kernel) + multipole_expansion_factory = \ + expansion_factory.get_multipole_expansion_class(kernel) + multipole_expansion_factory = partial(multipole_expansion_factory, kernel) + + from sumpy.fmm import SumpyTreeIndependentDataForWrangler + tree_indep = SumpyTreeIndependentDataForWrangler( + actx, multipole_expansion_factory, local_expansion_factory, [kernel]) + + global_tree_dev = None + sources_weights = actx.np.zeros(0, dtype=dtype) + + if mpi_rank == 0: + # Generate random particles and source weights + from boxtree.tools import make_normal_particle_array as p_normal + sources = p_normal(actx, nsources, dims, dtype, seed=15) + targets = p_normal(actx, ntargets, dims, dtype, seed=18) + + # FIXME: Use arraycontext instead of raw PyOpenCL arrays + rng = np.random.default_rng(20) + sources_weights = actx.from_numpy(rng.random(nsources, dtype=np.float64)) + target_radii = actx.from_numpy(0.05 * rng.random(ntargets, dtype=np.float64)) + + # Build the tree and interaction lists + from boxtree import TreeBuilder + tb = TreeBuilder(actx) + global_tree_dev, _ = tb( + actx, sources, targets=targets, target_radii=target_radii, + stick_out_factor=0.25, max_particles_in_box=30, debug=True) + + global_trav_dev, _ = traversal_builder(actx, global_tree_dev, debug=True) + + from sumpy.fmm import SumpyExpansionWrangler + wrangler = SumpyExpansionWrangler(tree_indep, global_trav_dev, dtype, + fmm_level_to_order) + + # Compute FMM with one MPI rank + from boxtree.fmm import drive_fmm + shmem_potential = drive_fmm(actx, wrangler, [sources_weights]) + + # Compute FMM using the distributed implementation + + def wrangler_factory(local_traversal, global_traversal): + from sumpy.distributed import DistributedSumpyExpansionWrangler + return DistributedSumpyExpansionWrangler( + actx, comm, tree_indep, local_traversal, global_traversal, dtype, + fmm_level_to_order, + communicate_mpoles_via_allreduce=communicate_mpoles_via_allreduce) + + from boxtree.distributed import DistributedFMMRunner + distributed_fmm_info = DistributedFMMRunner( + actx, global_tree_dev, traversal_builder, wrangler_factory, comm=comm) + + distributed_potential = distributed_fmm_info.drive_dfmm(actx, [sources_weights]) + + if mpi_rank == 0: + assert shmem_potential.shape == (1,) + assert distributed_potential.shape == (1,) + + shmem_potential = actx.to_numpy(shmem_potential[0]) + distributed_potential = actx.to_numpy(distributed_potential[0]) + + error = (np.linalg.norm(distributed_potential - shmem_potential, ord=np.inf) + / np.linalg.norm(shmem_potential, ord=np.inf)) + print(error) + assert error < 1e-14 + + +@pytest.mark.mpi +@pytest.mark.parametrize( + "num_processes, dims, nsources, ntargets, communicate_mpoles_via_allreduce", [ + (4, 3, 10000, 10000, True), + (4, 3, 10000, 10000, False) + ] +) +def test_against_single_rank( + num_processes, dims, nsources, ntargets, communicate_mpoles_via_allreduce): + pytest.importorskip("mpi4py") + + from boxtree.tools import run_mpi + run_mpi(__file__, num_processes, { + "OMP_NUM_THREADS": 1, + "_SUMPY_TEST_NAME": "against_single_rank", + "_SUMPY_TEST_DIMS": dims, + "_SUMPY_TEST_NSOURCES": nsources, + "_SUMPY_TEST_NTARGETS": ntargets, + "_SUMPY_TEST_MPOLES_ALLREDUCE": communicate_mpoles_via_allreduce + }) + +# }}} + + +if __name__ == "__main__": + if "_SUMPY_TEST_NAME" in os.environ: + name = os.environ["_SUMPY_TEST_NAME"] + if name == "against_single_rank": + # Run "test_against_single_rank" test case + dims = int(os.environ["_SUMPY_TEST_DIMS"]) + nsources = int(os.environ["_SUMPY_TEST_NSOURCES"]) + ntargets = int(os.environ["_SUMPY_TEST_NTARGETS"]) + + communicate_mpoles_via_allreduce = ( + os.environ["_SUMPY_TEST_MPOLES_ALLREDUCE"] == "True") + + _test_against_single_rank( + dims, nsources, ntargets, np.float64, + communicate_mpoles_via_allreduce) + else: + raise ValueError(f"Invalid test name: {name!r}") + else: + # You can test individual routines by typing + # $ python test_distributed.py + # 'test_against_single_rank(4, 3, 10000, 10000, False)' + import sys + exec(sys.argv[1]) diff --git a/sumpy/test/test_fmm.py b/sumpy/test/test_fmm.py new file mode 100644 index 000000000..1a87a67f6 --- /dev/null +++ b/sumpy/test/test_fmm.py @@ -0,0 +1,760 @@ +from __future__ import annotations + + +__copyright__ = "Copyright (C) 2013 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import logging +import os +import sys +from dataclasses import fields +from functools import partial + +import numpy as np +import numpy.linalg as la +import pytest + +from arraycontext import ( + ArrayContextFactory, + PyOpenCLArrayContext, + pytest_generate_tests_for_array_contexts, +) +from pytools import obj_array + +from sumpy.array_context import ( # ruff:ignore[unused-import] + PytestPyOpenCLArrayContextFactory, + _acf, +) +from sumpy.expansion.local import ( + H2DLocalExpansion, + LinearPDEConformingVolumeTaylorLocalExpansion, + VolumeTaylorLocalExpansion, + Y2DLocalExpansion, +) +from sumpy.expansion.multipole import ( + H2DMultipoleExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion, + VolumeTaylorMultipoleExpansion, + Y2DMultipoleExpansion, +) +from sumpy.fmm import SumpyExpansionWrangler, SumpyTreeIndependentDataForWrangler +from sumpy.kernel import ( + BiharmonicKernel, + HelmholtzKernel, + LaplaceKernel, + ScalarKernel, + YukawaKernel, +) + + +logger = logging.getLogger(__name__) + +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) + + +# {{{ test_sumpy_fmm + +@pytest.mark.parametrize( + ("use_translation_classes", "use_fft", "fft_backend"), [ + (False, False, None), + (True, False, None), + (True, True, "loopy"), + (True, True, "pyvkfft"), + ]) +@pytest.mark.parametrize( + ("knl", "local_expn_class", "mpole_expn_class", + "order_varies_with_level"), [ + (LaplaceKernel(2), VolumeTaylorLocalExpansion, + VolumeTaylorMultipoleExpansion, False), + (LaplaceKernel(2), LinearPDEConformingVolumeTaylorLocalExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion, False), + (LaplaceKernel(3), VolumeTaylorLocalExpansion, + VolumeTaylorMultipoleExpansion, False), + (LaplaceKernel(3), LinearPDEConformingVolumeTaylorLocalExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion, False), + (HelmholtzKernel(2), VolumeTaylorLocalExpansion, + VolumeTaylorMultipoleExpansion, False), + (HelmholtzKernel(2), LinearPDEConformingVolumeTaylorLocalExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion, False), + (HelmholtzKernel(2), H2DLocalExpansion, H2DMultipoleExpansion, False), + (HelmholtzKernel(2), H2DLocalExpansion, H2DMultipoleExpansion, True), + (HelmholtzKernel(3), VolumeTaylorLocalExpansion, + VolumeTaylorMultipoleExpansion, False), + (HelmholtzKernel(3), LinearPDEConformingVolumeTaylorLocalExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion, False), + (YukawaKernel(2), Y2DLocalExpansion, Y2DMultipoleExpansion, + False), + ]) +def test_sumpy_fmm( + actx_factory: ArrayContextFactory, + knl: ScalarKernel, + local_expn_class, + mpole_expn_class, + order_varies_with_level, + use_translation_classes, + use_fft, + fft_backend, + visualize=False): + if fft_backend == "pyvkfft": + pytest.importorskip("pyvkfft") + + if visualize: + logging.basicConfig(level=logging.INFO) + + if local_expn_class == VolumeTaylorLocalExpansion and use_fft: + pytest.skip("VolumeTaylorExpansion with FFT takes a lot of resources.") + + if local_expn_class in [H2DLocalExpansion, Y2DLocalExpansion] and use_fft: + pytest.skip("Fourier/Bessel based expansions with FFT is not supported yet.") + + if use_fft: + from unittest.mock import patch + + with patch.dict(os.environ, {"SUMPY_FFT_BACKEND": fft_backend}): + _test_sumpy_fmm(actx_factory, knl, local_expn_class, mpole_expn_class, + order_varies_with_level, use_translation_classes, use_fft, + fft_backend) + else: + _test_sumpy_fmm(actx_factory, knl, local_expn_class, mpole_expn_class, + order_varies_with_level, use_translation_classes, use_fft, + fft_backend) + + +def _test_sumpy_fmm( + actx_factory: ArrayContextFactory, + knl: ScalarKernel, + local_expn_class, + mpole_expn_class, + order_varies_with_level, + use_translation_classes, + use_fft, + fft_backend): + + actx = actx_factory() + + if fft_backend == "pyvkfft": + from pyopencl.characterize import get_pocl_version + if (isinstance(actx, PyOpenCLArrayContext) + and get_pocl_version(actx.queue.device.platform) >= (7,)): + pytest.skip("pocl 7 and pyvkfft don't get along: " + "https://github.com/pocl/pocl/issues/2069") + + nsources = 1000 + ntargets = 300 + dtype = np.float64 + + from boxtree.tools import make_normal_particle_array as p_normal + sources = p_normal(actx, nsources, knl.dim, dtype, seed=15) + + if 1: + offset = np.zeros(knl.dim) + offset[0] = 0.1 + targets = offset + p_normal(actx, ntargets, knl.dim, dtype, seed=18) + + del offset + else: + from sumpy.visualization import FieldPlotter + fp = FieldPlotter(np.array([0.5, 0]), extent=3, npoints=200) + + targets = obj_array.new_1d([fp.points[i] for i in range(knl.dim)]) + + from boxtree import TreeBuilder + tb = TreeBuilder(actx) + tree, _ = tb(actx, sources, targets=targets, + max_particles_in_box=30, debug=True) + + from boxtree.traversal import FMMTraversalBuilder + tbuild = FMMTraversalBuilder(actx) + trav, _ = tbuild(actx, tree, debug=True) + + # {{{ plot tree + + if 0: + host_tree = actx.to_numpy(tree) + host_trav = actx.to_numpy(trav) + + if 0: + logger.info("src_box: %s", host_tree.find_box_nr_for_source(403)) + logger.info("tgt_box: %s", host_tree.find_box_nr_for_target(28)) + logger.info("%s", + list(host_trav.target_or_target_parent_boxes).index(37)) + logger.info("%s", host_trav.get_box_list("sep_bigger", 22)) + + from boxtree.visualization import TreePlotter + plotter = TreePlotter(host_tree) + plotter.draw_tree(fill=False, edgecolor="black", zorder=10) + plotter.set_bounding_box() + plotter.draw_box_numbers() + + import matplotlib.pyplot as pt + pt.show() + + # }}} + + rng = np.random.default_rng(44) + weights = actx.from_numpy(rng.random(nsources, dtype=np.float64)) + logger.info("computing direct (reference) result") + + from pytools.convergence import PConvergenceVerifier + pconv_verifier = PConvergenceVerifier() + + extra_kwargs = {} + dtype = np.float64 + order_values = [1, 2, 3] + if isinstance(knl, HelmholtzKernel): + extra_kwargs["k"] = 0.05 + dtype = np.complex128 + + if knl.dim == 3: + order_values = [1, 2] + elif knl.dim == 2 and issubclass(local_expn_class, H2DLocalExpansion): + order_values = [4, 5] + + elif isinstance(knl, YukawaKernel): + extra_kwargs["lam"] = 2 + dtype = np.complex128 + + if knl.dim == 3: + order_values = [1, 2] + elif knl.dim == 2 and issubclass(local_expn_class, Y2DLocalExpansion): + order_values = [10, 12] + + for order in order_values: + target_kernels = [knl] + + if use_fft: + from sumpy.expansion.m2l import FFTM2LTranslationClassFactory + m2l_translation_factory = FFTM2LTranslationClassFactory() + else: + from sumpy.expansion.m2l import NonFFTM2LTranslationClassFactory + m2l_translation_factory = NonFFTM2LTranslationClassFactory() + + m2l_translation = m2l_translation_factory.get_m2l_translation_class( + knl, local_expn_class)() + + if any(f.name == "m2l_translation_override" for f in fields(local_expn_class)): + local_expansion_factory = partial(local_expn_class, knl, + m2l_translation_override=m2l_translation) + else: + local_expansion_factory = partial(local_expn_class, knl) + + tree_indep = SumpyTreeIndependentDataForWrangler( + actx, + partial(mpole_expn_class, knl), + local_expansion_factory, + target_kernels) + + if order_varies_with_level: + def fmm_level_to_order(kernel, kernel_args, tree, lev): + return order + lev % 2 # ruff:ignore[function-uses-loop-variable] + else: + def fmm_level_to_order(kernel, kernel_args, tree, lev): + return order # ruff:ignore[function-uses-loop-variable] + + wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, + fmm_level_to_order=fmm_level_to_order, + kernel_extra_kwargs=extra_kwargs, + _disable_translation_classes=not use_translation_classes) + + from boxtree.fmm import drive_fmm + + pot, = drive_fmm(actx, wrangler, (weights,)) + + from sumpy import P2P + p2p = P2P(target_kernels, exclude_self=False) + ref_pot, = p2p(actx, targets, sources, (weights,), **extra_kwargs) + + pot = actx.to_numpy(pot) + ref_pot = actx.to_numpy(ref_pot) + + rel_err = la.norm(pot - ref_pot, np.inf) / la.norm(ref_pot, np.inf) + logger.info("order %d -> relative l2 error: %g", order, rel_err) + + pconv_verifier.add_data_point(order, rel_err) + + logger.info("\n%s", pconv_verifier) + pconv_verifier() + +# }}} + + +# {{{ test_coeff_magnitude_rscale + +@pytest.mark.parametrize("knl", [LaplaceKernel(2), BiharmonicKernel(2)]) +def test_coeff_magnitude_rscale(actx_factory: ArrayContextFactory, knl): + """Checks that the rscale used keeps the coefficient magnitude + difference small + """ + local_expn_class = LinearPDEConformingVolumeTaylorLocalExpansion + mpole_expn_class = LinearPDEConformingVolumeTaylorMultipoleExpansion + + actx = actx_factory() + + nsources = 1000 + ntargets = 300 + dtype = np.float64 + + from boxtree.tools import make_normal_particle_array as p_normal + + sources = p_normal(actx, nsources, knl.dim, dtype, seed=15) + offset = np.zeros(knl.dim) + offset[0] = 0.1 + + targets = offset + p_normal(actx, ntargets, knl.dim, dtype, seed=18) + + from boxtree import TreeBuilder + tb = TreeBuilder(actx) + tree, _ = tb(actx, sources, targets=targets, max_particles_in_box=30, debug=True) + + from boxtree.traversal import FMMTraversalBuilder + tbuild = FMMTraversalBuilder(actx) + trav, _ = tbuild(actx, tree, debug=True) + + rng = np.random.default_rng(31) + weights = actx.from_numpy(rng.random(nsources, dtype=np.float64)) + + extra_kwargs = {} + dtype = np.float64 + order = 10 + if isinstance(knl, HelmholtzKernel): + extra_kwargs["k"] = 0.05 + dtype = np.complex128 + + elif isinstance(knl, YukawaKernel): + extra_kwargs["lam"] = 2 + dtype = np.complex128 + + target_kernels = [knl] + + tree_indep = SumpyTreeIndependentDataForWrangler( + actx, + partial(mpole_expn_class, knl), + partial(local_expn_class, knl), + target_kernels) + + def fmm_level_to_order(kernel, kernel_args, tree, lev): + return order + + wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, + fmm_level_to_order=fmm_level_to_order, + kernel_extra_kwargs=extra_kwargs) + + weights = wrangler.reorder_sources(weights) + (weights,) = wrangler.distribute_source_weights(actx, (weights,), None) + + local_result = wrangler.form_locals( + actx, + trav.level_start_target_or_target_parent_box_nrs, + trav.target_or_target_parent_boxes, + trav.from_sep_bigger_starts, + trav.from_sep_bigger_lists, + (weights,)) + + result = actx.to_numpy( + actx.np.abs(wrangler.local_expansions_view(local_result, 5)[1][0]) + ) + + result_ratio = np.max(result) / np.min(result) + assert result_ratio < 10**6, result_ratio + +# }}} + + +# {{{ test_unified_single_and_double + +def test_unified_single_and_double(actx_factory: ArrayContextFactory, visualize=False): + """ + Test that running one FMM for single layer + double layer gives the + same result as running one FMM for each and adding the results together + at the end + """ + if visualize: + logging.basicConfig(level=logging.INFO) + + actx = actx_factory() + + knl = LaplaceKernel(2) + local_expn_class = LinearPDEConformingVolumeTaylorLocalExpansion + mpole_expn_class = LinearPDEConformingVolumeTaylorMultipoleExpansion + + nsources = 1000 + ntargets = 300 + dtype = np.float64 + + from boxtree.tools import make_normal_particle_array as p_normal + + sources = p_normal(actx, nsources, knl.dim, dtype, seed=15) + offset = np.zeros(knl.dim) + offset[0] = 0.1 + + targets = offset + p_normal(actx, ntargets, knl.dim, dtype, seed=18) + del offset + + from boxtree import TreeBuilder + tb = TreeBuilder(actx) + tree, _ = tb(actx, sources, targets=targets, max_particles_in_box=30, debug=True) + + from boxtree.traversal import FMMTraversalBuilder + tbuild = FMMTraversalBuilder(actx) + trav, _ = tbuild(actx, tree, debug=True) + + rng = np.random.default_rng(44) + weights = ( + actx.from_numpy(rng.random(nsources, dtype=np.float64)), + actx.from_numpy(rng.random(nsources, dtype=np.float64)) + ) + + logger.info("computing direct (reference) result") + + dtype = np.float64 + order = 3 + + from sumpy.kernel import AxisTargetDerivative, DirectionalSourceDerivative + + deriv_knl = DirectionalSourceDerivative(knl, "dir_vec") + + target_kernels = [knl, AxisTargetDerivative(0, knl)] + source_kernel_vecs = [[knl], [deriv_knl], [knl, deriv_knl]] + strength_usages = [[0], [1], [0, 1]] + + alpha = np.linspace(0, 2*np.pi, nsources, np.float64) + dir_vec = actx.from_numpy(np.vstack([np.cos(alpha), np.sin(alpha)])) + + results = [] + for source_kernels, strength_usage in zip( + source_kernel_vecs, strength_usages, strict=True): + source_extra_kwargs = {} + if deriv_knl in source_kernels: + source_extra_kwargs["dir_vec"] = dir_vec + tree_indep = SumpyTreeIndependentDataForWrangler( + actx, + partial(mpole_expn_class, knl), + partial(local_expn_class, knl), + target_kernels=target_kernels, source_kernels=source_kernels, + strength_usage=strength_usage) + wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, + fmm_level_to_order=lambda kernel, kernel_args, tree, lev: order, + source_extra_kwargs=source_extra_kwargs) + + from boxtree.fmm import drive_fmm + + pot = drive_fmm(actx, wrangler, weights) + results.append(np.array([actx.to_numpy(pot[0]), actx.to_numpy(pot[1])])) + + ref_pot = results[0] + results[1] + pot = results[2] + rel_err = la.norm(pot - ref_pot, np.inf) / la.norm(ref_pot, np.inf) + + assert rel_err < 1e-12 + +# }}} + + +# {{{ test_sumpy_fmm_timing_data_collection + +@pytest.mark.parametrize("use_fft", [True, False]) +def test_sumpy_fmm_timing_data_collection(ctx_factory, use_fft, visualize=False): + if visualize: + logging.basicConfig(level=logging.INFO) + + import pyopencl as cl + + from sumpy.array_context import PyOpenCLArrayContext + + ctx = ctx_factory() + queue = cl.CommandQueue(ctx, + properties=cl.command_queue_properties.PROFILING_ENABLE) + actx = PyOpenCLArrayContext(queue) + + nsources = 500 + dtype = np.float64 + + from boxtree.tools import make_normal_particle_array as p_normal + + knl = LaplaceKernel(2) + local_expn_class = VolumeTaylorLocalExpansion + mpole_expn_class = VolumeTaylorMultipoleExpansion + order = 1 + + sources = p_normal(actx, nsources, knl.dim, dtype, seed=15) + + from boxtree import TreeBuilder + tb = TreeBuilder(actx) + tree, _ = tb(actx, sources, max_particles_in_box=30, debug=True) + + from boxtree.traversal import FMMTraversalBuilder + tbuild = FMMTraversalBuilder(actx) + trav, _ = tbuild(actx, tree, debug=True) + + rng = np.random.default_rng(44) + weights = actx.from_numpy(rng.random(nsources, dtype=np.float64)) + + target_kernels = [knl] + + if use_fft: + from sumpy.expansion.m2l import FFTM2LTranslationClassFactory + m2l_translation_factory = FFTM2LTranslationClassFactory() + else: + from sumpy.expansion.m2l import NonFFTM2LTranslationClassFactory + m2l_translation_factory = NonFFTM2LTranslationClassFactory() + + m2l_translation = m2l_translation_factory.get_m2l_translation_class( + knl, local_expn_class)() + + tree_indep = SumpyTreeIndependentDataForWrangler( + actx, + partial(mpole_expn_class, knl), + partial(local_expn_class, knl, m2l_translation_override=m2l_translation), + target_kernels) + + wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, + fmm_level_to_order=lambda kernel, kernel_args, tree, lev: order) + from boxtree.fmm import drive_fmm + + _pot, = drive_fmm(actx, wrangler, (weights,)) + + +def test_sumpy_fmm_exclude_self(actx_factory: ArrayContextFactory, visualize=False): + if visualize: + logging.basicConfig(level=logging.INFO) + + actx = actx_factory() + + nsources = 500 + dtype = np.float64 + + from boxtree.tools import make_normal_particle_array as p_normal + + knl = LaplaceKernel(2) + local_expn_class = VolumeTaylorLocalExpansion + mpole_expn_class = VolumeTaylorMultipoleExpansion + order = 10 + + sources = p_normal(actx, nsources, knl.dim, dtype, seed=15) + + from boxtree import TreeBuilder + tb = TreeBuilder(actx) + + tree, _ = tb(actx, sources, max_particles_in_box=30, debug=True) + + from boxtree.traversal import FMMTraversalBuilder + tbuild = FMMTraversalBuilder(actx) + trav, _ = tbuild(actx, tree, debug=True) + + rng = np.random.default_rng(44) + weights = actx.from_numpy(rng.random(nsources, dtype=np.float64)) + + target_to_source = actx.from_numpy(np.arange(tree.ntargets, dtype=np.int32)) + self_extra_kwargs = {"target_to_source": target_to_source} + + target_kernels = [knl] + + tree_indep = SumpyTreeIndependentDataForWrangler( + actx, + partial(mpole_expn_class, knl), + partial(local_expn_class, knl), + target_kernels, + exclude_self=True) + + wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, + fmm_level_to_order=lambda kernel, kernel_args, tree, lev: order, + self_extra_kwargs=self_extra_kwargs) + + from boxtree.fmm import drive_fmm + + pot, = drive_fmm(actx, wrangler, (weights,)) + + from sumpy import P2P + p2p = P2P(target_kernels, exclude_self=True) + ref_pot, = p2p(actx, sources, sources, (weights,), **self_extra_kwargs) + + pot = actx.to_numpy(pot) + ref_pot = actx.to_numpy(ref_pot) + + rel_err = la.norm(pot - ref_pot) / la.norm(ref_pot) + logger.info("order %d -> relative l2 error: %g", order, rel_err) + + assert np.isclose(rel_err, 0, atol=1e-7) + +# }}} + + +# {{{ test_sumpy_axis_source_derivative + +def test_sumpy_axis_source_derivative( + actx_factory: ArrayContextFactory, + visualize=False): + if visualize: + logging.basicConfig(level=logging.INFO) + + actx = actx_factory() + + nsources = 500 + dtype = np.float64 + + from boxtree.tools import make_normal_particle_array as p_normal + + knl = LaplaceKernel(2) + local_expn_class = VolumeTaylorLocalExpansion + mpole_expn_class = VolumeTaylorMultipoleExpansion + order = 10 + + sources = p_normal(actx, nsources, knl.dim, dtype, seed=15) + + from boxtree import TreeBuilder + tb = TreeBuilder(actx) + tree, _ = tb(actx, sources, max_particles_in_box=30, debug=True) + + from boxtree.traversal import FMMTraversalBuilder + tbuild = FMMTraversalBuilder(actx) + trav, _ = tbuild(actx, tree, debug=True) + + rng = np.random.default_rng(12) + weights = actx.from_numpy(rng.random(nsources, dtype=np.float64)) + + target_to_source = actx.from_numpy(np.arange(tree.ntargets, dtype=np.int32)) + self_extra_kwargs = {"target_to_source": target_to_source} + + from sumpy.kernel import AxisSourceDerivative, AxisTargetDerivative + + pots = [] + for tgt_knl, src_knl in [ + (AxisTargetDerivative(0, knl), knl), + (knl, AxisSourceDerivative(0, knl))]: + tree_indep = SumpyTreeIndependentDataForWrangler( + actx, + partial(mpole_expn_class, knl), + partial(local_expn_class, knl), + target_kernels=[tgt_knl], + source_kernels=[src_knl], + exclude_self=True) + + wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, + fmm_level_to_order=lambda kernel, kernel_args, tree, lev: order, + self_extra_kwargs=self_extra_kwargs) + + from boxtree.fmm import drive_fmm + + pot, = drive_fmm(actx, wrangler, (weights,)) + pots.append(actx.to_numpy(pot)) + + rel_err = la.norm(pots[0] + pots[1]) / la.norm(pots[0]) + logger.info("order %d -> relative l2 error: %g", order, rel_err) + + assert np.isclose(rel_err, 0, atol=1e-5) + +# }}} + + +# {{{ test_sumpy_target_point_multiplier + +@pytest.mark.parametrize("deriv_axes", [(), (0,), (1,)]) +def test_sumpy_target_point_multiplier( + actx_factory: ArrayContextFactory, + deriv_axes, + visualize=False): + if visualize: + logging.basicConfig(level=logging.INFO) + + actx = actx_factory() + + nsources = 500 + dtype = np.float64 + + from boxtree.tools import make_normal_particle_array as p_normal + + knl = LaplaceKernel(2) + local_expn_class = VolumeTaylorLocalExpansion + mpole_expn_class = VolumeTaylorMultipoleExpansion + order = 5 + + sources = p_normal(actx, nsources, knl.dim, dtype, seed=15) + + from boxtree import TreeBuilder + tb = TreeBuilder(actx) + + tree, _ = tb(actx, sources, + max_particles_in_box=30, debug=True) + + from boxtree.traversal import FMMTraversalBuilder + tbuild = FMMTraversalBuilder(actx) + trav, _ = tbuild(actx, tree, debug=True) + + rng = np.random.default_rng(12) + weights = actx.from_numpy(rng.random(nsources, dtype=np.float64)) + + target_to_source = actx.from_numpy(np.arange(tree.ntargets, dtype=np.int32)) + self_extra_kwargs = {"target_to_source": target_to_source} + + from sumpy.kernel import AxisTargetDerivative, TargetPointMultiplier + + tgt_knls = [TargetPointMultiplier(0, knl), knl, knl] + for axis in deriv_axes: + tgt_knls[0] = AxisTargetDerivative(axis, tgt_knls[0]) + tgt_knls[1] = AxisTargetDerivative(axis, tgt_knls[1]) + + tree_indep = SumpyTreeIndependentDataForWrangler( + actx, + partial(mpole_expn_class, knl), + partial(local_expn_class, knl), + target_kernels=tgt_knls, + source_kernels=[knl], + exclude_self=True) + + wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, + fmm_level_to_order=lambda kernel, kernel_args, tree, lev: order, + self_extra_kwargs=self_extra_kwargs) + + from boxtree.fmm import drive_fmm + + pot0, pot1, pot2 = drive_fmm(actx, wrangler, (weights,)) + pot0, pot1, pot2 = actx.to_numpy(pot0), actx.to_numpy(pot1), actx.to_numpy(pot2) + if deriv_axes == (0,): + ref_pot = pot1 * actx.to_numpy(sources[0]) + pot2 + else: + ref_pot = pot1 * actx.to_numpy(sources[0]) + + rel_err = la.norm(pot0 - ref_pot) / la.norm(ref_pot) + logger.info("order %d -> relative l2 error: %g", order, rel_err) + + assert np.isclose(rel_err, 0, atol=1e-5) + +# }}} + + +""" +You can test individual routines by typing +$ python test/test_fmm.py 'test_sumpy_fmm(_acf, LaplaceKernel(2), + VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion, + order_varies_with_level=False, use_translation_classes=True, use_fft=True, + fft_backend="pyvkfft", visualize=True)' +""" + +if __name__ == "__main__": + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + pytest.main([__file__]) + +# vim: fdm=marker diff --git a/sumpy/test/test_heat_translations.py b/sumpy/test/test_heat_translations.py new file mode 100644 index 000000000..3d2f686f1 --- /dev/null +++ b/sumpy/test/test_heat_translations.py @@ -0,0 +1,318 @@ +from __future__ import annotations + + +__copyright__ = "Copyright (C) 2012 Chaoqi Lin" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import logging +import sys +from functools import partial + +import numpy as np +import numpy.linalg as la +import pytest + +from arraycontext import ArrayContextFactory, pytest_generate_tests_for_array_contexts +from pytools.convergence import EOCRecorder + +import sumpy.toys as t +from sumpy.array_context import ( # ruff:ignore[unused-import] + PytestPyOpenCLArrayContextFactory, + _acf, +) +from sumpy.expansion.local import ( + LinearPDEConformingVolumeTaylorLocalExpansion, + VolumeTaylorLocalExpansion, +) +from sumpy.expansion.m2l import NonFFTM2LTranslationClassFactory +from sumpy.expansion.multipole import ( + LinearPDEConformingVolumeTaylorMultipoleExpansion, + VolumeTaylorMultipoleExpansion, +) +from sumpy.kernel import HeatKernel + + +logger = logging.getLogger(__name__) + +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) + + +# {{{ test_heat_m2m + +@pytest.mark.parametrize("order", [4]) +@pytest.mark.parametrize("alpha", [1.0]) +@pytest.mark.parametrize(("knl", "mpole_expn_class"), [ + (HeatKernel(1), LinearPDEConformingVolumeTaylorMultipoleExpansion), + (HeatKernel(1), VolumeTaylorMultipoleExpansion), + ]) +def test_heat_m2m( + actx_factory: ArrayContextFactory, + knl, + mpole_expn_class, + alpha, + order): + # h-convergence of the heat-kernel M2M translation. + # src_center = (1, 0.5), shrinks with h (half-sizes h_x, h_t). + # tgt_center = (0, 2.5), fixed (half-sizes 1, 0.5). + # M2M from src_center to a shifted center c5 = src_center + (-h_x, h_t). + actx = actx_factory() + + extra_kwargs = {"alpha": alpha} + + t_sep = 1.0 + L = np.sqrt(4 * alpha * t_sep) # ruff:ignore[non-lowercase-variable-in-function] + + src_center = np.array([1.0, 0.5]) + tgt_center = np.array([0.0, 2.5]) + + grid = np.linspace(-1.0, 1.0, 5) + xs, ts = np.meshgrid(grid, grid, indexing="xy") + targets = np.vstack([ + tgt_center[0] + 1.0 * xs.ravel(), + tgt_center[1] + 0.5 * ts.ravel(), + ]) + + toy_ctx = t.ToyContext( + kernel=knl, + mpole_expn_class=mpole_expn_class, + extra_kernel_kwargs=extra_kwargs, + ) + + h_values = [1/4, 1/8, 1/16, 1/32] + rscale = 1 / order + + eoc_rec = EOCRecorder() + for h in h_values: + h_x = h * L + h_t = h * t_sep + + src_grid = np.linspace(-1.0, 1.0, 11) + sxs, sts = np.meshgrid(src_grid, src_grid, indexing="xy") + sources = np.vstack([ + src_center[0] + h_x * sxs.ravel(), + src_center[1] + h_t * sts.ravel(), + ]) + strengths = np.ones(sources.shape[1]) + + pt_src = t.PointSources(toy_ctx, sources, weights=strengths) + + c5 = src_center + np.array([-h_x, h_t]) + + p2m = t.multipole_expand(actx, pt_src, src_center, + order=order, rscale=rscale) + m2m = t.multipole_expand(actx, p2m, c5, order=order, rscale=rscale) + p2m_direct = t.multipole_expand(actx, pt_src, c5, + order=order, rscale=rscale) + + m2m_vals = np.asarray(m2m.eval(actx, targets)).ravel() + p2m_direct_vals = np.asarray(p2m_direct.eval(actx, targets)).ravel() + err = la.norm(m2m_vals - p2m_direct_vals) / la.norm(p2m_direct_vals) + eoc_rec.add_data_point(h, err) + + logger.info("knl %s order %d", knl, order) + logger.info("M2M:\n%s", eoc_rec) + + tgt_order = order + 1 + slack = 0.5 + assert eoc_rec.order_estimate() > tgt_order - slack + +# }}} + + +# {{{ test_heat_l2l + +@pytest.mark.parametrize("order", [4]) +@pytest.mark.parametrize("alpha", [1.0]) +@pytest.mark.parametrize(("knl", "local_expn_class"), [ + (HeatKernel(1), LinearPDEConformingVolumeTaylorLocalExpansion), + (HeatKernel(1), VolumeTaylorLocalExpansion), + ]) +def test_heat_l2l( + actx_factory: ArrayContextFactory, + knl, + local_expn_class, + alpha, + order): + # h-convergence of the heat-kernel L2L translation. + # src_center = (0, 0.5), fixed (half-widths 1, 0.5). + # tgt_center = (0, 2.5), shrinks with h (half-sizes h_x, h_t). + # L2L from tgt_center to a shifted center c2 = tgt_center - (h_x, h_t). + actx = actx_factory() + + extra_kwargs = {"alpha": alpha} + + src_center = np.array([0.0, 0.5]) + src_hx = 1.0 + src_ht = 0.5 + tgt_center = np.array([0.0, 2.5]) + + t_sep = 2 * src_ht + L = np.sqrt(4 * alpha * t_sep) # ruff:ignore[non-lowercase-variable-in-function] + + rng = np.random.default_rng(0) + src_grid = np.linspace(-1.0, 1.0, 11) + sxs, sts = np.meshgrid(src_grid, src_grid, indexing="xy") + sources = np.vstack([ + src_center[0] + src_hx * sxs.ravel(), + src_center[1] + src_ht * sts.ravel(), + ]) + strengths = rng.random(sources.shape[1]) + + toy_ctx = t.ToyContext( + kernel=knl, + local_expn_class=local_expn_class, + extra_kernel_kwargs=extra_kwargs, + ) + pt_src = t.PointSources(toy_ctx, sources, weights=strengths) + + rscale = 1 / order + p2l = t.local_expand(actx, pt_src, tgt_center, + order=order, rscale=rscale) + + h_values = [1/4, 1/8, 1/16, 1/32] + + eoc_rec = EOCRecorder() + for h in h_values: + h_x = h * L + h_t = h * t_sep + c2 = tgt_center - np.array([h_x, h_t]) + + l2l = t.local_expand(actx, p2l, c2, order=order, rscale=rscale) + p2l_direct = t.local_expand(actx, pt_src, c2, + order=order, rscale=rscale) + + tgt_grid = np.linspace(-1.0, 1.0, 5) + txs, tts = np.meshgrid(tgt_grid, tgt_grid, indexing="xy") + targets = np.vstack([ + tgt_center[0] + h_x * txs.ravel(), + tgt_center[1] + h_t * tts.ravel(), + ]) + l2l_vals = np.asarray(l2l.eval(actx, targets)).ravel() + p2l_direct_vals = np.asarray(p2l_direct.eval(actx, targets)).ravel() + err = la.norm(l2l_vals - p2l_direct_vals) / la.norm(p2l_direct_vals) + eoc_rec.add_data_point(h, err) + + logger.info("knl %s order %d", knl, order) + logger.info("L2L:\n%s", eoc_rec) + + tgt_order = order + 1 + slack = 0.5 + assert eoc_rec.order_estimate() > tgt_order - slack + +# }}} + + +# {{{ test_heat_m2l + +@pytest.mark.parametrize("order", [4]) +@pytest.mark.parametrize("alpha", [1.0]) +@pytest.mark.parametrize(("knl", "local_expn_class", "mpole_expn_class"), [ + (HeatKernel(1), + LinearPDEConformingVolumeTaylorLocalExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion), + ]) +def test_heat_m2l( + actx_factory: ArrayContextFactory, + knl, + local_expn_class, + mpole_expn_class, + alpha, + order): + # h-convergence of the heat-kernel M2L translation. + # src_center = (0, 0.5), fixed (half-widths 1, 0.5). + # tgt_center = (0, 2.5), shrinks with h (half-sizes h_x, h_t). + # Local expansion formed at c2 = tgt_center - (h_x, h_t). + actx = actx_factory() + + extra_kwargs = {"alpha": alpha} + + src_center = np.array([0.0, 0.5]) + src_hx = 1.0 + src_ht = 0.5 + tgt_center = np.array([0.0, 2.5]) + + t_sep = 2 * src_ht + L = np.sqrt(4 * alpha * t_sep) # ruff:ignore[non-lowercase-variable-in-function] + + rng = np.random.default_rng(0) + src_grid = np.linspace(-1.0, 1.0, 11) + sxs, sts = np.meshgrid(src_grid, src_grid, indexing="xy") + sources = np.vstack([ + src_center[0] + src_hx * sxs.ravel(), + src_center[1] + src_ht * sts.ravel(), + ]) + strengths = rng.random(sources.shape[1]) + + m2l_factory = NonFFTM2LTranslationClassFactory() + m2l_translation = m2l_factory.get_m2l_translation_class(knl, local_expn_class)() + toy_ctx = t.ToyContext( + kernel=knl, + local_expn_class=partial(local_expn_class, + m2l_translation_override=m2l_translation), + mpole_expn_class=mpole_expn_class, + extra_kernel_kwargs=extra_kwargs, + ) + pt_src = t.PointSources(toy_ctx, sources, weights=strengths) + + rscale = 1 / order + p2m = t.multipole_expand(actx, pt_src, src_center, + order=order, rscale=rscale) + + h_values = [1/4, 1/8, 1/16, 1/32] + + eoc_rec = EOCRecorder() + for h in h_values: + h_x = h * L + h_t = h * t_sep + c2 = tgt_center - np.array([h_x, h_t]) + + m2l = t.local_expand(actx, p2m, c2, order=order, rscale=rscale) + + tgt_grid = np.linspace(-1.0, 1.0, 5) + txs, tts = np.meshgrid(tgt_grid, tgt_grid, indexing="xy") + targets = np.vstack([ + tgt_center[0] + h_x * txs.ravel(), + tgt_center[1] + h_t * tts.ravel(), + ]) + m2l_vals = np.asarray(m2l.eval(actx, targets)).ravel() + p2m_vals = np.asarray(p2m.eval(actx, targets)).ravel() + err = la.norm(m2l_vals - p2m_vals) / la.norm(p2m_vals) + eoc_rec.add_data_point(h, err) + + logger.info("knl %s order %d", knl, order) + logger.info("M2L:\n%s", eoc_rec) + + tgt_order = order + 1 + slack = 0.5 + assert eoc_rec.order_estimate() > tgt_order - slack + +# }}} + + +if __name__ == "__main__": + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + pytest.main([__file__]) diff --git a/test/test_kernels.py b/sumpy/test/test_kernels.py similarity index 50% rename from test/test_kernels.py rename to sumpy/test/test_kernels.py index 1e0db7ce2..ea6d2b0fa 100644 --- a/test/test_kernels.py +++ b/sumpy/test/test_kernels.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2012 Andreas Kloeckner" __license__ = """ @@ -20,68 +23,107 @@ THE SOFTWARE. """ -import numpy as np -import numpy.linalg as la +import logging import sys +from functools import partial +from typing import TYPE_CHECKING +import numpy as np +import numpy.linalg as la import pytest -import pyopencl as cl -from pyopencl.tools import ( # noqa - pytest_generate_tests_for_pyopencl as pytest_generate_tests) -from sumpy.expansion.multipole import ( - VolumeTaylorMultipoleExpansion, H2DMultipoleExpansion, - VolumeTaylorMultipoleExpansionBase, - LinearPDEConformingVolumeTaylorMultipoleExpansion) -from sumpy.expansion.local import ( - VolumeTaylorLocalExpansion, H2DLocalExpansion, - LinearPDEConformingVolumeTaylorLocalExpansion) -from sumpy.kernel import (LaplaceKernel, HelmholtzKernel, AxisTargetDerivative, - DirectionalSourceDerivative, BiharmonicKernel, StokesletKernel) -import sumpy.symbolic as sym +from arraycontext import ( + ArrayContextFactory, + PyOpenCLArrayContext, + pytest_generate_tests_for_array_contexts, +) +from pytools import memoize_on_first_arg from pytools.convergence import PConvergenceVerifier -import logging +import sumpy.symbolic as sym +import sumpy.toys as t +from sumpy.array_context import ( # ruff:ignore[unused-import] + PytestPyOpenCLArrayContextFactory, + _acf, +) +from sumpy.expansion.local import ( + H2DLocalExpansion, + LinearPDEConformingVolumeTaylorLocalExpansion, + LineTaylorLocalExpansion, + VolumeTaylorLocalExpansion, +) +from sumpy.expansion.m2l import ( + FFTM2LTranslationClassFactory, + NonFFTM2LTranslationClassFactory, +) +from sumpy.expansion.multipole import ( + H2DMultipoleExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion, + VolumeTaylorMultipoleExpansion, + VolumeTaylorMultipoleExpansionBase, +) +from sumpy.kernel import ( + AxisTargetDerivative, + BiharmonicKernel, + BrinkmanletComponentKernel, + BrinkmanStressComponentKernel, + DirectionalSourceDerivative, + ElasticityComponentKernel, + HelmholtzKernel, + LaplaceKernel, + LineOfCompressionKernel, + OneKernel, + ScalarKernel, + StokesletComponentKernel, + StressletComponentKernel, + YukawaKernel, +) +from sumpy.test.geometries import make_ellipsoid, make_torus + + +if TYPE_CHECKING: + from collections.abc import Callable + logger = logging.getLogger(__name__) -try: - import faulthandler -except ImportError: - pass -else: - faulthandler.enable() +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) + +# {{{ test_p2p @pytest.mark.parametrize("exclude_self", (True, False)) -def test_p2p(ctx_factory, exclude_self): - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) +def test_p2p(actx_factory: ArrayContextFactory, exclude_self): + actx = actx_factory() dimensions = 3 n = 5000 from sumpy.p2p import P2P lknl = LaplaceKernel(dimensions) - knl = P2P(ctx, - [lknl, AxisTargetDerivative(0, lknl)], - exclude_self=exclude_self) - - targets = np.random.rand(dimensions, n) - sources = targets if exclude_self else np.random.rand(dimensions, n) + knl = P2P([lknl, AxisTargetDerivative(0, lknl)], exclude_self=exclude_self) + rng = np.random.default_rng(42) + targets = rng.random(size=(dimensions, n)) + sources = targets if exclude_self else rng.random(size=(dimensions, n)) strengths = np.ones(n, dtype=np.float64) extra_kwargs = {} if exclude_self: - extra_kwargs["target_to_source"] = np.arange(n, dtype=np.int32) - - evt, (potential, x_derivative) = knl( - queue, targets, sources, [strengths], - out_host=True, **extra_kwargs) + extra_kwargs["target_to_source"] = ( + actx.from_numpy(np.arange(n, dtype=np.int32))) + + potential, _ = knl( + actx, + actx.from_numpy(targets), + actx.from_numpy(sources), + [actx.from_numpy(strengths)], + **extra_kwargs) + potential = actx.to_numpy(potential) potential_ref = np.empty_like(potential) - targets = targets.T sources = sources.T for itarg in range(n): @@ -98,24 +140,25 @@ def test_p2p(ctx_factory, exclude_self): potential_ref *= 1/(4*np.pi) rel_err = la.norm(potential - potential_ref)/la.norm(potential_ref) - print(rel_err) + logger.info("error: %.12e", rel_err) + assert rel_err < 1e-3 +# }}} + + +# {{{ test_p2e_multiple @pytest.mark.parametrize(("base_knl", "expn_class"), [ (LaplaceKernel(2), LinearPDEConformingVolumeTaylorLocalExpansion), (LaplaceKernel(2), LinearPDEConformingVolumeTaylorMultipoleExpansion), ]) -def test_p2e_multiple(ctx_factory, base_knl, expn_class): - - from sympy.core.cache import clear_cache - clear_cache() - +def test_p2e_multiple( + actx_factory: ArrayContextFactory, + base_knl: ScalarKernel, + expn_class): order = 4 - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) - - np.random.seed(17) + actx = actx_factory() nsources = 100 @@ -125,7 +168,7 @@ def test_p2e_multiple(ctx_factory, base_knl, expn_class): extra_kwargs["k"] = 0.2 * (0.707 + 0.707j) else: extra_kwargs["k"] = 0.2 - if isinstance(base_knl, StokesletKernel): + if isinstance(base_knl, StokesletComponentKernel): extra_kwargs["mu"] = 0.2 source_kernels = [ @@ -138,21 +181,24 @@ def test_p2e_multiple(ctx_factory, base_knl, expn_class): from sumpy import P2EFromSingleBox + rng = np.random.default_rng(14) center = np.array([2, 1, 0][:knl.dim], np.float64) - sources = (0.7*(-0.5+np.random.rand(knl.dim, nsources).astype(np.float64)) - + center[:, np.newaxis]) + sources = actx.from_numpy( + 0.7 * (-0.5 + rng.random(size=(knl.dim, nsources), dtype=np.float64)) + + center[:, np.newaxis]) strengths = [ - np.ones(nsources, dtype=np.float64) * (1/nsources), - np.ones(nsources, dtype=np.float64) * (2/nsources) + actx.from_numpy(np.ones(nsources, dtype=np.float64) * (1/nsources)), + actx.from_numpy(np.ones(nsources, dtype=np.float64) * (2/nsources)) ] - source_boxes = np.array([0], dtype=np.int32) - box_source_starts = np.array([0], dtype=np.int32) - box_source_counts_nonchild = np.array([nsources], dtype=np.int32) + source_boxes = actx.from_numpy(np.array([0], dtype=np.int32)) + box_source_starts = actx.from_numpy(np.array([0], dtype=np.int32)) + box_source_counts_nonchild = ( + actx.from_numpy(np.array([nsources], dtype=np.int32))) alpha = np.linspace(0, 2*np.pi, nsources, np.float64) - dir_vec = np.vstack([np.cos(alpha), np.sin(alpha)]) + dir_vec = actx.from_numpy(np.vstack([np.cos(alpha), np.sin(alpha)])) from sumpy.expansion.local import LocalExpansionBase if issubclass(expn_class, LocalExpansionBase): @@ -162,12 +208,16 @@ def test_p2e_multiple(ctx_factory, base_knl, expn_class): centers = (np.array([0.0, 0.0, 0.0][:knl.dim], dtype=np.float64).reshape(knl.dim, 1) + center[:, np.newaxis]) + centers = actx.from_numpy(centers) rscale = 0.5 # pick something non-1 # apply p2e at the same time - p2e = P2EFromSingleBox(ctx, expn, kernels=source_kernels, strength_usage=[0, 1]) - evt, (mpoles,) = p2e(queue, + p2e = P2EFromSingleBox(expn, + kernels=source_kernels, + strength_usage=[0, 1]) + + mpoles = p2e(actx, source_boxes=source_boxes, box_source_starts=box_source_starts, box_source_counts_nonchild=box_source_counts_nonchild, @@ -177,13 +227,9 @@ def test_p2e_multiple(ctx_factory, base_knl, expn_class): nboxes=1, tgt_base_ibox=0, rscale=rscale, - - #flags="print_hl_cl", - out_host=True, dir_vec=dir_vec, **extra_kwargs) - - actual_result = mpoles + actual_result = actx.to_numpy(mpoles) # apply p2e separately expected_result = np.zeros_like(actual_result) @@ -191,9 +237,11 @@ def test_p2e_multiple(ctx_factory, base_knl, expn_class): extra_source_kwargs = extra_kwargs.copy() if isinstance(source_kernel, DirectionalSourceDerivative): extra_source_kwargs["dir_vec"] = dir_vec - p2e = P2EFromSingleBox(ctx, expn, + + p2e = P2EFromSingleBox(expn, kernels=[source_kernel], strength_usage=[i]) - evt, (mpoles,) = p2e(queue, + + mpoles = p2e(actx, source_boxes=source_boxes, box_source_starts=box_source_starts, box_source_counts_nonchild=box_source_counts_nonchild, @@ -203,14 +251,18 @@ def test_p2e_multiple(ctx_factory, base_knl, expn_class): nboxes=1, tgt_base_ibox=0, rscale=rscale, + **extra_source_kwargs) + mpoles = actx.to_numpy(mpoles) - #flags="print_hl_cl", - out_host=True, **extra_source_kwargs) expected_result += mpoles norm = la.norm(actual_result - expected_result)/la.norm(expected_result) assert norm < 1e-12 +# }}} + + +# {{{ test_p2e2p @pytest.mark.parametrize("order", [4]) @pytest.mark.parametrize(("base_knl", "expn_class"), [ @@ -244,17 +296,13 @@ def test_p2e_multiple(ctx_factory, base_knl, expn_class): False, True ]) -# Sample: test_p2e2p(cl._csc, LaplaceKernel(2), VolumeTaylorLocalExpansion, 4, False) -def test_p2e2p(ctx_factory, base_knl, expn_class, order, with_source_derivative): - #logging.basicConfig(level=logging.INFO) - - from sympy.core.cache import clear_cache - clear_cache() - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) - - np.random.seed(17) +def test_p2e2p( + actx_factory: ArrayContextFactory, + base_knl, + expn_class, + order, + with_source_derivative): + actx = actx_factory() res = 100 nsources = 100 @@ -265,7 +313,7 @@ def test_p2e2p(ctx_factory, base_knl, expn_class, order, with_source_derivative) extra_kwargs["k"] = 0.2 * (0.707 + 0.707j) else: extra_kwargs["k"] = 0.2 - if isinstance(base_knl, StokesletKernel): + if isinstance(base_knl, StokesletComponentKernel): extra_kwargs["mu"] = 0.2 if with_source_derivative: @@ -273,17 +321,6 @@ def test_p2e2p(ctx_factory, base_knl, expn_class, order, with_source_derivative) else: knl = base_knl - target_kernels = [ - knl, - AxisTargetDerivative(0, knl), - ] - expn = expn_class(knl, order=order) - - from sumpy import P2EFromSingleBox, E2PFromSingleBox, P2P - p2e = P2EFromSingleBox(ctx, expn, kernels=[knl]) - e2p = E2PFromSingleBox(ctx, expn, kernels=target_kernels) - p2p = P2P(ctx, target_kernels, exclude_self=False) - from pytools.convergence import EOCRecorder eoc_rec_pot = EOCRecorder() eoc_rec_grad_x = EOCRecorder() @@ -294,149 +331,116 @@ def test_p2e2p(ctx_factory, base_knl, expn_class, order, with_source_derivative) else: h_values = [1/2, 1/3, 1/5] + rng = np.random.default_rng(19) center = np.array([2, 1, 0][:knl.dim], np.float64) - sources = (0.7*(-0.5+np.random.rand(knl.dim, nsources).astype(np.float64)) - + center[:, np.newaxis]) - - strengths = np.ones(nsources, dtype=np.float64) * (1/nsources) + sources = ( + 0.7 * (-0.5 + rng.random((knl.dim, nsources), dtype=np.float64)) + + center[:, np.newaxis]) - source_boxes = np.array([0], dtype=np.int32) - box_source_starts = np.array([0], dtype=np.int32) - box_source_counts_nonchild = np.array([nsources], dtype=np.int32) + strengths = np.ones(nsources, dtype=np.float64) / nsources extra_source_kwargs = extra_kwargs.copy() if isinstance(knl, DirectionalSourceDerivative): alpha = np.linspace(0, 2*np.pi, nsources, np.float64) dir_vec = np.vstack([np.cos(alpha), np.sin(alpha)]) - extra_source_kwargs["dir_vec"] = dir_vec + extra_source_kwargs["dir_vec"] = actx.from_numpy(dir_vec) + + if issubclass(expn_class, LocalExpansionBase): + toy_ctx = t.ToyContext( + knl, + local_expn_class=expn_class, + extra_source_kwargs=extra_source_kwargs, + extra_kernel_kwargs=extra_kwargs, + ) + toy_ctx_grad_x = t.ToyContext( + AxisTargetDerivative(0, knl), + local_expn_class=expn_class, + extra_source_kwargs=extra_source_kwargs, + extra_kernel_kwargs=extra_kwargs, + ) + else: + toy_ctx = t.ToyContext( + knl, + mpole_expn_class=expn_class, + extra_source_kwargs=extra_source_kwargs, + extra_kernel_kwargs=extra_kwargs, + ) + toy_ctx_grad_x = t.ToyContext( + AxisTargetDerivative(0, knl), + mpole_expn_class=expn_class, + extra_source_kwargs=extra_source_kwargs, + extra_kernel_kwargs=extra_kwargs, + ) + + point_sources = t.PointSources(toy_ctx, sources, strengths) + point_sources_grad_x = t.PointSources(toy_ctx_grad_x, sources, strengths) from sumpy.visualization import FieldPlotter for h in h_values: if issubclass(expn_class, LocalExpansionBase): loc_center = np.array([5.5, 0.0, 0.0][:knl.dim]) + center - centers = np.array(loc_center, dtype=np.float64).reshape(knl.dim, 1) fp = FieldPlotter(loc_center, extent=h, npoints=res) else: eval_center = np.array([1/h, 0.0, 0.0][:knl.dim]) + center fp = FieldPlotter(eval_center, extent=0.1, npoints=res) - centers = (np.array([0.0, 0.0, 0.0][:knl.dim], - dtype=np.float64).reshape(knl.dim, 1) - + center[:, np.newaxis]) targets = fp.points rscale = 0.5 # pick something non-1 - # {{{ apply p2e - - evt, (mpoles,) = p2e(queue, - source_boxes=source_boxes, - box_source_starts=box_source_starts, - box_source_counts_nonchild=box_source_counts_nonchild, - centers=centers, - sources=sources, - strengths=(strengths,), - nboxes=1, - tgt_base_ibox=0, - rscale=rscale, - - #flags="print_hl_cl", - out_host=True, **extra_source_kwargs) - - # }}} - - # {{{ apply e2p - - ntargets = targets.shape[-1] - - box_target_starts = np.array([0], dtype=np.int32) - box_target_counts_nonchild = np.array([ntargets], dtype=np.int32) - - evt, (pot, grad_x, ) = e2p( - queue, - src_expansions=mpoles, - src_base_ibox=0, - target_boxes=source_boxes, - box_target_starts=box_target_starts, - box_target_counts_nonchild=box_target_counts_nonchild, - centers=centers, - targets=targets, - rscale=rscale, - - #flags="print_hl_cl", - out_host=True, **extra_kwargs) - - # }}} - - # {{{ compute (direct) reference solution - - evt, (pot_direct, grad_x_direct, ) = p2p( - queue, - targets, sources, (strengths,), - out_host=True, - **extra_source_kwargs) - - err_pot = la.norm((pot - pot_direct)/res**2) - err_grad_x = la.norm((grad_x - grad_x_direct)/res**2) - - if 1: - err_pot = err_pot / la.norm((pot_direct)/res**2) - err_grad_x = err_grad_x / la.norm((grad_x_direct)/res**2) - - if 0: - import matplotlib.pyplot as pt - from matplotlib.colors import Normalize - - pt.subplot(131) - im = fp.show_scalar_in_matplotlib(pot.real) - im.set_norm(Normalize(vmin=-0.1, vmax=0.1)) - - pt.subplot(132) - im = fp.show_scalar_in_matplotlib(pot_direct.real) - im.set_norm(Normalize(vmin=-0.1, vmax=0.1)) - pt.colorbar() - - pt.subplot(133) - im = fp.show_scalar_in_matplotlib(np.log10(1e-15+np.abs(pot-pot_direct))) - im.set_norm(Normalize(vmin=-6, vmax=1)) - - pt.colorbar() - pt.show() - - # }}} - + if issubclass(expn_class, LocalExpansionBase): + expn = t.local_expand(actx, point_sources, loc_center, + order=order, rscale=rscale) + expn_grad_x = t.local_expand(actx, point_sources_grad_x, loc_center, + order=order, rscale=rscale) + else: + expn = t.multipole_expand(actx, point_sources, center, + order=order, rscale=rscale) + expn_grad_x = t.multipole_expand(actx, point_sources_grad_x, center, + order=order, rscale=rscale) + + pot = expn.eval(actx, targets) + pot_direct = point_sources.eval(actx, targets) + grad_x = expn_grad_x.eval(actx, targets) + grad_x_direct = point_sources_grad_x.eval(actx, targets) + + # In the multipole expansion testing, ||pot_direct|| and + # ||grad_x_direct|| vary with h, so absolute error measure + # is more accurate for estimating convergence order. + err_pot = la.norm(pot - pot_direct) + err_grad_x = la.norm(grad_x - grad_x_direct) eoc_rec_pot.add_data_point(h, err_pot) eoc_rec_grad_x.add_data_point(h, err_grad_x) - print(expn_class, knl, order) - print("POTENTIAL:") - print(eoc_rec_pot) - print("X TARGET DERIVATIVE:") - print(eoc_rec_grad_x) + logger.info("expn_cls %s knl %s order %d", expn_class, knl, order) + logger.info("POTENTIAL:") + logger.info("%s", eoc_rec_pot) + logger.info("X TARGET DERIVATIVE:") + logger.info("%s", eoc_rec_grad_x) tgt_order = order + 1 if issubclass(expn_class, LocalExpansionBase): tgt_order_grad = tgt_order - 1 - slack = 0.7 - grad_slack = 0.5 + slack = 0.5 + grad_slack = 0.6 else: tgt_order_grad = tgt_order + 1 - slack = 0.5 - grad_slack = 1 - - if order <= 2: - slack += 1 - grad_slack += 1 - - if isinstance(knl, DirectionalSourceDerivative): - slack += 1 - grad_slack += 2 - - if isinstance(base_knl, DirectionalSourceDerivative): - slack += 1 - grad_slack += 2 + grad_slack = 0.6 + + # For the 2D biharmonic kernel K(r) = r^2 log(r), + # the expected convergence order for multipole expansions is p-1. + # Adding source derivatives does not change the convergence order, + # since the differentiation is applied to the truncated Taylor expansion + # of the kernel. + if (isinstance(base_knl, DirectionalSourceDerivative) + and isinstance(base_knl.inner_kernel, BiharmonicKernel)): + if not issubclass(expn_class, LocalExpansionBase): + tgt_order = order - 1 + tgt_order_grad = tgt_order + 1 + slack = 0.7 + grad_slack = 0.7 if isinstance(base_knl, HelmholtzKernel): if base_knl.allow_evanescent: @@ -450,52 +454,77 @@ def test_p2e2p(ctx_factory, base_knl, expn_class, order, with_source_derivative) assert eoc_rec_pot.order_estimate() > tgt_order - slack assert eoc_rec_grad_x.order_estimate() > tgt_order_grad - grad_slack +# }}} -@pytest.mark.parametrize("knl, local_expn_class, mpole_expn_class", [ - (LaplaceKernel(2), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion), + +# {{{ test_translations + +@pytest.mark.parametrize("knl, local_expn_class, mpole_expn_class, use_fft", [ + (LaplaceKernel(2), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion, + False), (LaplaceKernel(2), LinearPDEConformingVolumeTaylorLocalExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion), - (LaplaceKernel(3), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion), + LinearPDEConformingVolumeTaylorMultipoleExpansion, False), + (LaplaceKernel(2), LinearPDEConformingVolumeTaylorLocalExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion, True), + (LaplaceKernel(3), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion, + False), (LaplaceKernel(3), LinearPDEConformingVolumeTaylorLocalExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion), - (HelmholtzKernel(2), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion), + LinearPDEConformingVolumeTaylorMultipoleExpansion, False), + (LaplaceKernel(3), LinearPDEConformingVolumeTaylorLocalExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion, True), + (HelmholtzKernel(2), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion, + False), (HelmholtzKernel(2), LinearPDEConformingVolumeTaylorLocalExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion), - (HelmholtzKernel(3), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion), + LinearPDEConformingVolumeTaylorMultipoleExpansion, False), + (HelmholtzKernel(3), VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion, + False), (HelmholtzKernel(3), LinearPDEConformingVolumeTaylorLocalExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion), - (HelmholtzKernel(2), H2DLocalExpansion, H2DMultipoleExpansion), - (StokesletKernel(2, 0, 0), VolumeTaylorLocalExpansion, - VolumeTaylorMultipoleExpansion), - (StokesletKernel(2, 0, 0), LinearPDEConformingVolumeTaylorLocalExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion), + LinearPDEConformingVolumeTaylorMultipoleExpansion, False), + (HelmholtzKernel(2), H2DLocalExpansion, H2DMultipoleExpansion, False), + (StokesletComponentKernel(2, 0, 0), VolumeTaylorLocalExpansion, + VolumeTaylorMultipoleExpansion, False), + (StokesletComponentKernel(2, 0, 0), LinearPDEConformingVolumeTaylorLocalExpansion, + LinearPDEConformingVolumeTaylorMultipoleExpansion, False), + (BrinkmanletComponentKernel(2, 0, 0), VolumeTaylorLocalExpansion, + VolumeTaylorMultipoleExpansion, False), + (BrinkmanStressComponentKernel(2, 0, 0, 0), VolumeTaylorLocalExpansion, + VolumeTaylorMultipoleExpansion, False), ]) -def test_translations(ctx_factory, knl, local_expn_class, mpole_expn_class): - logging.basicConfig(level=logging.INFO) - - from sympy.core.cache import clear_cache - clear_cache() - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) +def test_translations( + actx_factory: ArrayContextFactory, + knl, + local_expn_class, + mpole_expn_class, + use_fft, + visualize=False): + if visualize: + logging.basicConfig(level=logging.INFO) - np.random.seed(17) + actx = actx_factory() res = 20 nsources = 15 - target_kernels = [knl] - extra_kwargs = {} if isinstance(knl, HelmholtzKernel): extra_kwargs["k"] = 0.05 - if isinstance(knl, StokesletKernel): + elif isinstance(knl, YukawaKernel): + extra_kwargs["lam"] = 0.05 + elif isinstance(knl, (StokesletComponentKernel, StressletComponentKernel)): + extra_kwargs["mu"] = 0.05 + elif isinstance(knl, ElasticityComponentKernel): extra_kwargs["mu"] = 0.05 + extra_kwargs["nu"] = 0.1 + elif isinstance(knl, (BrinkmanletComponentKernel, BrinkmanStressComponentKernel)): + extra_kwargs["mu"] = 0.1 + extra_kwargs["k"] = 0.1 # Just to make sure things also work away from the origin + rng = np.random.default_rng(18) origin = np.array([2, 1, 0][:knl.dim], np.float64) - sources = (0.7*(-0.5+np.random.rand(knl.dim, nsources).astype(np.float64)) - + origin[:, np.newaxis]) + sources = ( + 0.7 * (-0.5 + rng.random((knl.dim, nsources), dtype=np.float64)) + + origin[:, np.newaxis]) strengths = np.ones(nsources, dtype=np.float64) * (1/nsources) pconv_verifier_p2m2p = PConvergenceVerifier() @@ -506,6 +535,8 @@ def test_translations(ctx_factory, knl, local_expn_class, mpole_expn_class): from sumpy.visualization import FieldPlotter eval_offset = np.array([5.5, 0.0, 0][:knl.dim]) + fp = FieldPlotter(eval_offset + origin, extent=0.3, npoints=res) + targets = fp.points centers = (np.array( [ @@ -526,200 +557,64 @@ def test_translations(ctx_factory, knl, local_expn_class, mpole_expn_class): del eval_offset if knl.dim == 2: - orders = [2, 3, 4] + if isinstance(knl, BrinkmanStressComponentKernel): + orders = [3, 4, 5] + else: + orders = [2, 3, 4] else: orders = [3, 4, 5] - nboxes = centers.shape[-1] - - def eval_at(e2p, source_box_nr, rscale): - e2p_target_boxes = np.array([source_box_nr], dtype=np.int32) - - # These are indexed by global box numbers. - e2p_box_target_starts = np.array([0, 0, 0, 0], dtype=np.int32) - e2p_box_target_counts_nonchild = np.array([0, 0, 0, 0], - dtype=np.int32) - e2p_box_target_counts_nonchild[source_box_nr] = ntargets - - evt, (pot,) = e2p( - queue, - - src_expansions=mpoles, - src_base_ibox=0, - - target_boxes=e2p_target_boxes, - box_target_starts=e2p_box_target_starts, - box_target_counts_nonchild=e2p_box_target_counts_nonchild, - centers=centers, - targets=targets, + if use_fft: + m2l_factory = FFTM2LTranslationClassFactory() + else: + m2l_factory = NonFFTM2LTranslationClassFactory() + m2l_translation = m2l_factory.get_m2l_translation_class(knl, local_expn_class)() - rscale=rscale, + toy_ctx = t.ToyContext( + kernel=knl, + local_expn_class=partial(local_expn_class, + m2l_translation_override=m2l_translation), + mpole_expn_class=mpole_expn_class, + extra_kernel_kwargs=extra_kwargs, + ) - out_host=True, **extra_kwargs - ) + p = t.PointSources(toy_ctx, sources, weights=strengths) + p2p = p.eval(actx, targets) - return pot + m1_rscale = 0.5 + m2_rscale = 0.25 + l1_rscale = 0.5 + l2_rscale = 0.25 for order in orders: - m_expn = mpole_expn_class(knl, order=order) - l_expn = local_expn_class(knl, order=order) - - from sumpy import P2EFromSingleBox, E2PFromSingleBox, P2P, E2EFromCSR - p2m = P2EFromSingleBox(ctx, m_expn) - m2m = E2EFromCSR(ctx, m_expn, m_expn) - m2p = E2PFromSingleBox(ctx, m_expn, target_kernels) - m2l = E2EFromCSR(ctx, m_expn, l_expn) - l2l = E2EFromCSR(ctx, l_expn, l_expn) - l2p = E2PFromSingleBox(ctx, l_expn, target_kernels) - p2p = P2P(ctx, target_kernels, exclude_self=False) - - fp = FieldPlotter(centers[:, -1], extent=0.3, npoints=res) - targets = fp.points - - # {{{ compute (direct) reference solution - - evt, (pot_direct,) = p2p( - queue, - targets, sources, (strengths,), - out_host=True, **extra_kwargs) - - # }}} - - m1_rscale = 0.5 - m2_rscale = 0.25 - l1_rscale = 0.5 - l2_rscale = 0.25 - - # {{{ apply P2M - - p2m_source_boxes = np.array([0], dtype=np.int32) - - # These are indexed by global box numbers. - p2m_box_source_starts = np.array([0, 0, 0, 0], dtype=np.int32) - p2m_box_source_counts_nonchild = np.array([nsources, 0, 0, 0], - dtype=np.int32) - - evt, (mpoles,) = p2m(queue, - source_boxes=p2m_source_boxes, - box_source_starts=p2m_box_source_starts, - box_source_counts_nonchild=p2m_box_source_counts_nonchild, - centers=centers, - sources=sources, - strengths=(strengths,), - nboxes=nboxes, - rscale=m1_rscale, - - tgt_base_ibox=0, - - #flags="print_hl_wrapper", - out_host=True, **extra_kwargs) - - # }}} - - ntargets = targets.shape[-1] - - pot = eval_at(m2p, 0, m1_rscale) - - err = la.norm((pot - pot_direct)/res**2) - err = err / (la.norm(pot_direct) / res**2) + logger.info("Centers: %s", centers[:, 0].shape) + p2m = t.multipole_expand(actx, p, centers[:, 0], + order=order, rscale=m1_rscale) + p2m2p = p2m.eval(actx, targets) + err = la.norm((p2m2p - p2p) / res**2) + err = err / (la.norm(p2p) / res**2) pconv_verifier_p2m2p.add_data_point(order, err) - # {{{ apply M2M - - m2m_target_boxes = np.array([1], dtype=np.int32) - m2m_src_box_starts = np.array([0, 1], dtype=np.int32) - m2m_src_box_lists = np.array([0], dtype=np.int32) - - evt, (mpoles,) = m2m(queue, - src_expansions=mpoles, - src_base_ibox=0, - tgt_base_ibox=0, - ntgt_level_boxes=mpoles.shape[0], - - target_boxes=m2m_target_boxes, - - src_box_starts=m2m_src_box_starts, - src_box_lists=m2m_src_box_lists, - centers=centers, - - src_rscale=m1_rscale, - tgt_rscale=m2_rscale, - - #flags="print_hl_cl", - out_host=True, **extra_kwargs) - - # }}} - - pot = eval_at(m2p, 1, m2_rscale) - - err = la.norm((pot - pot_direct)/res**2) - err = err / (la.norm(pot_direct) / res**2) - + p2m2m = t.multipole_expand(actx, p2m, centers[:, 1], + order=order, rscale=m2_rscale) + p2m2m2p = p2m2m.eval(actx, targets) + err = la.norm((p2m2m2p - p2p)/res**2) + err = err / (la.norm(p2p) / res**2) pconv_verifier_p2m2m2p.add_data_point(order, err) - # {{{ apply M2L - - m2l_target_boxes = np.array([2], dtype=np.int32) - m2l_src_box_starts = np.array([0, 1], dtype=np.int32) - m2l_src_box_lists = np.array([1], dtype=np.int32) - - evt, (mpoles,) = m2l(queue, - src_expansions=mpoles, - src_base_ibox=0, - tgt_base_ibox=0, - ntgt_level_boxes=mpoles.shape[0], - - target_boxes=m2l_target_boxes, - src_box_starts=m2l_src_box_starts, - src_box_lists=m2l_src_box_lists, - centers=centers, - - src_rscale=m2_rscale, - tgt_rscale=l1_rscale, - - #flags="print_hl_cl", - out_host=True, **extra_kwargs) - - # }}} - - pot = eval_at(l2p, 2, l1_rscale) - - err = la.norm((pot - pot_direct)/res**2) - err = err / (la.norm(pot_direct) / res**2) - + p2m2m2l = t.local_expand(actx, p2m2m, centers[:, 2], + order=order, rscale=l1_rscale) + p2m2m2l2p = p2m2m2l.eval(actx, targets) + err = la.norm((p2m2m2l2p - p2p)/res**2) + err = err / (la.norm(p2p) / res**2) pconv_verifier_p2m2m2l2p.add_data_point(order, err) - # {{{ apply L2L - - l2l_target_boxes = np.array([3], dtype=np.int32) - l2l_src_box_starts = np.array([0, 1], dtype=np.int32) - l2l_src_box_lists = np.array([2], dtype=np.int32) - - evt, (mpoles,) = l2l(queue, - src_expansions=mpoles, - src_base_ibox=0, - tgt_base_ibox=0, - ntgt_level_boxes=mpoles.shape[0], - - target_boxes=l2l_target_boxes, - src_box_starts=l2l_src_box_starts, - src_box_lists=l2l_src_box_lists, - centers=centers, - - src_rscale=l1_rscale, - tgt_rscale=l2_rscale, - - #flags="print_hl_wrapper", - out_host=True, **extra_kwargs) - - # }}} - - pot = eval_at(l2p, 3, l2_rscale) - - err = la.norm((pot - pot_direct)/res**2) - err = err / (la.norm(pot_direct) / res**2) - + p2m2m2l2l = t.local_expand(actx, p2m2m2l, centers[:, 3], + order=order, rscale=l2_rscale) + p2m2m2l2l2p = p2m2m2l2l.eval(actx, targets) + err = la.norm((p2m2m2l2l2p - p2p)/res**2) + err = err / (la.norm(p2p) / res**2) pconv_verifier_full.add_data_point(order, err) for name, verifier in [ @@ -728,13 +623,18 @@ def eval_at(e2p, source_box_nr, rscale): ("p2m2m2l2p", pconv_verifier_p2m2m2l2p), ("full", pconv_verifier_full), ]: - print(30*"-") - print(name) - print(30*"-") - print(verifier) - print(30*"-") + logger.info(30*"-") + logger.info("name: %s", name) + logger.info(30*"-") + logger.info("result: %s", verifier) + logger.info(30*"-") + verifier() +# }}} + + +# {{{ test_m2m_and_l2l_exprs_simpler @pytest.mark.parametrize("order", [4]) @pytest.mark.parametrize(("base_knl", "local_expn_class", "mpole_expn_class"), [ @@ -746,19 +646,13 @@ def eval_at(e2p, source_box_nr, rscale): ]) def test_m2m_and_l2l_exprs_simpler(base_knl, local_expn_class, mpole_expn_class, order, with_source_derivative): - - from sympy.core.cache import clear_cache - clear_cache() - - np.random.seed(17) - extra_kwargs = {} if isinstance(base_knl, HelmholtzKernel): if base_knl.allow_evanescent: extra_kwargs["k"] = 0.2 * (0.707 + 0.707j) else: extra_kwargs["k"] = 0.2 - if isinstance(base_knl, StokesletKernel): + if isinstance(base_knl, StokesletComponentKernel): extra_kwargs["mu"] = 0.2 if with_source_derivative: @@ -769,9 +663,8 @@ def test_m2m_and_l2l_exprs_simpler(base_knl, local_expn_class, mpole_expn_class, mpole_expn = mpole_expn_class(knl, order=order) local_expn = local_expn_class(knl, order=order) - from sumpy.symbolic import make_sym_vector, Symbol, USE_SYMENGINE - dvec = make_sym_vector("d", knl.dim) - src_coeff_exprs = [Symbol(f"src_coeff{i}") for i in range(len(mpole_expn))] + dvec = sym.make_sym_vector("d", knl.dim) + src_coeff_exprs = [sym.Symbol(f"src_coeff{i}") for i in range(len(mpole_expn))] src_rscale = 3 tgt_rscale = 2 @@ -781,30 +674,23 @@ def test_m2m_and_l2l_exprs_simpler(base_knl, local_expn_class, mpole_expn_class, slower_m2m = mpole_expn.translate_from(mpole_expn, src_coeff_exprs, src_rscale, dvec, tgt_rscale, _fast_version=False) - def _check_equal(expr1, expr2): - if USE_SYMENGINE: - return float((expr1 - expr2).expand()) == 0.0 - else: - # with sympy we are using UnevaluatedExpr and expand doesn't expand it - # Running doit replaces UnevaluatedExpr with evaluated exprs - return float((expr1 - expr2).doit().expand()) == 0.0 - - for expr1, expr2 in zip(faster_m2m, slower_m2m): - assert _check_equal(expr1, expr2) + for expr1, expr2 in zip(faster_m2m, slower_m2m, strict=True): + assert float(sym.doit(expr1 - expr2).expand()) == 0.0 # ruff:ignore[float-equality-comparison] faster_l2l = local_expn.translate_from(local_expn, src_coeff_exprs, src_rscale, dvec, tgt_rscale) slower_l2l = local_expn.translate_from(local_expn, src_coeff_exprs, src_rscale, dvec, tgt_rscale, _fast_version=False) - for expr1, expr2 in zip(faster_l2l, slower_l2l): - assert _check_equal(expr1, expr2) + for expr1, expr2 in zip(faster_l2l, slower_l2l, strict=True): + assert float(sym.doit(expr1 - expr2).expand()) == 0.0 # ruff:ignore[float-equality-comparison] + +# }}} # {{{ test toeplitz def _m2l_translate_simple(tgt_expansion, src_expansion, src_coeff_exprs, src_rscale, dvec, tgt_rscale): - if not tgt_expansion.use_rscale: src_rscale = 1 tgt_rscale = 1 @@ -828,39 +714,49 @@ def _m2l_translate_simple(tgt_expansion, src_expansion, src_coeff_exprs, src_rsc local_result = [] for coeff, term in zip( src_coeff_exprs, - src_expansion.get_coefficient_identifiers()): + src_expansion.get_coefficient_identifiers(), strict=True): kernel_deriv = taker.diff(add_mi(deriv, term)) / src_rscale**sum(deriv) local_result.append( coeff * kernel_deriv * tgt_rscale**sum(deriv)) result.append(sym.Add(*local_result)) + return result def test_m2l_toeplitz(): dim = 3 knl = LaplaceKernel(dim) + local_expn_class = LinearPDEConformingVolumeTaylorLocalExpansion mpole_expn_class = LinearPDEConformingVolumeTaylorMultipoleExpansion + m2l_factory = NonFFTM2LTranslationClassFactory() + m2l_translation = m2l_factory.get_m2l_translation_class(knl, local_expn_class)() - local_expn = local_expn_class(knl, order=5) + local_expn = local_expn_class(knl, order=5, + m2l_translation_override=m2l_translation) mpole_expn = mpole_expn_class(knl, order=5) dvec = sym.make_sym_vector("d", dim) - src_coeff_exprs = list(1 + np.random.randn(len(mpole_expn))) + + rng = np.random.default_rng(44) + src_coeff_exprs = list(1 + rng.standard_normal(len(mpole_expn))) src_rscale = 2.0 tgt_rscale = 1.0 - expected_output = _m2l_translate_simple(local_expn, mpole_expn, src_coeff_exprs, - src_rscale, dvec, tgt_rscale) - actual_output = local_expn.translate_from(mpole_expn, src_coeff_exprs, - src_rscale, dvec, tgt_rscale, sac=None) + expected_output = _m2l_translate_simple( + local_expn, mpole_expn, src_coeff_exprs, + src_rscale, dvec, tgt_rscale) + actual_output = local_expn.translate_from( + mpole_expn, src_coeff_exprs, + src_rscale, dvec, tgt_rscale, sac=None) - replace_dict = {d: np.random.rand(1)[0] for d in dvec} - for sym_a, sym_b in zip(expected_output, actual_output): + replace_dict = {d: rng.random() for d in dvec} + for sym_a, sym_b in zip(expected_output, actual_output, strict=True): num_a = sym_a.xreplace(replace_dict) num_b = sym_b.xreplace(replace_dict) + assert abs(num_a - num_b)/abs(num_a) < 1e-10 # }}} @@ -870,10 +766,11 @@ def test_m2l_toeplitz(): @pytest.mark.parametrize("dim", [2, 3]) @pytest.mark.parametrize("order", [2, 4, 6]) -def test_m2m_compressed_error_helmholtz(ctx_factory, dim, order): - import sumpy.toys as t +def test_m2m_compressed_error_helmholtz(actx_factory: ArrayContextFactory, dim, order): + from sumpy import toys + + actx = actx_factory() - ctx = ctx_factory() knl = HelmholtzKernel(dim) extra_kernel_kwargs = {"k": 5} @@ -907,48 +804,155 @@ def test_m2m_compressed_error_helmholtz(ctx_factory, dim, order): furthest_source = np.max(np.abs(sources - mpole_center)) m2m_vals = [0, 0] for i, (mpole_expn_class, local_expn_class) in \ - enumerate(zip(mpole_expn_classes, local_expn_classes)): - tctx = t.ToyContext( - ctx, + enumerate(zip(mpole_expn_classes, local_expn_classes, strict=True)): + tctx = toys.ToyContext( knl, extra_kernel_kwargs=extra_kernel_kwargs, local_expn_class=local_expn_class, mpole_expn_class=mpole_expn_class, ) - pt_src = t.PointSources( + pt_src = toys.PointSources( tctx, sources, np.ones(sources.shape[-1]) ) - mexp = t.multipole_expand(pt_src, + mexp = toys.multipole_expand(actx, pt_src, center=mpole_center.reshape(dim), order=order, rscale=h) - mexp2 = t.multipole_expand(mexp, + mexp2 = toys.multipole_expand(actx, mexp, center=second_center.reshape(dim), order=order, rscale=h) - m2m_vals[i] = mexp2.eval(targets) + m2m_vals[i] = mexp2.eval(actx, targets) err = np.linalg.norm(m2m_vals[1] - m2m_vals[0]) \ / np.linalg.norm(m2m_vals[1]) eoc_rec.add_data_point(furthest_source, err) - print(eoc_rec) + logger.info("\n%s", eoc_rec) assert eoc_rec.order_estimate() >= order + 1 # }}} +# {{{ test_jump + +@pytest.mark.parametrize(("kernel_cls", "kernel_kwargs"), [ + (LaplaceKernel, {}), + (HelmholtzKernel, {"k": 1}), + (YukawaKernel, {"lam": 1}) + ]) +@pytest.mark.parametrize("dim", [2, 3]) +def test_jump( + actx_factory: ArrayContextFactory, + kernel_cls: type[ScalarKernel], + kernel_kwargs: dict[str, float], + dim: int, + order: int = 3, + ): + # x-ref https://github.com/inducer/sumpy/pull/224 + # fails for Yukawa on then-main + + actx = actx_factory() + if not isinstance(actx, PyOpenCLArrayContext): + pytest.skip() + + if dim == 2: + geo = make_ellipsoid(npoints=1600) + qbx_radius = 0.02 + tol = 3e-7 + + elif dim == 3: + geo = make_torus() + qbx_radius = 0.25 + tol = 1e-3 + + else: + raise ValueError(f"unexpected dim: {dim}") + + center_dist = qbx_radius*np.ones(2) + center_sides = np.array([-1, 1], dtype=np.float64) + + targets = geo.nodes[:, 0][:, np.newaxis] + np.zeros(2) + centers = (geo.nodes[:, 0][:, np.newaxis] + + center_sides * center_dist*geo.normals[:, 0][:, np.newaxis]) + + from sumpy.kernel import DirectionalSourceDerivative + kernel = kernel_cls(dim) + dlp_knl = DirectionalSourceDerivative(kernel) + + from sumpy.qbx import LayerPotential + lpot = LayerPotential( + expansion=LineTaylorLocalExpansion(kernel, order=order), + source_kernels=(dlp_knl,), + target_kernels=(kernel,), + value_dtypes=np.complex128,) + + y, = lpot(actx, + actx.from_numpy(targets), + actx.from_numpy(geo.nodes), + actx.from_numpy(centers), + [actx.from_numpy(geo.weights * geo.area_elements)], + expansion_radii=actx.from_numpy(center_dist), + src_derivative_dir=actx.from_numpy(geo.normals), + **kernel_kwargs, + ) + + inside, outside = actx.to_numpy(y) + err = abs((inside-outside) - -1) + assert err < tol, err + +# }}} + + +# {{{ test_pickle + +@memoize_on_first_arg +def get_kernel_name_for_test(knl: ScalarKernel) -> Callable[[str], str]: + return lambda prefix: f"{prefix}: {type(knl).__name__}" + + +@pytest.mark.parametrize("knl", [ + BiharmonicKernel(2), + BrinkmanletComponentKernel(2, 0, 1), + BrinkmanletComponentKernel(3, 0, 0, + viscosity_mu_name="viscosity", + darcy_impermeability_name="kappa"), + BrinkmanStressComponentKernel(2, 0, 1, 0), + BrinkmanStressComponentKernel(3, 0, 0, 1, + viscosity_mu_name="viscosity", + darcy_impermeability_name="kappa"), + ElasticityComponentKernel(2, 0, 0), + HelmholtzKernel(3, helmholtz_k_name="kay"), + LaplaceKernel(3), + LineOfCompressionKernel(), + OneKernel(2), + StokesletComponentKernel(2, 0, 0), + StressletComponentKernel(2, 0, 0, 0), + YukawaKernel(2, yukawa_lambda_name="lambda"), +]) +def test_pickle(knl: ScalarKernel) -> None: + import pickle + + result = pickle.dumps(knl) + assert pickle.loads(result) == knl + + _ = get_kernel_name_for_test(knl) + result = pickle.dumps(knl) + assert pickle.loads(result) == knl + +# }}} + + # You can test individual routines by typing -# $ python test_kernels.py 'test_p2p(cl.create_some_context)' +# $ python test_kernels.py 'test_p2p(_acf, True)' if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) else: - from pytest import main - main([__file__]) + pytest.main([__file__]) # vim: fdm=marker diff --git a/sumpy/test/test_l2l_coeffs.py b/sumpy/test/test_l2l_coeffs.py new file mode 100644 index 000000000..0e78b2737 --- /dev/null +++ b/sumpy/test/test_l2l_coeffs.py @@ -0,0 +1,213 @@ +from __future__ import annotations + + +__copyright__ = """ +Copyright (C) 2026 Shawn/Chaoqi Lin +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import math +import sys +from typing import TYPE_CHECKING + +import numpy as np +import pytest + +from arraycontext import ( + pytest_generate_tests_for_array_contexts, +) + +import sumpy.toys as t +from .coeff_test_tools import NumericMatVecOperator, get_repl_dict, to_scalar +from sumpy.array_context import ( # ruff:ignore[unused-import] + PytestPyOpenCLArrayContextFactory, + _acf, +) +from sumpy.expansion.local import ( + LinearPDEConformingVolumeTaylorLocalExpansion, + VolumeTaylorLocalExpansion, +) +from sumpy.kernel import ( + BiharmonicKernel, + HelmholtzKernel, + LaplaceKernel, + ScalarKernel, + YukawaKernel, +) +from sumpy.tools import add_mi, build_matrix, mi_factorial, mi_power + + +if TYPE_CHECKING: + from collections.abc import Mapping + + from arraycontext import ArrayContextFactory + + +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) + + +@pytest.mark.parametrize("knl,extra_kwargs", [ + (LaplaceKernel(2), {}), + (YukawaKernel(2), {"lam": 0.1}), + (HelmholtzKernel(2), {"k": 0.5}), + (BiharmonicKernel(2), {}), +]) +def test_l2l_coefficient_differences( + actx_factory: ArrayContextFactory, + knl: ScalarKernel, + extra_kwargs: Mapping[str, float], + verbose: bool = True, + ): + """ + Tests that the expression for the difference between compressed and uncompressed + translation in the compressed-expansions paper matches implemented reality. + """ + order = 7 + dim = 2 + repl_dict = get_repl_dict(knl, extra_kwargs) + + # Setup sources and centers + source = np.array([[5.0], [5.0]]) + c1 = np.array([0.0, 0.0]) + c2 = c1 + np.array([-0.5, 1.0]) + strength = np.array([1.0]) + + actx = actx_factory() + toy_ctx = t.ToyContext( + knl, + local_expn_class=LinearPDEConformingVolumeTaylorLocalExpansion, + extra_kernel_kwargs=extra_kwargs + ) + toy_ctx_full = t.ToyContext( + knl, + local_expn_class=VolumeTaylorLocalExpansion, + extra_kernel_kwargs=extra_kwargs + ) + + # Compute expansions + p = t.PointSources(toy_ctx, source, weights=strength) + p_full = t.PointSources(toy_ctx_full, source, weights=strength) + + p2l = t.local_expand(actx, p, c1, order=order, rscale=1.0) + p2l2l = t.local_expand(actx, p2l, c2, order=order, rscale=1.0) + p2l_full = t.local_expand(actx, p_full, c1, order=order, rscale=1.0) + p2l2l_full = t.local_expand(actx, p2l_full, c2, order=order, rscale=1.0) + + # Build matrix M + p2l2l_expn = LinearPDEConformingVolumeTaylorLocalExpansion(knl, order) + wrangler = p2l2l_expn.expansion_terms_wrangler + M_symbolic = wrangler.get_projection_matrix(rscale=1.0) # ruff:ignore[non-lowercase-variable-in-function] + numeric_op = NumericMatVecOperator(M_symbolic, repl_dict) + M = build_matrix(numeric_op, dtype=np.complex128) # ruff:ignore[non-lowercase-variable-in-function] + + # Get compressed coefficients + mu_c_symbolic = wrangler.get_full_kernel_derivatives_from_stored( + p2l2l.coeffs, rscale=1.0 + ) + mu_c = [] + for coeff in mu_c_symbolic: + if hasattr(coeff, "xreplace"): + mu_c.append(to_scalar(coeff.xreplace(repl_dict))) + else: + mu_c.append(to_scalar(coeff)) + + # Get identifiers + stored_identifiers = p2l2l_expn.get_coefficient_identifiers() + full_identifiers = p2l2l_expn.get_full_coefficient_identifiers() + lexpn = VolumeTaylorLocalExpansion(knl, order) + lexpn_idx = lexpn.get_full_coefficient_identifiers() + + h = c2 - c1 + global_const = to_scalar(knl.get_global_scaling_const()) + + if verbose: + print(f'\n{"="*104}') + print(f"L2L Verification: {type(knl).__name__} (order={order})") + print(f'{"="*104}') + print(f"c1 = {c1}, c2 = {c2}, h = {h}") + print() + print(f"{'i':>3s} | {'ν(i)':>15s} | {'|ν|':4s} | " # ruff:ignore[ambiguous-unicode-character-string] + f"{'formula':>31s} | {'direct':>31s} | {'abs err':>10s}") + print("-" * 104) + + max_abs_error = 0.0 + + for i, nu_i in enumerate(full_identifiers): + i_card = sum(nu_i) + + # Compute error by formula + error = 0.0 + 0.0j + for k, nu_jk in enumerate(stored_identifiers): + jk_card = sum(nu_jk) + if jk_card >= i_card: + continue + + start_idx = math.comb(order - i_card + dim, dim) + end_idx = math.comb(order - jk_card + dim, dim) + + for q_idx in range(start_idx, end_idx): + nu_q = full_identifiers[q_idx] + nu_sum = add_mi(nu_q, nu_jk) + if nu_sum not in full_identifiers: + continue + + deriv_idx = full_identifiers.index(nu_sum) + gamma_deriv = to_scalar(p2l_full.coeffs[deriv_idx]) + h_pow = float(mi_power(h, nu_q)) + fact_nu_q = mi_factorial(nu_q) + + error += -M[i, k] * gamma_deriv * h_pow / fact_nu_q + + error /= mi_factorial(nu_i) + error *= global_const + + # Compute direct difference + true_i_idx = lexpn_idx.index(nu_i) + mu_full = to_scalar(p2l2l_full.coeffs[true_i_idx]) + direct_diff = (mu_full - mu_c[i]) / mi_factorial(nu_i) + direct_diff *= global_const + + # Compute errors + abs_err = abs(error - direct_diff) + max_abs_error = max(max_abs_error, abs_err) + + if verbose: + print(f"{i:3d} | {nu_i!s:>15s} | {i_card:4d} | " + f"{error.real: .8e}{error.imag:+.8e}j | " + f"{direct_diff.real: .8e}{direct_diff.imag:+.8e}j | " + f"{abs_err:9.2e}") + + if verbose: + print(f"\nMaximum absolute error: {max_abs_error:.2e}") + + assert max_abs_error < 1e-10, \ + f"{type(knl).__name__}: error {max_abs_error:.2e}" + + +if __name__ == "__main__": + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + pytest.main([__file__]) diff --git a/sumpy/test/test_m2m_coeffs.py b/sumpy/test/test_m2m_coeffs.py new file mode 100644 index 000000000..d04c41f75 --- /dev/null +++ b/sumpy/test/test_m2m_coeffs.py @@ -0,0 +1,239 @@ +from __future__ import annotations + + +__copyright__ = """ +Copyright (C) 2026 Shawn/Chaoqi Lin +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import math +import sys +from typing import TYPE_CHECKING + +import numpy as np +import pytest + +from arraycontext import ( + pytest_generate_tests_for_array_contexts, +) + +import sumpy.toys as t +from .coeff_test_tools import NumericMatVecOperator, get_repl_dict, to_scalar +from sumpy.array_context import ( # ruff:ignore[unused-import] + PytestPyOpenCLArrayContextFactory, + _acf, +) +from sumpy.expansion.local import ( + LinearPDEConformingVolumeTaylorLocalExpansion, +) +from sumpy.expansion.multipole import ( + LinearPDEConformingVolumeTaylorMultipoleExpansion, + VolumeTaylorMultipoleExpansion, +) +from sumpy.kernel import ( + BiharmonicKernel, + HelmholtzKernel, + LaplaceKernel, + ScalarKernel, + YukawaKernel, +) +from sumpy.tools import add_mi, build_matrix, mi_factorial, mi_power + + +if TYPE_CHECKING: + from collections.abc import Mapping + + from arraycontext import ArrayContextFactory + + +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) + + +@pytest.mark.parametrize("knl,extra_kwargs", [ + (LaplaceKernel(2), {}), + (YukawaKernel(2), {"lam": 0.1}), + (HelmholtzKernel(2), {"k": 0.5}), + (BiharmonicKernel(2), {}), +]) +def test_m2m_coefficient_differences( + actx_factory: ArrayContextFactory, + knl: ScalarKernel, + extra_kwargs: Mapping[str, float], + verbose: bool = True, + ): + """ + Compares two approaches: + 1. Compress coefficients -> embed -> M2M translate + 2. M2M translate with full coefficients + + Verifies the difference formula between these two approaches. + """ + order = 7 + dim = 2 + repl_dict = get_repl_dict(knl, extra_kwargs) + global_const = to_scalar(knl.get_global_scaling_const()) + + # Set up source, centers, and target + source = np.array([[0.0], [0.1]]) + strength = np.array([1.0]) + + m_center1 = np.array([0.0, 0.0]) + offset_direction = np.array([-0.5, 0.25]) + c2_c1_dist = 0.1 + m_center2 = m_center1 + c2_c1_dist * offset_direction + h = m_center2 - m_center1 + + target = np.array([[2.0], [2.0]]) + + if verbose: + print(f"M2M Coefficient Verification for {type(knl).__name__}:") + print(f"m_center1 = {m_center1}") + print(f"m_center2 = {m_center2}") + print(f"h = m_center2 - m_center1 = {h}") + print() + print(f"{'k':>3s} | {'ν(k)':>15s} | {'|ν(k)|':6s} | " # ruff:ignore[ambiguous-unicode-character-string] + f"{'difference by formula':>31s} | " + f"{'difference by direct computation':>31s} | " + f"{'abs err':>10s}") + print("-" * 120) + + actx = actx_factory() + + toy_ctx_full = t.ToyContext( + knl, + mpole_expn_class=VolumeTaylorMultipoleExpansion, + extra_kernel_kwargs=extra_kwargs + ) + + toy_ctx_local = t.ToyContext( + knl, + local_expn_class=LinearPDEConformingVolumeTaylorLocalExpansion, + extra_kernel_kwargs=extra_kwargs + ) + + p_full = t.PointSources(toy_ctx_full, source, weights=strength) + p2m_full = t.multipole_expand(actx, p_full, m_center1, order=order, rscale=1.0) + + p_local = t.PointSources(toy_ctx_local, m_center2.reshape(2, 1), weights=strength) + p2l = t.local_expand(actx, p_local, target, order=order) + + mexpn = LinearPDEConformingVolumeTaylorMultipoleExpansion(knl, order) + + # Build matrix M + wrangler = mexpn.expansion_terms_wrangler + M_symbolic = wrangler.get_projection_matrix(rscale=1.0) # ruff:ignore[non-lowercase-variable-in-function] + numeric_op = NumericMatVecOperator(M_symbolic, repl_dict) + M = build_matrix(numeric_op, dtype=np.complex128) # ruff:ignore[non-lowercase-variable-in-function] + coeffs_full = (M @ p2l.coeffs) * global_const + + # Get coefficient identifiers + stored_identifiers = mexpn.get_coefficient_identifiers() + full_identifiers = mexpn.get_full_coefficient_identifiers() + is_stored = [mi in stored_identifiers for mi in full_identifiers] + stored_indices = [i for i, st in enumerate(is_stored) if st] + + mexpn_full = VolumeTaylorMultipoleExpansion(knl, order) + mexpn_full_idx = mexpn_full.get_full_coefficient_identifiers() + + max_abs_error = 0.0 + + for k, nu_k in enumerate(full_identifiers): + k_card = sum(np.array(nu_k)) + # assume all coefficient values are 1 + alpha_k = 1 + + true_k_idx = mexpn_full_idx.index(nu_k) + basis_full = np.zeros(len(mexpn_full_idx), dtype=np.complex128) + basis_full[true_k_idx] = alpha_k + p2m_full_k = p2m_full.with_coeffs(basis_full) + + # M^T @ alpha + basis_cmp = np.zeros(M.shape[0], dtype=np.complex128) + basis_cmp[stored_indices] = M[k, :] * alpha_k + + # Embed back into full basis + basis_cmp_full = np.zeros(len(mexpn_full_idx), dtype=np.complex128) + for i, nu_i in enumerate(full_identifiers): + if basis_cmp[i] != 0: + true_i_idx = mexpn_full_idx.index(nu_i) + basis_cmp_full[true_i_idx] = basis_cmp[i] + + p2m_cmp_k = p2m_full.with_coeffs(basis_cmp_full) + + p2m2m_cmp = t.multipole_expand( + actx, p2m_cmp_k, m_center2, order=order + ).eval(actx, target) + p2m2m_full = t.multipole_expand( + actx, p2m_full_k, m_center2, order=order + ).eval(actx, target) + + direct_diff = (p2m2m_cmp - p2m2m_full)[0] + + error = 0.0 + 0.0j + for s, nu_js in enumerate(stored_identifiers): + nu_js_card = sum(nu_js) + inner_sum = 0.0 + 0.0j + + if nu_js_card <= k_card: + start_idx = math.comb(order - k_card + dim, dim) + end_idx = math.comb(order - nu_js_card + dim, dim) + + for idx in range(start_idx, end_idx): + nu_l = full_identifiers[idx] + nu_sum = add_mi(nu_l, nu_js) + + if nu_sum not in full_identifiers: + continue + + derivative_idx = full_identifiers.index(nu_sum) + h_pow = float(mi_power(h, nu_l)) + fact_nu_l = mi_factorial(nu_l) + + inner_sum += coeffs_full[derivative_idx] * h_pow / fact_nu_l + + error += inner_sum * M[k, s] + + abs_err = abs(error - direct_diff) + max_abs_error = max(max_abs_error, abs_err) + + if verbose: + print(f"{k:3d} | {nu_k!s:>15s} | {k_card:6d} | " + f"{error.real:.8e}{error.imag:+.8e}j | " + f"{direct_diff.real:.8e}{direct_diff.imag:+.8e}j | " + f"{abs_err:9.2e}") + + if verbose: + print(f"\nMaximum absolute error: {max_abs_error:.2e}") + + assert max_abs_error < 1e-15, ( + f"{type(knl).__name__}: error {max_abs_error:.2e}" + ) + + +if __name__ == "__main__": + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + pytest.main([__file__]) diff --git a/sumpy/test/test_matrixgen.py b/sumpy/test/test_matrixgen.py new file mode 100644 index 000000000..1f89fafea --- /dev/null +++ b/sumpy/test/test_matrixgen.py @@ -0,0 +1,288 @@ +from __future__ import annotations + + +__copyright__ = "Copyright (C) 2018 Alexandru Fikl" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import logging +import sys + +import numpy as np +import numpy.linalg as la +import pytest + +from arraycontext import ArrayContextFactory, pytest_generate_tests_for_array_contexts +from pytools import obj_array + +from sumpy.array_context import ( # ruff:ignore[unused-import] + PytestPyOpenCLArrayContextFactory, + _acf, +) + + +logger = logging.getLogger(__name__) + +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) + + +def _build_geometry(actx, ntargets, nsources, mode, target_radius=1.0): + # source points + t = np.linspace(0.0, 2.0 * np.pi, nsources, endpoint=False) + sources = np.array([np.cos(t), np.sin(t)]) + + # density + sigma = np.cos(mode * t) + + # target points + t = np.linspace(0.0, 2.0 * np.pi, ntargets, endpoint=False) + targets = target_radius * np.array([np.cos(t), np.sin(t)]) + + # target centers and expansion radii + h = 2.0 * np.pi * target_radius / ntargets + radius = 7.0 * h + centers = (1.0 - radius) * targets + expansion_radii = np.full(ntargets, radius) + + return (actx.from_numpy(targets), + actx.from_numpy(sources), + actx.from_numpy(centers), + actx.from_numpy(expansion_radii), + actx.from_numpy(sigma)) + + +def _build_subset_indices(actx, ntargets, nsources, factor): + tgtindices = np.arange(0, ntargets) + srcindices = np.arange(0, nsources) + + rng = np.random.default_rng() + if abs(factor - 1.0) > 1.0e-14: + tgtindices = rng.choice(tgtindices, + size=int(factor * ntargets), replace=False) + srcindices = rng.choice(srcindices, + size=int(factor * nsources), replace=False) + else: + rng.shuffle(tgtindices) + rng.shuffle(srcindices) + + tgtindices, srcindices = np.meshgrid(tgtindices, srcindices) + return ( + actx.freeze(actx.from_numpy(tgtindices.ravel())), + actx.freeze(actx.from_numpy(srcindices.ravel()))) + + +# {{{ test_qbx_direct + +@pytest.mark.parametrize("factor", [1.0, 0.6]) +@pytest.mark.parametrize("lpot_id", [1, 2]) +def test_qbx_direct( + actx_factory: ArrayContextFactory, + factor, + lpot_id, + visualize=False): + if visualize: + logging.basicConfig(level=logging.INFO) + + actx = actx_factory() + + ndim = 2 + order = 12 + mode_nr = 25 + + from sumpy.kernel import DirectionalSourceDerivative, LaplaceKernel + if lpot_id == 1: + base_knl = LaplaceKernel(ndim) + knl = base_knl + elif lpot_id == 2: + base_knl = LaplaceKernel(ndim) + knl = DirectionalSourceDerivative(base_knl, dir_vec_name="dsource_vec") + else: + raise ValueError(f"unknown lpot_id: {lpot_id}") + + from sumpy.expansion.local import LineTaylorLocalExpansion + expn = LineTaylorLocalExpansion(knl, order) + + from sumpy.qbx import LayerPotential + lpot = LayerPotential( + expansion=expn, + source_kernels=(knl,), + target_kernels=(base_knl,)) + + from sumpy.qbx import LayerPotentialMatrixGenerator + mat_gen = LayerPotentialMatrixGenerator( + expansion=expn, + source_kernels=(knl,), + target_kernels=(base_knl,)) + + from sumpy.qbx import LayerPotentialMatrixSubsetGenerator + blk_gen = LayerPotentialMatrixSubsetGenerator( + expansion=expn, + source_kernels=(knl,), + target_kernels=(base_knl,)) + + for n in [200, 300, 400]: + targets, sources, centers, expansion_radii, sigma = ( + _build_geometry(actx, n, n, mode_nr, target_radius=1.2)) + + h = 2 * np.pi / n + strengths = (sigma * h,) + tgtindices, srcindices = ( + _build_subset_indices(actx, ntargets=n, nsources=n, factor=factor)) + + extra_kwargs = {} + if lpot_id == 2: + extra_kwargs["dsource_vec"] = ( + actx.from_numpy(obj_array.new_1d(np.ones((ndim, n)))) + ) + + result_lpot, = lpot(actx, + targets=targets, + sources=sources, + centers=centers, + expansion_radii=expansion_radii, + strengths=strengths, **extra_kwargs) + result_lpot = actx.to_numpy(result_lpot) + + mat, = mat_gen(actx, + targets=targets, + sources=sources, + centers=centers, + expansion_radii=expansion_radii, **extra_kwargs) + mat = actx.to_numpy(mat) + result_mat = mat @ actx.to_numpy(strengths[0]) + + blk, = blk_gen(actx, + targets=targets, + sources=sources, + centers=centers, + expansion_radii=expansion_radii, + tgtindices=tgtindices, + srcindices=srcindices, **extra_kwargs) + blk = actx.to_numpy(blk) + + tgtindices = actx.to_numpy(tgtindices) + srcindices = actx.to_numpy(srcindices) + + eps = 1.0e-10 * la.norm(result_lpot) + assert la.norm(result_mat - result_lpot) < eps + assert la.norm(blk - mat[tgtindices, srcindices]) < eps + +# }}} + + +# {{{ test_p2p_direct + +@pytest.mark.parametrize("exclude_self", [True, False]) +@pytest.mark.parametrize("factor", [1.0, 0.6]) +@pytest.mark.parametrize("lpot_id", [1, 2]) +def test_p2p_direct( + actx_factory: ArrayContextFactory, + exclude_self, + factor, + lpot_id, + visualize=False): + if visualize: + logging.basicConfig(level=logging.INFO) + + actx = actx_factory() + + ndim = 2 + mode_nr = 25 + + from sumpy.kernel import DirectionalSourceDerivative, LaplaceKernel + if lpot_id == 1: + lknl = LaplaceKernel(ndim) + elif lpot_id == 2: + lknl = LaplaceKernel(ndim) + lknl = DirectionalSourceDerivative(lknl, dir_vec_name="dsource_vec") + else: + raise ValueError(f"unknown lpot_id: '{lpot_id}'") + + from sumpy.p2p import P2P + lpot = P2P(target_kernels=[lknl], exclude_self=exclude_self) + + from sumpy.p2p import P2PMatrixGenerator + mat_gen = P2PMatrixGenerator( + target_kernels=[lknl], exclude_self=exclude_self) + + from sumpy.p2p import P2PMatrixSubsetGenerator + blk_gen = P2PMatrixSubsetGenerator( + target_kernels=[lknl], exclude_self=exclude_self) + + for n in [200, 300, 400]: + targets, sources, _, _, sigma = ( + _build_geometry(actx, n, n, mode_nr, target_radius=1.2)) + + h = 2 * np.pi / n + strengths = (sigma * h,) + tgtindices, srcindices = ( + _build_subset_indices(actx, ntargets=n, nsources=n, factor=factor)) + + extra_kwargs = {} + if exclude_self: + extra_kwargs["target_to_source"] = ( + actx.from_numpy(np.arange(n, dtype=np.int32)) + ) + if lpot_id == 2: + extra_kwargs["dsource_vec"] = ( + actx.from_numpy(obj_array.new_1d(np.ones((ndim, n))))) + + result_lpot, = lpot(actx, + targets=targets, + sources=sources, + strength=strengths, **extra_kwargs) + result_lpot = actx.to_numpy(result_lpot) + + mat, = mat_gen(actx, + targets=targets, + sources=sources, **extra_kwargs) + mat = actx.to_numpy(mat) + result_mat = mat @ actx.to_numpy(strengths[0]) + + blk, = blk_gen(actx, + targets=targets, + sources=sources, + tgtindices=tgtindices, + srcindices=srcindices, **extra_kwargs) + blk = actx.to_numpy(blk) + + tgtindices = actx.to_numpy(tgtindices) + srcindices = actx.to_numpy(srcindices) + + eps = 1.0e-10 * la.norm(result_lpot) + assert la.norm(result_mat - result_lpot) < eps + assert la.norm(blk - mat[tgtindices, srcindices]) < eps + +# }}} + + +# You can test individual routines by typing +# $ python test_matrixgen.py 'test_p2p_direct(_acf, True, 1.0, 1, visualize=True)' + +if __name__ == "__main__": + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + pytest.main([__file__]) + +# vim: fdm=marker diff --git a/sumpy/test/test_misc.py b/sumpy/test/test_misc.py new file mode 100644 index 000000000..69414a790 --- /dev/null +++ b/sumpy/test/test_misc.py @@ -0,0 +1,934 @@ +from __future__ import annotations + +from sumpy.expansion.local import LinearPDEConformingVolumeTaylorLocalExpansion + + +__copyright__ = "Copyright (C) 2017 Andreas Kloeckner" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + + +import logging +import sys +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any + +import numpy as np +import numpy.linalg as la +import pytest +from typing_extensions import override + +from arraycontext import ArrayContextFactory, pytest_generate_tests_for_array_contexts + +import sumpy.symbolic as sym +import sumpy.toys as t +from sumpy.array_context import ( # ruff:ignore[unused-import] + PytestPyOpenCLArrayContextFactory, + _acf, +) +from sumpy.expansion import ( + FullExpansionTermsWrangler, + LinearPDEBasedExpansionTermsWrangler, +) +from sumpy.expansion.diff_op import ( + as_scalar_pde, + concat, + curl, + diff, + divergence, + gradient, + laplacian, + make_identity_diff_op, + to_fourier_matrix, +) +from sumpy.kernel import ( + BiharmonicKernel, + BrinkmanletComponentKernel, + BrinkmanletSystemKernel, + BrinkmanStressComponentKernel, + BrinkmanStressSystemKernel, + ElasticityComponentKernel, + ElasticityStressComponentKernel, + ElasticityStressSystemKernel, + ElasticitySystemKernel, + ExpressionKernel, + HeatKernel, + HelmholtzKernel, + LaplaceKernel, + LineOfCompressionKernel, + ScalarKernel, + StokesletComponentKernel, + StokesletSystemKernel, + StressletComponentKernel, + StressletSystemKernel, + SystemKernel, + YukawaKernel, +) + + +if TYPE_CHECKING: + from collections.abc import Callable + + +logger = logging.getLogger(__name__) + +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) + + +# {{{ pde check for kernels + +class KernelInfo: + kernel: ScalarKernel + + def __init__(self, kernel: ScalarKernel, **kwargs: Any) -> None: + self.kernel = kernel + self.extra_kwargs = kwargs + diff_op = self.kernel.get_pde_as_diff_op() + assert len(diff_op.eqs) == 1 + eq = diff_op.eqs[0] + self.eq = eq + + def __repr__(self) -> str: + return str(self.kernel) + + def pde_func(self, cp, pot): + subs_dict = {sym.Symbol(k): v for k, v in self.extra_kwargs.items()} + result = 0 + for ident, coeff in self.eq.items(): + lresult = pot + for axis, nderivs in enumerate(ident.mi): + lresult = cp.diff(axis, lresult, nderivs) + result += lresult*float(sym.sympify(coeff).xreplace(subs_dict)) + return result + + @property + def nderivs(self): + return max(sum(ident.mi) for ident in self.eq) + + +@pytest.mark.parametrize("knl_info", [ + KernelInfo(BiharmonicKernel(2)), + KernelInfo(BiharmonicKernel(3)), + KernelInfo(YukawaKernel(2), lam=5), + KernelInfo(YukawaKernel(3), lam=5), + KernelInfo(LaplaceKernel(2)), + KernelInfo(LaplaceKernel(3)), + KernelInfo(HelmholtzKernel(2), k=5), + KernelInfo(HelmholtzKernel(3), k=5), + KernelInfo(StokesletComponentKernel(2, 0, 1), mu=5), + KernelInfo(StokesletComponentKernel(2, 1, 1), mu=5), + KernelInfo(StokesletComponentKernel(3, 0, 1), mu=5), + KernelInfo(StokesletComponentKernel(3, 1, 1), mu=5), + KernelInfo(StressletComponentKernel(2, 0, 0, 0), mu=5), + KernelInfo(StressletComponentKernel(2, 0, 0, 1), mu=5), + KernelInfo(StressletComponentKernel(3, 0, 0, 0), mu=5), + KernelInfo(StressletComponentKernel(3, 0, 0, 1), mu=5), + KernelInfo(StressletComponentKernel(3, 0, 1, 2), mu=5), + KernelInfo(ElasticityComponentKernel(2, 0, 1), mu=5, nu=0.2), + KernelInfo(ElasticityComponentKernel(2, 0, 0), mu=5, nu=0.2), + KernelInfo(ElasticityComponentKernel(3, 0, 1), mu=5, nu=0.2), + KernelInfo(ElasticityComponentKernel(3, 0, 0), mu=5, nu=0.2), + KernelInfo(ElasticityStressComponentKernel(2, 0, 1, 0), mu=5, nu=0.2), + KernelInfo(ElasticityStressComponentKernel(2, 0, 0, 1), mu=5, nu=0.2), + KernelInfo(ElasticityStressComponentKernel(3, 0, 1, 0), mu=5, nu=0.2), + KernelInfo(ElasticityStressComponentKernel(3, 0, 0, 0), mu=5, nu=0.2), + KernelInfo(LineOfCompressionKernel(3, 0), mu=5, nu=0.2), + KernelInfo(LineOfCompressionKernel(3, 1), mu=5, nu=0.2), + KernelInfo(BrinkmanletComponentKernel(2, 0, 1), mu=5, k=3), + KernelInfo(BrinkmanletComponentKernel(2, 1, 1), mu=5, k=3), + KernelInfo(BrinkmanletComponentKernel(3, 0, 1), mu=5, k=3), + KernelInfo(BrinkmanletComponentKernel(3, 1, 1), mu=5, k=3), + KernelInfo(BrinkmanStressComponentKernel(2, 0, 1, 0), mu=5, k=3), + KernelInfo(BrinkmanStressComponentKernel(2, 0, 1, 1), mu=5, k=3), + KernelInfo(BrinkmanStressComponentKernel(3, 0, 1, 0), mu=5, k=3), + KernelInfo(BrinkmanStressComponentKernel(3, 0, 1, 2), mu=5, k=3), + KernelInfo(HeatKernel(1), alpha=0.1), + KernelInfo(HeatKernel(2), alpha=0.1), + KernelInfo(HeatKernel(3), alpha=0.1), + ], ids=repr) +def test_pde_check_kernels(actx_factory: ArrayContextFactory, knl_info, order=5): + actx = actx_factory() + + dim = knl_info.kernel.dim + tctx = t.ToyContext(knl_info.kernel, + extra_source_kwargs=knl_info.extra_kwargs) + + rng = np.random.default_rng(42) + source_points = rng.random(size=(dim, 50)) - 0.5 + if isinstance(knl_info.kernel, HeatKernel): + source_points[-1] += 0.5 + pt_src = t.PointSources( + tctx, + source_points, + np.ones(50)) + + from pytools.convergence import EOCRecorder + + from sumpy.point_calculus import CalculusPatch + eoc_rec = EOCRecorder() + + for h in [0.1, 0.05, 0.025]: + if isinstance(knl_info.kernel, HeatKernel): + cp = CalculusPatch(np.array([0, 0, 0, 2])[-dim:], h=h, order=order) + else: + cp = CalculusPatch(np.array([1, 0, 0])[:dim], h=h, order=order) + pot = pt_src.eval(actx, cp.points) + + pde = knl_info.pde_func(cp, pot) + + err = la.norm(pde) + eoc_rec.add_data_point(h, err) + + logger.info("eoc:\n%s", eoc_rec) + assert eoc_rec.order_estimate() > order - knl_info.nderivs + 1 - 0.1 + +# }}} + + +# {{{ test_pde_check + +@pytest.mark.parametrize("dim", [1, 2, 3]) +def test_pde_check(dim, order=4): + from pytools.convergence import EOCRecorder + + from sumpy.point_calculus import CalculusPatch + + for iaxis in range(dim): + eoc_rec = EOCRecorder() + for h in [0.1, 0.01, 0.001]: + cp = CalculusPatch(np.array([3, 0, 0])[:dim], h=h, order=order) + df_num = cp.diff(iaxis, np.sin(10*cp.points[iaxis])) + df_true = 10*np.cos(10*cp.points[iaxis]) + + err = la.norm(df_num-df_true) + eoc_rec.add_data_point(h, err) + + logger.info("eoc:\n%s", eoc_rec) + assert eoc_rec.order_estimate() > order-2-0.1 + +# }}} + + +# {{{ test_order_finder + +@dataclass(frozen=True) +class FakeTree: + dimensions: int + root_extent: float + stick_out_factor: float + + +@pytest.mark.parametrize("knl", [ + LaplaceKernel(2), HelmholtzKernel(2), + LaplaceKernel(3), HelmholtzKernel(3)]) +def test_order_finder(knl: ScalarKernel) -> None: + from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder + + ofind = SimpleExpansionOrderFinder(1e-5) + + tree = FakeTree(knl.dim, 200, 0.5) + orders = [ + ofind(knl, frozenset([("k", 5)]), tree, level) + for level in range(30)] + logger.info("orders: %s", orders) + + # Order should not increase with level + assert (np.diff(orders) <= 0).all() + + +@pytest.mark.parametrize("knl", [ + LaplaceKernel(2), HelmholtzKernel(2), + LaplaceKernel(3), HelmholtzKernel(3)]) +def test_fmmlib_order_finder(knl): + pytest.importorskip("pyfmmlib") + from sumpy.expansion.level_to_order import FMMLibExpansionOrderFinder + + ofind = FMMLibExpansionOrderFinder(1e-5) + + tree = FakeTree(knl.dim, 200, 0.5) + orders = [ + ofind(knl, frozenset([("k", 5)]), tree, level) + for level in range(30)] + logger.info("orders: %s", orders) + + # Order should not increase with level + assert (np.diff(orders) <= 0).all() + +# }}} + + +# {{{ expansion toys p2e2e2p test cases + +def approx_convergence_factor(orders, errors): + poly = np.polyfit(orders, np.log(errors), deg=1) + return np.exp(poly[0]) + + +@dataclass(frozen=True) +class P2E2E2PTestCase: + source: np.ndarray + target: np.ndarray + center1: np.ndarray + center2: np.ndarray + expansion1: Callable[..., Any] + expansion2: Callable[..., Any] + conv_factor: str + m2l_use_fft: bool = False + + @property + def dim(self): + return len(self.source) + + +P2E2E2P_TEST_CASES = ( + # local to local, 3D + P2E2E2PTestCase( + source=np.array([3., 4., 5.]), + center1=np.array([1., 0., 0.]), + center2=np.array([1., 3., 0.]), + target=np.array([1., 1., 1.]), + expansion1=t.local_expand, + expansion2=t.local_expand, + conv_factor="norm(t-c1)/norm(s-c1)"), + + # multipole to multipole, 3D + P2E2E2PTestCase( + source=np.array([1., 1., 1.]), + center1=np.array([1., 0., 0.]), + center2=np.array([1., 0., 3.]), + target=np.array([3., 4., 5.]), + expansion1=t.multipole_expand, + expansion2=t.multipole_expand, + conv_factor="norm(s-c2)/norm(t-c2)"), + + # multipole to local, 3D + P2E2E2PTestCase( + source=np.array([-2., 2., 1.]), + center1=np.array([-2., 5., 3.]), + center2=np.array([0., 0., 0.]), + target=np.array([0., 0., -1]), + expansion1=t.multipole_expand, + expansion2=t.local_expand, + conv_factor="norm(t-c2)/(norm(c2-c1)-norm(c1-s))"), + + # multipole to local, 3D with FFT + P2E2E2PTestCase( + source=np.array([-2., 2., 1.]), + center1=np.array([-2., 5., 3.]), + center2=np.array([0., 0., 0.]), + target=np.array([0., 0., -1]), + expansion1=t.multipole_expand, + expansion2=t.local_expand, + m2l_use_fft=True, + conv_factor="norm(t-c2)/(norm(c2-c1)-norm(c1-s))"), +) + +# }}} + + +# {{{ test_toy_p2e2e2p + +ORDERS_P2E2E2P = (3, 4, 5) +RTOL_P2E2E2P = 1e-2 + + +@pytest.mark.parametrize("case", P2E2E2P_TEST_CASES) +def test_toy_p2e2e2p(actx_factory: ArrayContextFactory, case): + dim = case.dim + + src = case.source.reshape(dim, -1) + tgt = case.target.reshape(dim, -1) + + from pymbolic import evaluate, parse + case_conv_factor = evaluate(parse(case.conv_factor), { + "s": case.source, + "c1": case.center1, + "c2": case.center2, + "t": case.target, + "norm": la.norm, + }) + + if not 0 <= case_conv_factor <= 1: + raise ValueError( + f"convergence factor not in valid range: {case_conv_factor}") + + from sumpy.expansion import VolumeTaylorExpansionFactory + + actx = actx_factory() + ctx = t.ToyContext( + LaplaceKernel(dim), + expansion_factory=VolumeTaylorExpansionFactory(), + m2l_use_fft=case.m2l_use_fft) + + errors = [] + + src_pot = t.PointSources(ctx, src, weights=np.array([1.])) + pot_actual = src_pot.eval(actx, tgt).item() + + for order in ORDERS_P2E2E2P: + expn = case.expansion1(actx, src_pot, case.center1, order=order) + expn2 = case.expansion2(actx, expn, case.center2, order=order) + pot_p2e2e2p = expn2.eval(actx, tgt).item() + errors.append(np.abs(pot_actual - pot_p2e2e2p)) + + conv_factor = approx_convergence_factor(1 + np.array(ORDERS_P2E2E2P), errors) + assert conv_factor <= min(1, case_conv_factor * (1 + RTOL_P2E2E2P)), \ + (conv_factor, case_conv_factor * (1 + RTOL_P2E2E2P)) + +# }}} + + +# {{{ test_cse_matvec + +def test_cse_matvec(): + from sumpy.expansion import CSEMatVecOperator + input_coeffs = [ + [(0, 2)], + [], + [(1, 1)], + [(1, 9)], + ] + + output_coeffs = [ + [], + [(0, 3)], + [], + [(2, 7), (1, 5)], + ] + + op = CSEMatVecOperator(input_coeffs, output_coeffs, shape=(4, 2)) + m = np.array([[2, 0], [6, 0], [0, 1], [30, 16]]) + + rng = np.random.default_rng(42) + vec = rng.random(2) + expected_result = m @ vec + actual_result = [float(x) for x in op.matvec(vec)] + assert np.allclose(expected_result, actual_result) + + vec = rng.random(4) + expected_result = m.T @ vec + actual_result = [float(x) for x in op.transpose_matvec(vec)] + assert np.allclose(expected_result, actual_result) + +# }}} + + +# {{{ test_diff_op_stokes + +def test_diff_op_stokes(): + from sumpy.symbolic import Function, symbols + diff_op = make_identity_diff_op(3, 4) + u = diff_op[:3] + p = diff_op[3] + pde = concat(laplacian(u) - gradient(p), divergence(u)) + + actual_output = pde.to_sym() + x, y, z = syms = symbols("x0, x1, x2") + funcs = symbols("f0, f1, f2, f3", cls=Function) + u, v, w, p = (f(*syms) for f in funcs) + + eq1 = u.diff(x, x) + u.diff(y, y) + u.diff(z, z) - p.diff(x) + eq2 = v.diff(x, x) + v.diff(y, y) + v.diff(z, z) - p.diff(y) + eq3 = w.diff(x, x) + w.diff(y, y) + w.diff(z, z) - p.diff(z) + eq4 = u.diff(x) + v.diff(y) + w.diff(z) + + expected_output = [eq1, eq2, eq3, eq4] + + assert expected_output == actual_output + +# }}} + + +# {{{ test_as_scalar_pde_stokes + +def test_as_scalar_pde_stokes(): + diff_op = make_identity_diff_op(3, 4) + u = diff_op[:3] + p = diff_op[3] + pde = concat(laplacian(u) - gradient(p), divergence(u)) + + # velocity components in Stokes should satisfy Biharmonic + for i in range(3): + logger.info("pde\n%s", as_scalar_pde(pde, i)) + logger.info("\n%s", laplacian(laplacian(u[i]))) + assert as_scalar_pde(pde, i) == laplacian(laplacian(u[0])) + + # pressure should satisfy Laplace + assert as_scalar_pde(pde, 3) == laplacian(u[0]) + +# }}} + + +# {{{ test_as_scalar_pde_maxwell + +def test_as_scalar_pde_maxwell(): + from sumpy.symbolic import symbols + op = make_identity_diff_op(3, 6, time_dependent=True) + E = op[:3] # ruff:ignore[non-lowercase-variable-in-function] + B = op[3:] # ruff:ignore[non-lowercase-variable-in-function] + mu, epsilon = symbols("mu, epsilon") + t = (0, 0, 0, 1) + + pde = concat(curl(E) + diff(B, t), curl(B) - mu*epsilon*diff(E, t), + divergence(E), divergence(B)) + as_scalar_pde(pde, 3) + + for i in range(6): + assert as_scalar_pde(pde, i) == \ + laplacian(op[0]) - mu*epsilon*diff(diff(op[0], t), t) + +# }}} + + +# {{{ test_as_scalar_pde_elasticity + +def test_as_scalar_pde_elasticity(): + # Ref: https://doi.org/10.1006/jcph.1996.0102 + + diff_op = make_identity_diff_op(2, 5) + sigma_x = diff_op[0] + sigma_y = diff_op[1] + tau = diff_op[2] + u = diff_op[3] + v = diff_op[4] + + # Use numeric values as the expressions grow exponentially large otherwise + from sumpy.symbolic import symbols + lam, mu = symbols("lam, mu") + + x = (1, 0) + y = (0, 1) + + pde = concat(*[ + diff(sigma_x, x) + diff(tau, y), + diff(tau, x) + diff(sigma_y, y), + sigma_x - (lam + 2*mu)*diff(u, x) - lam*diff(v, y), + sigma_y - (lam + 2*mu)*diff(v, y) - lam*diff(u, x), + tau - mu*(diff(u, y) + diff(v, x)), + ]) + + assert pde.order == 1 + for i in range(5): + scalar_pde = as_scalar_pde(pde, i) + assert scalar_pde == laplacian(laplacian(diff_op[0])) + assert scalar_pde.order == 4 + + # Wikipedia: displacement formulation + + u = make_identity_diff_op(3, 3) + pde = mu * laplacian(u) + (mu + lam) * gradient(divergence(u)) + + for i in range(3): + scalar_pde = as_scalar_pde(pde, i) + assert scalar_pde == laplacian(laplacian(u[0])) + +# }}} + + +# {{{ test_as_scalar_pde_brinkman + +def test_as_scalar_pde_brinkman(): + dim = 3 + mu = sym.Symbol("mu") + k = sym.Symbol("k") + + # NOTE: momentum + incompressibility equations + diff_op = make_identity_diff_op(dim, dim + 1) + u = diff_op[:3] + p = diff_op[3] + pde = concat(mu * (laplacian(u) - k**2 * u) - gradient(p), divergence(u)) + + # velocity components in Brinkman should satisfy Yukawa + for i in range(3): + scalar_pde = as_scalar_pde(pde, i) + + logger.info("pde\n%s", scalar_pde) + logger.info("\n%s", laplacian(laplacian(u[i]))) + assert scalar_pde == laplacian(laplacian(u[0]) - k**2 * u[0]) + + # pressure should satisfy Laplace + assert as_scalar_pde(pde, 3) == laplacian(u[0]) + +# }}} + + +# {{{ test_elasticity_pickle + +def test_elasticity_pickle(): + from pickle import dumps, loads + stokes_knl = StokesletComponentKernel( + 3, 0, 1, viscosity_mu_name="mu1") + elasticity_knl = ElasticityComponentKernel( + 3, 0, 1, viscosity_mu_name="mu1", poisson_ratio_name="nu1") + elasticity_helper_knl = LineOfCompressionKernel( + 3, 0, viscosity_mu_name="mu1", poisson_ratio_name="nu1") + + assert loads(dumps(stokes_knl)) == stokes_knl + assert loads(dumps(elasticity_knl)) == elasticity_knl + assert loads(dumps(elasticity_helper_knl)) == elasticity_helper_knl + +# }}} + + +# {{{ test_to_fourier_matrix_laplace + +def test_to_fourier_matrix_scalar() -> None: + ks = sym.make_sym_vector("k", 3) + lam = sym.Symbol("lam") + + # LaplaceKernel + kernel = LaplaceKernel(2) + mat = to_fourier_matrix(kernel.get_pde_as_diff_op(), ks) + assert mat == sym.Matrix([[-ks[0]**2 - ks[1]**2]]) + + kernel = LaplaceKernel(3) + mat = to_fourier_matrix(kernel.get_pde_as_diff_op(), ks) + assert mat == sym.Matrix([[-ks[0]**2 - ks[1]**2 - ks[2]**2]]) + + # YukawaKernel + kernel = YukawaKernel(2, yukawa_lambda_name=lam.name) + mat = to_fourier_matrix(kernel.get_pde_as_diff_op(), ks) + assert mat == sym.Matrix([[-ks[0]**2 - ks[1]**2 - lam**2]]) + + kernel = YukawaKernel(3, yukawa_lambda_name=lam.name) + mat = to_fourier_matrix(kernel.get_pde_as_diff_op(), ks) + assert mat == sym.Matrix([[-ks[0]**2 - ks[1]**2 - ks[2]**2 - lam**2]]) + +# }}} + + +# {{{ test_to_fourier_matrix_stokes + +@pytest.mark.parametrize("dim", [2, 3]) +def test_to_fourier_matrix_stokes(dim: int) -> None: + ks = sym.make_sym_vector("k", dim) + mu = sym.Symbol("mu") + + kernel = StokesletSystemKernel(dim=dim, viscosity_mu_name=mu.name) + pde = kernel.get_pde_as_diff_op() + + k_sqr = sum(k**2 for k in ks) + if dim == 2: + expected = sym.Matrix([ + [-mu * k_sqr, 0, -sym.I*ks[0]], + [0, -mu * k_sqr, -sym.I*ks[1]], + [sym.I*ks[0], sym.I*ks[1], 0], + ]) + elif dim == 3: + expected = sym.Matrix([ + [-mu * k_sqr, 0, 0, -sym.I*ks[0]], + [0, -mu * k_sqr, 0, -sym.I*ks[1]], + [0, 0, -mu * k_sqr, -sym.I*ks[2]], + [sym.I*ks[0], sym.I*ks[1], sym.I*ks[2], 0], + ]) + else: + raise ValueError(f"unsupported dimension: {dim}") + + mat = to_fourier_matrix(pde, ks) + assert mat.expand() == expected.expand() + +# }}} + + +# {{{ test_to_fourier_matrix_elasticity + +@pytest.mark.parametrize("dim", [2, 3]) +def test_to_fourier_matrix_elasticity(dim: int) -> None: + ks = sym.make_sym_vector("k", dim) + mu = sym.Symbol("mu") + nu = sym.Symbol("nu") + mn = mu / (1 - 2 * nu) + + kernel = ElasticitySystemKernel(dim, + viscosity_mu_name=mu.name, + poisson_ratio_name=nu.name) + pde = kernel.get_pde_as_diff_op() + + k_sqr = sum(k**2 for k in ks) + if dim == 2: + expected = sym.Matrix([ + [-mu * k_sqr - mn * ks[0]**2, -mn * ks[0] * ks[1]], + [-mn * ks[1] * ks[0], -mu * k_sqr - mn * ks[1]**2], + ]) + elif dim == 3: + expected = sym.Matrix([ + [-mu * k_sqr - mn * ks[0]**2, -mn * ks[0] * ks[1], -mn * ks[0] * ks[2]], + [-mn * ks[1] * ks[0], -mu * k_sqr - mn * ks[1]**2, -mn * ks[1] * ks[2]], + [-mn * ks[2] * ks[0], -mn * ks[2] * ks[1], -mu * k_sqr - mn * ks[2]**2], + ]) + else: + raise ValueError(f"unsupported dimension: {dim}") + + mat = to_fourier_matrix(pde, ks) + assert mat.expand() == expected.expand() + +# }}} + + +# {{{ test_to_fourier_matrix_brinkman + +@pytest.mark.parametrize("dim", [2, 3]) +def test_to_fourier_matrix_brinkman(dim: int) -> None: + ks = sym.make_sym_vector("k", dim) + mu = sym.Symbol("mu") + kappa = sym.Symbol("k") + + kernel = BrinkmanletSystemKernel(dim, + viscosity_mu_name=mu.name, + darcy_impermeability_name=kappa.name) + pde = kernel.get_pde_as_diff_op() + + k_sqr = sum(k**2 for k in ks) + if dim == 2: + expected = sym.Matrix([ + [mu * (-k_sqr - kappa**2), 0, -sym.I*ks[0]], + [0, mu * (-k_sqr - kappa**2), -sym.I*ks[1]], + [sym.I*ks[0], sym.I*ks[1], 0], + ]) + elif dim == 3: + expected = sym.Matrix([ + [mu * (-k_sqr - kappa**2), 0, 0, -sym.I*ks[0]], + [0, mu * (-k_sqr - kappa**2), 0, -sym.I*ks[1]], + [0, 0, mu * (-k_sqr - kappa**2), -sym.I*ks[2]], + [sym.I*ks[0], sym.I*ks[1], sym.I*ks[2], 0], + ]) + else: + raise ValueError(f"unsupported dimension: {dim}") + + mat = to_fourier_matrix(pde, ks) + assert mat.expand() == expected.expand() + +# }}} + + +# {{{ test_weird_kernel + +w = make_identity_diff_op(2) + +pdes = [ + diff(w, (1, 1)) + diff(w, (2, 0)), + diff(w, (1, 1)) + diff(w, (0, 2)), +] + + +@pytest.mark.parametrize("pde", pdes) +def test_weird_kernel(pde): + class MyKernel(ExpressionKernel): + def __init__(self): + super().__init__(dim=2, expression=1, global_scaling_const=1) + + @property + @override + def is_complex_valued(self) -> bool: + return False + + @override + def get_pde_as_diff_op(self): + return pde + + from functools import reduce + from operator import mul + + knl = MyKernel() + order = 10 + expn = LinearPDEConformingVolumeTaylorLocalExpansion(kernel=knl, + order=order, use_rscale=False) + + coeffs = expn.get_coefficient_identifiers() + fft_size = reduce(mul, map(max, *coeffs), 1) + + assert fft_size == order + +# }}} + + +# {{{ test_get_storage_index + +class StorageIndexTestKernel(ExpressionKernel): + def __init__(self, dim, max_mi): + super().__init__(dim=dim, expression=1, global_scaling_const=1) + self._max_mi = max_mi + + @property + @override + def is_complex_valued(self) -> bool: + return False + + @override + def get_pde_as_diff_op(self): + w = make_identity_diff_op(self.dim) + return diff(w, tuple(self._max_mi)) + + +@pytest.mark.parametrize("order", [6]) +@pytest.mark.parametrize("knl", [ + LaplaceKernel(2), + LaplaceKernel(3), + StorageIndexTestKernel(2, (3, 0)), + StorageIndexTestKernel(2, (0, 3)), + StorageIndexTestKernel(3, (3, 0, 0)), + StorageIndexTestKernel(3, (0, 3, 0)), + StorageIndexTestKernel(3, (0, 0, 3)), + BiharmonicKernel(2), + BiharmonicKernel(3), +]) +@pytest.mark.parametrize("compressed", (True, False)) +def test_get_storage_index(order, knl, compressed): + dim = knl.dim + if compressed: + wrangler = LinearPDEBasedExpansionTermsWrangler(order, dim, None, knl=knl) + else: + wrangler = FullExpansionTermsWrangler(order, dim, max_mi=None) + for i, mi in enumerate(wrangler.get_coefficient_identifiers()): + assert i == wrangler.get_storage_index(mi) + +# }}} + + +# {{{ test_system_kernel_components + +def _get_scalar_cls(knl: SystemKernel) -> type[ScalarKernel]: + if isinstance(knl, ElasticitySystemKernel): + return ElasticityComponentKernel + if isinstance(knl, ElasticityStressSystemKernel): + return ElasticityStressComponentKernel + elif isinstance(knl, StokesletSystemKernel): + return StokesletComponentKernel + elif isinstance(knl, StressletSystemKernel): + return StressletComponentKernel + elif isinstance(knl, BrinkmanletSystemKernel): + return BrinkmanletComponentKernel + elif isinstance(knl, BrinkmanStressSystemKernel): + return BrinkmanStressComponentKernel + else: + raise AssertionError + + +@pytest.mark.parametrize("dim", [2, 3]) +@pytest.mark.parametrize("cls", [ + ElasticityStressSystemKernel, + ElasticitySystemKernel, + StokesletSystemKernel, + StressletSystemKernel, + BrinkmanletSystemKernel, + BrinkmanStressSystemKernel, +]) +def test_system_kernel_components(dim: int, cls: type[SystemKernel]) -> None: + from itertools import product + + knl = cls(dim=dim) + scalar_cls = _get_scalar_cls(knl) + shape = knl.shape + assert knl.ndim == len(shape) + + d = sym.make_sym_vector("d", dim) + expr = knl.get_expression(d) + assert isinstance(expr, np.ndarray) + assert expr.shape == shape + + # check components + if len(shape) == 2: + assert shape == (dim, dim) + for i, j in product(range(dim), repeat=2): + comp = knl[i, j] + assert comp.dim == dim + assert isinstance(comp, scalar_cls) + assert isinstance(comp.get_pde_system_kernel()[0], cls) + else: + assert shape == (dim, dim, dim) + for i, j, k in product(range(dim), repeat=3): + comp = knl[i, j, k] + + assert comp.dim == dim + assert isinstance(comp, scalar_cls) + assert isinstance(comp.get_pde_system_kernel()[0], cls) + + # check symmetry + if isinstance(knl, ( + ElasticitySystemKernel, + StokesletSystemKernel, + BrinkmanletSystemKernel, + )): + for i, j in product(range(dim), repeat=2): + knl_ij = knl[i, j] + expected_ij = tuple(sorted([i, j])) + + assert knl_ij is knl[j, i] + assert (knl_ij.icomp, knl_ij.jcomp) == expected_ij + elif isinstance(knl, (StressletSystemKernel,)): + for i, j, k in product(range(dim), repeat=3): + knl_ijk = knl[i, j, k] + expected_ij = tuple(sorted([i, j, k])) + + assert knl_ijk is knl[expected_ij] + assert (knl_ijk.icomp, knl_ijk.jcomp, knl_ijk.kcomp) == expected_ij + elif isinstance(knl, (BrinkmanStressSystemKernel,)): + for i, j, k in product(range(dim), repeat=3): + knl_ijk = knl[i, j, k] + expected_ij = min(i, k), j, max(i, k) + + assert knl_ijk is knl[expected_ij] + assert (knl_ijk.icomp, knl_ijk.jcomp, knl_ijk.kcomp) == expected_ij + elif isinstance(knl, (ElasticityStressSystemKernel,)): + for i, j, k in product(range(dim), repeat=3): + knl_ijk = knl[i, j, k] + expected_ij = min(i, j), max(i, j), k + + assert knl_ijk is knl[expected_ij] + assert (knl_ijk.icomp, knl_ijk.jcomp, knl_ijk.kcomp) == expected_ij + else: + raise AssertionError(knl) + +# }}} + + +# {{{ test_system_kernel_pickle + +@pytest.mark.parametrize("dim", [2, 3]) +@pytest.mark.parametrize("cls", [ + ElasticitySystemKernel, + StokesletSystemKernel, + StressletSystemKernel, + BrinkmanletSystemKernel, + BrinkmanStressSystemKernel, +]) +def test_system_kernel_pickle(dim: int, cls: type[SystemKernel]) -> None: + from pickle import dumps, loads + + knl = cls(dim=dim) + assert loads(dumps(knl)) == knl + +# }}} + + +# You can test individual routines by typing +# $ python test_misc.py 'test_pde_check_kernels(_acf, +# KernelInfo(HelmholtzKernel(2), k=5), order=5)' + +if __name__ == "__main__": + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + pytest.main([__file__]) + +# vim: fdm=marker diff --git a/test/test_qbx.py b/sumpy/test/test_qbx.py similarity index 53% rename from test/test_qbx.py rename to sumpy/test/test_qbx.py index 379ef0e22..533ed796e 100644 --- a/test/test_qbx.py +++ b/sumpy/test/test_qbx.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2017 Matt Wala" __license__ = """ @@ -20,33 +23,45 @@ THE SOFTWARE. """ -import numpy as np +import logging import sys -import pyopencl as cl -from pyopencl.tools import ( # noqa - pytest_generate_tests_for_pyopencl as pytest_generate_tests) +import numpy as np +import pytest + +from arraycontext import ArrayContextFactory, pytest_generate_tests_for_array_contexts + +from sumpy.array_context import ( # ruff:ignore[unused-import] + PytestPyOpenCLArrayContextFactory, + _acf, +) +from sumpy.expansion.local import LineTaylorLocalExpansion, VolumeTaylorLocalExpansion + -import logging logger = logging.getLogger(__name__) -from sumpy.expansion.local import ( - LineTaylorLocalExpansion, VolumeTaylorLocalExpansion) -import pytest + +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) +# {{{ test_direct_qbx_vs_eigval + @pytest.mark.parametrize("expn_class", [ LineTaylorLocalExpansion, VolumeTaylorLocalExpansion, ]) -def test_direct_qbx_vs_eigval(ctx_factory, expn_class): +def test_direct_qbx_vs_eigval( + actx_factory: ArrayContextFactory, + expn_class, + visualize=False): """This evaluates a single layer potential on a circle using a known eigenvalue/eigenvector combination. """ + if visualize: + logging.basicConfig(level=logging.INFO) - logging.basicConfig(level=logging.INFO) - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) + actx = actx_factory() from sumpy.kernel import LaplaceKernel lknl = LaplaceKernel(2) @@ -55,8 +70,10 @@ def test_direct_qbx_vs_eigval(ctx_factory, expn_class): from sumpy.qbx import LayerPotential - lpot = LayerPotential(ctx, expansion=expn_class(lknl, order), - target_kernels=(lknl,), source_kernels=(lknl,)) + lpot = LayerPotential( + expansion=expn_class(lknl, order), + target_kernels=(lknl,), + source_kernels=(lknl,)) mode_nr = 25 @@ -76,51 +93,62 @@ def test_direct_qbx_vs_eigval(ctx_factory, expn_class): h = 2 * np.pi / n - targets = unit_circle - sources = unit_circle + targets = actx.from_numpy(unit_circle) + sources = actx.from_numpy(unit_circle) radius = 7 * h - centers = unit_circle * (1 - radius) - - expansion_radii = np.ones(n) * radius + centers = actx.from_numpy((1 - radius) * unit_circle) + expansion_radii = actx.from_numpy(radius * np.ones(n)) + strengths = (actx.from_numpy(sigma * h),) - strengths = (sigma * h,) - evt, (result_qbx,) = lpot(queue, targets, sources, centers, strengths, + result_qbx, = lpot( + actx, + targets, sources, centers, strengths, expansion_radii=expansion_radii) + result_qbx = actx.to_numpy(result_qbx) - eocrec.add_data_point(h, np.max(np.abs(result_ref - result_qbx))) + error = np.linalg.norm(result_ref - result_qbx, np.inf) + eocrec.add_data_point(h, error) - print(eocrec) + logger.info("eoc:\n%s", eocrec) slack = 1.5 assert eocrec.order_estimate() > order - slack +# }}} + + +# {{{ test_direct_qbx_vs_eigval_with_tgt_deriv @pytest.mark.parametrize("expn_class", [ LineTaylorLocalExpansion, VolumeTaylorLocalExpansion, ]) -def test_direct_qbx_vs_eigval_with_tgt_deriv(ctx_factory, expn_class): +def test_direct_qbx_vs_eigval_with_tgt_deriv( + actx_factory, expn_class, visualize=False): """This evaluates a single layer potential on a circle using a known eigenvalue/eigenvector combination. """ + if visualize: + logging.basicConfig(level=logging.INFO) - logging.basicConfig(level=logging.INFO) - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) + actx = actx_factory() - from sumpy.kernel import LaplaceKernel, AxisTargetDerivative + from sumpy.kernel import AxisTargetDerivative, LaplaceKernel lknl = LaplaceKernel(2) order = 8 from sumpy.qbx import LayerPotential - lpot_dx = LayerPotential(ctx, expansion=expn_class(lknl, order), - target_kernels=(AxisTargetDerivative(0, lknl),), source_kernels=(lknl,)) - lpot_dy = LayerPotential(ctx, expansion=expn_class(lknl, order), - target_kernels=(AxisTargetDerivative(1, lknl),), source_kernels=(lknl,)) + lpot_dx = LayerPotential( + expansion=expn_class(lknl, order), + target_kernels=(AxisTargetDerivative(0, lknl),), + source_kernels=(lknl,)) + lpot_dy = LayerPotential( + expansion=expn_class(lknl, order), + target_kernels=(AxisTargetDerivative(1, lknl),), + source_kernels=(lknl,)) mode_nr = 15 @@ -134,45 +162,55 @@ def test_direct_qbx_vs_eigval_with_tgt_deriv(ctx_factory, expn_class): unit_circle = np.array([unit_circle.real, unit_circle.imag]) sigma = np.cos(mode_nr * t) - #eigval = 1/(2*mode_nr) + # eigval = 1/(2*mode_nr) eigval = 0.5 result_ref = eigval * sigma h = 2 * np.pi / n - targets = unit_circle - sources = unit_circle + targets = actx.from_numpy(unit_circle) + sources = actx.from_numpy(unit_circle) radius = 7 * h - centers = unit_circle * (1 - radius) + centers = actx.from_numpy((1 - radius) * unit_circle) + expansion_radii = actx.from_numpy(radius * np.ones(n)) + strengths = (actx.from_numpy(sigma * h),) - expansion_radii = np.ones(n) * radius - - strengths = (sigma * h,) - - evt, (result_qbx_dx,) = lpot_dx(queue, targets, sources, centers, strengths, + result_qbx_dx, = lpot_dx( + actx, + targets, sources, centers, strengths, expansion_radii=expansion_radii) - evt, (result_qbx_dy,) = lpot_dy(queue, targets, sources, centers, strengths, + result_qbx_dy, = lpot_dy( + actx, + targets, sources, centers, strengths, expansion_radii=expansion_radii) + result_qbx_dx = actx.to_numpy(result_qbx_dx) + result_qbx_dy = actx.to_numpy(result_qbx_dy) + normals = unit_circle result_qbx = normals[0] * result_qbx_dx + normals[1] * result_qbx_dy - eocrec.add_data_point(h, np.max(np.abs(result_ref - result_qbx))) + error = np.linalg.norm(result_ref - result_qbx, np.inf) + eocrec.add_data_point(h, error) if expn_class is not LineTaylorLocalExpansion: - print(eocrec) + logger.info("eoc:\n%s", eocrec) slack = 1.5 assert eocrec.order_estimate() > order - slack +# }}} + + +# You can test individual routines by typing +# $ python test_qbx.py 'test_direct_qbx_vs_eigval(_acf, LineTaylorLocalExpansion)' if __name__ == "__main__": if len(sys.argv) > 1: exec(sys.argv[1]) else: - from pytest import main - main([__file__]) + pytest.main([__file__]) # vim: fdm=marker diff --git a/sumpy/test/test_target_deriv.py b/sumpy/test/test_target_deriv.py new file mode 100644 index 000000000..97913761f --- /dev/null +++ b/sumpy/test/test_target_deriv.py @@ -0,0 +1,152 @@ +from __future__ import annotations + + +__copyright__ = """ +Copyright (C) 2025 Shawn Lin +Copyright (C) 2025 University of Illinois Board of Trustees +""" + +__license__ = """ +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +""" + +import sys +from typing import TYPE_CHECKING + +import numpy as np +import pytest + +from arraycontext import ( + PyOpenCLArrayContext, + pytest_generate_tests_for_array_contexts, +) +from pytools.convergence import EOCRecorder + +from sumpy.array_context import ( # ruff:ignore[unused-import] + PytestPyOpenCLArrayContextFactory, + _acf, +) +from sumpy.expansion.local import LineTaylorLocalExpansion +from sumpy.kernel import AxisTargetDerivative, LaplaceKernel, ScalarKernel +from sumpy.test.geometries import make_starfish + + +if TYPE_CHECKING: + from arraycontext import ArrayContextFactory + +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) + + +@pytest.mark.parametrize("knl", [LaplaceKernel(2)]) +def test_lpot_dx_jump_relation_convergence( + actx_factory: ArrayContextFactory, + knl: ScalarKernel): + """Test convergence of jump relations for single layer potential derivatives.""" + + actx = actx_factory() + if not isinstance(actx, PyOpenCLArrayContext): + pytest.skip() + + qbx_order = 5 + + ntargets = 20 + target_geo = make_starfish(npoints=ntargets) + targets_h = target_geo.nodes + targets = actx.from_numpy(targets_h) + + from sumpy.qbx import LayerPotential + expansion = LineTaylorLocalExpansion(knl, qbx_order) + lplot_dx = LayerPotential( + expansion=expansion, + target_kernels=(AxisTargetDerivative(0, knl),), + source_kernels=(knl,) + ) + lplot_dy = LayerPotential( + expansion=expansion, + target_kernels=(AxisTargetDerivative(1, knl),), + source_kernels=(knl,) + ) + eocrec = EOCRecorder() + + for nsources in [320, 640, 1280, 2560]: + source_geo = make_starfish(npoints=nsources) + sources = actx.from_numpy(source_geo.nodes) + + weights_nodes_h = source_geo.area_elements * source_geo.weights + weights_nodes = actx.from_numpy(weights_nodes_h) + + expansion_radii_h = 4 * target_geo.area_elements / nsources + expansion_radii = actx.from_numpy(expansion_radii_h) + centers_in = actx.from_numpy( + targets_h - target_geo.normals * expansion_radii_h) + centers_out = actx.from_numpy( + targets_h + target_geo.normals * expansion_radii_h) + + strengths = (weights_nodes,) + (eval_in_dx,) = lplot_dx( + actx, + targets, sources, centers_in, strengths, + expansion_radii=expansion_radii + ) + + (eval_in_dy,) = lplot_dy( + actx, + targets, sources, centers_in, strengths, + expansion_radii=expansion_radii + ) + + (eval_out_dx,) = lplot_dx( + actx, + targets, sources, centers_out, strengths, + expansion_radii=expansion_radii + ) + + (eval_out_dy,) = lplot_dy( + actx, + targets, sources, centers_out, strengths, + expansion_radii=expansion_radii + ) + + eval_in_dx = actx.to_numpy(eval_in_dx) + eval_in_dy = actx.to_numpy(eval_in_dy) + eval_out_dx = actx.to_numpy(eval_out_dx) + eval_out_dy = actx.to_numpy(eval_out_dy) + + eval_in = eval_in_dx * target_geo.normals[0] + \ + eval_in_dy * target_geo.normals[1] + eval_out = eval_out_dx * target_geo.normals[0] + \ + eval_out_dy * target_geo.normals[1] + + # check jump relation: S'_int - S'_ext = sigma (=1 for constant density) + jump_error = np.abs(eval_in - eval_out - 1) + + h_max = 1/nsources + eocrec.add_data_point(h_max, np.max(jump_error)) + + print(eocrec) + assert eocrec.order_estimate() > qbx_order - 1.5 + + +if __name__ == "__main__": + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + pytest.main([__file__]) diff --git a/test/test_tools.py b/sumpy/test/test_tools.py similarity index 52% rename from test/test_tools.py rename to sumpy/test/test_tools.py index a3d2a9f1a..3d053a607 100644 --- a/test/test_tools.py +++ b/sumpy/test/test_tools.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2020 Isuru Fernando" __license__ = """ @@ -21,18 +24,41 @@ """ import logging -logger = logging.getLogger(__name__) +import sys -import sumpy.symbolic as sym -from sumpy.tools import (fft_toeplitz_upper_triangular, - matvec_toeplitz_upper_triangular) import numpy as np +import pytest + +from arraycontext import ArrayContextFactory, pytest_generate_tests_for_array_contexts + +import sumpy.symbolic as sym +from sumpy.array_context import ( # ruff:ignore[unused-import] + PytestPyOpenCLArrayContextFactory, + _acf, +) +from sumpy.tools import ( + fft, + fft_toeplitz_upper_triangular, + loopy_fft, + matvec_toeplitz_upper_triangular, +) -def test_fft(): +logger = logging.getLogger(__name__) + +pytest_generate_tests = pytest_generate_tests_for_array_contexts([ + PytestPyOpenCLArrayContextFactory, + ]) + + +# {{{ test_matvec_fft + +def test_matvec_fft(): k = 5 - v = np.random.rand(k) - x = np.random.rand(k) + + rng = np.random.default_rng(42) + v = rng.random(k) + x = rng.random(k) fft = fft_toeplitz_upper_triangular(v, x) matvec = matvec_toeplitz_upper_triangular(v, x) @@ -40,8 +66,12 @@ def test_fft(): for i in range(k): assert abs(fft[i] - matvec[i]) < 1e-14 +# }}} + -def test_fft_small_floats(): +# {{{ test_matvec_fft_small_floats + +def test_matvec_fft_small_floats(): k = 5 v = sym.make_sym_vector("v", k) x = sym.make_sym_vector("x", k) @@ -52,3 +82,39 @@ def test_fft_small_floats(): if f == 0: continue assert abs(f) > 1e-10 + +# }}} + + +# {{{ test_fft + +@pytest.mark.parametrize("size", [1, 2, 7, 10, 30, 210]) +def test_fft(actx_factory: ArrayContextFactory, size: int): + actx = actx_factory() + + inp = np.arange(size, dtype=np.complex64) + inp_dev = actx.from_numpy(inp) + out = fft(inp) + + fft_func = loopy_fft( + inp.shape[-1], + n_batch_dims=len(inp.shape) - 1, + inverse=False, + complex_dtype=inp.dtype.type) + out_dev = actx.call_loopy(fft_func, y=inp_dev)["x"] + + assert np.allclose(actx.to_numpy(out_dev), out) + +# }}} + + +# You can test individual routines by typing +# $ python test_tools.py 'test_fft(_acf, 30)' + +if __name__ == "__main__": + if len(sys.argv) > 1: + exec(sys.argv[1]) + else: + pytest.main([__file__]) + +# vim: fdm=marker diff --git a/sumpy/tools.py b/sumpy/tools.py index 14152d706..6a5d2f419 100644 --- a/sumpy/tools.py +++ b/sumpy/tools.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = """ Copyright (C) 2012 Andreas Kloeckner Copyright (C) 2018 Alexandru Fikl @@ -24,457 +27,182 @@ THE SOFTWARE. """ -__doc__ = """ - - Misc tools - ========== - - .. autoclass:: ExprDerivativeTaker - .. autoclass:: LaplaceDerivativeTaker - .. autoclass:: RadialDerivativeTaker - .. autoclass:: HelmholtzDerivativeTaker - .. autoclass:: DifferentiatedExprDerivativeTaker -""" - -from pytools import memoize_method -from pytools.tag import Tag, tag_dataclass -import numbers -from collections import defaultdict -from pymbolic.mapper import WalkMapper +import enum +import logging +import warnings +from abc import ABC, abstractmethod +from collections.abc import Hashable, Iterable, Iterator, Sequence, Set as AbstractSet +from dataclasses import dataclass +from typing import TYPE_CHECKING, Any, TypeAlias, cast import numpy as np -import sumpy.symbolic as sym +from typing_extensions import override import loopy as lp -from typing import Dict, Tuple, Any - -import logging -logger = logging.getLogger(__name__) - - -# {{{ multi_index helpers - -def add_mi(mi1, mi2): - return tuple([mi1i + mi2i for mi1i, mi2i in zip(mi1, mi2)]) - - -def mi_factorial(mi): - import math - result = 1 - for mi_i in mi: - result *= math.factorial(mi_i) - return result - - -def mi_increment_axis(mi, axis, increment): - new_mi = list(mi) - new_mi[axis] += increment - return tuple(new_mi) - - -def mi_set_axis(mi, axis, value): - new_mi = list(mi) - new_mi[axis] = value - return tuple(new_mi) - - -def mi_power(vector, mi, evaluate=True): - result = 1 - for mi_i, vec_i in zip(mi, vector): - if mi_i == 1: - result *= vec_i - elif evaluate: - result *= vec_i**mi_i - else: - result *= sym.unevaluated_pow(vec_i, mi_i) - return result - - -def add_to_sac(sac, expr): - if sac is None: - return expr - - if isinstance(expr, (numbers.Number, sym.Number, int, - float, complex, sym.Symbol)): - return expr +from pymbolic.mapper.dependency import DependencyMapper +from pyopencl.characterize import get_pocl_version +from pytools import T, memoize_method +from pytools.tag import Tag, tag_dataclass - name = sac.assign_temp("temp", expr) - return sym.Symbol(name) +import sumpy.symbolic as sym +from sumpy.array_context import PyOpenCLArrayContext, make_loopy_program -class ExprDerivativeTaker: - """Facilitates the efficient computation of (potentially) high-order - derivatives of a given :mod:`sympy` expression *expr* while attempting - to maximize the number of common subexpressions generated. +if TYPE_CHECKING: + import numpy + from numpy.typing import DTypeLike + from optype.numpy import Array2D - This class defines the interface and realizes a baseline implementation. - More specialized implementations may offer better efficiency for special - cases. + import pyopencl + from arraycontext import ArrayContext + from pymbolic.primitives import Variable + from pymbolic.typing import Expression - .. automethod:: diff - """ + from sumpy.assignment_collection import SymbolicAssignmentCollection + from sumpy.expansion import ExpansionBase + from sumpy.kernel import KernelArgument, ScalarKernel - def __init__(self, expr, var_list, rscale=1, sac=None): - r""" - A class to take scaled derivatives of the symbolic expression - expr w.r.t. variables var_list and the scaling parameter rscale. +logger = logging.getLogger(__name__) - Consider a Taylor multipole expansion: - .. math:: +__doc__ = """ +Tools +===== - f (x - y) = \sum_{i = 0}^{\infty} (\partial_y^i f) (x - y) \big|_{y = c} - \frac{(y - c)^i}{i!} . +.. autofunction:: to_complex_dtype +.. autofunction:: is_obj_array_like +.. autoclass:: OrderedSet - Now suppose we would like to use a scaled version :math:`g` of the - kernel :math:`f`: +Multi-index Helpers +------------------- - .. math:: +.. autofunction:: add_mi +.. autofunction:: mi_factorial +.. autofunction:: mi_increment_axis +.. autofunction:: mi_set_axis +.. autofunction:: mi_power - \begin{eqnarray*} - f (x) & = & g (x / \alpha),\\ - f^{(i)} (x) & = & \frac{1}{\alpha^i} g^{(i)} (x / \alpha) . - \end{eqnarray*} +Symbolic Helpers +---------------- - where :math:`\alpha` is chosen to be on a length scale similar to - :math:`x` (for example by choosing :math:`\alpha` proporitional to the - size of the box for which the expansion is intended) so that :math:`x / - \alpha` is roughly of unit magnitude, to avoid arithmetic issues with - small arguments. This yields +.. autofunction:: add_to_sac +.. autofunction:: gather_arguments +.. autofunction:: gather_source_arguments +.. autofunction:: gather_loopy_arguments +.. autofunction:: gather_loopy_source_arguments - .. math:: +.. autoclass:: ScalingAssignmentTag +.. autoclass:: KernelComputation +.. autoclass:: KernelCacheMixin - f (x - y) = \sum_{i = 0}^{\infty} (\partial_y^i g) - \left( \frac{x - y}{\alpha} \right) \Bigg|_{y = c} - \cdot - \frac{(y - c)^i}{\alpha^i \cdot i!}. +.. autofunction:: reduced_row_echelon_form +.. autofunction:: nullspace - Observe that the :math:`(y - c)` term is now scaled to unit magnitude, - as is the argument of :math:`g`. +.. class:: KernelLike + See below. - With :math:`\xi = x / \alpha`, we find +.. autodata:: KernelLike + :no-index: - .. math:: +FFT +--- - \begin{eqnarray*} - g (\xi) & = & f (\alpha \xi),\\ - g^{(i)} (\xi) & = & \alpha^i f^{(i)} (\alpha \xi) . - \end{eqnarray*} +.. autofunction:: fft +.. autofunction:: fft_toeplitz_upper_triangular +.. autofunction:: matvec_toeplitz_upper_triangular - Generically for all kernels, :math:`f^{(i)} (\alpha \xi)` is computable - by taking a sufficient number of symbolic derivatives of :math:`f` and - providing :math:`\alpha \xi = x` as the argument. +.. autoclass:: FFTBackend + :members: +.. autofunction:: loopy_fft +.. autofunction:: get_opencl_fft_app +.. autofunction:: run_opencl_fft - Now, for some kernels, like :math:`f (x) = C \log x`, the powers of - :math:`\alpha^i` from the chain rule cancel with the ones from the - argument substituted into the kernel derivatives: +Profiling +--------- - .. math:: +.. autofunction:: get_native_event +.. autoclass:: ProfileGetter +.. autoclass:: AggregateProfilingEvent +.. autoclass:: MarkerBasedProfilingEvent - g^{(i)} (\xi) = \alpha^i f^{(i)} (\alpha \xi) = C' \cdot \alpha^i \cdot - \frac{1}{(\alpha x)^i} \quad (i > 0), +References +---------- - making them what you might call *scale-invariant*. +.. class:: DTypeLike - This derivative taker returns :math:`g^{(i)}(\xi) = \alpha^i f^{(i)}` - given :math:`f^{(0)}` as *expr* and :math:`\alpha` as :attr:`rscale`. - """ + See :data:`numpy.typing.DTypeLike`. +""" - assert isinstance(expr, sym.Basic) - self.var_list = var_list - zero_mi = (0,) * len(var_list) - self.cache_by_mi = {zero_mi: expr} - self.rscale = rscale - self.sac = sac - self.dim = len(self.var_list) - self.orig_expr = expr - def mi_dist(self, a, b): - return np.array(a, dtype=int) - np.array(b, dtype=int) +KernelLike: TypeAlias = "ScalarKernel | ExpansionBase" - def diff(self, mi): - """Take the derivative of the expression represented by - :class:`ExprDerivativeTaker`. - :param mi: multi-index representing the derivative - """ - try: - return self.cache_by_mi[mi] - except KeyError: - pass +# {{{ multi_index helpers - current_mi = self.get_closest_cached_mi(mi) - expr = self.cache_by_mi[current_mi] +def add_mi(mi1: Sequence[int], mi2: Sequence[int]) -> tuple[int, ...]: + # NOTE: these are used a lot and `tuple([])` is faster + return tuple([mi1i + mi2i for mi1i, mi2i in zip(mi1, mi2, strict=True)]) - for next_deriv, next_mi in self.get_derivative_taking_sequence( - current_mi, mi): - expr = expr.diff(next_deriv) * self.rscale - self.cache_by_mi[next_mi] = expr - return expr +def sub_mi(mi1: Sequence[int], mi2: Sequence[int]) -> tuple[int, ...]: + # NOTE: these are used a lot and `tuple([])` is faster + return tuple([mi1i - mi2i for mi1i, mi2i in zip(mi1, mi2, strict=True)]) - def get_derivative_taking_sequence(self, start_mi, end_mi): - current_mi = np.array(start_mi, dtype=int) - for idx, (mi_i, vec_i) in enumerate( - zip(self.mi_dist(end_mi, start_mi), self.var_list)): - for _ in range(1, 1 + mi_i): - current_mi[idx] += 1 - yield vec_i, tuple(current_mi) - def get_closest_cached_mi(self, mi): - return min((other_mi - for other_mi in self.cache_by_mi.keys() - if (np.array(mi) >= np.array(other_mi)).all()), - key=lambda other_mi: sum(self.mi_dist(mi, other_mi))) +def mi_factorial(mi: Sequence[int]) -> int: + import math + result = 1 + for mi_i in mi: + result *= math.factorial(mi_i) + return result -class LaplaceDerivativeTaker(ExprDerivativeTaker): - """Specialized derivative taker for Laplace potential. - """ +def mi_increment_axis( + mi: Sequence[int], axis: int, increment: int + ) -> tuple[int, ...]: + new_mi = list(mi) + new_mi[axis] += increment + return tuple(new_mi) - def __init__(self, expr, var_list, rscale=1, sac=None): - super().__init__(expr, var_list, rscale, sac) - self.scaled_var_list = [add_to_sac(self.sac, v/rscale) for v in var_list] - self.scaled_r = add_to_sac(self.sac, - sym.sqrt(sum(v**2 for v in self.scaled_var_list))) - def diff(self, mi): - """ - Implements the algorithm described in [Fernando2021] to take cartesian - derivatives of Laplace potential using recurrences. Cost of each derivative - is amortized constant. +def mi_set_axis(mi: Sequence[int], axis: int, value: int) -> tuple[int, ...]: + new_mi = list(mi) + new_mi[axis] = value + return tuple(new_mi) - .. [Fernando2021]: Fernando, I., Klöckner, A., 2021. Automatic Synthesis of - Low Complexity Translation Operators for the Fast - Multipole Method. In preparation. - """ - # Return zero for negative values. Makes the algorithm readable. - if min(mi) < 0: - return 0 - try: - return self.cache_by_mi[mi] - except KeyError: - pass - - dim = self.dim - if max(mi) == 1: - return ExprDerivativeTaker.diff(self, mi) - d = -1 - for i in range(dim): - if mi[i] >= 2: - d = i - break - assert d >= 0 - expr = 0 - for i in range(dim): - mi_minus_one = list(mi) - mi_minus_one[i] -= 1 - mi_minus_one = tuple(mi_minus_one) - mi_minus_two = list(mi) - mi_minus_two[i] -= 2 - mi_minus_two = tuple(mi_minus_two) - x = self.scaled_var_list[i] - n = mi[i] - if i == d: - if dim == 3: - expr -= (2*n - 1) * x * self.diff(mi_minus_one) - expr -= (n - 1)**2 * self.diff(mi_minus_two) - else: - expr -= 2 * x * (n - 1) * self.diff(mi_minus_one) - expr -= (n - 1) * (n - 2) * self.diff(mi_minus_two) - if n == 2 and sum(mi) == 2: - expr += 1 - else: - expr -= 2 * n * x * self.diff(mi_minus_one) - expr -= n * (n - 1) * self.diff(mi_minus_two) - expr /= self.scaled_r**2 - expr = add_to_sac(self.sac, expr) - self.cache_by_mi[mi] = expr - return expr +def mi_power( + vector: Sequence[sym.Expr], mi: Sequence[int], + evaluate: bool = True) -> sym.Expr: + result = sym.sympify(1) + for mi_i, vec_i in zip(mi, vector, strict=True): + if mi_i == 1: + result *= vec_i + elif evaluate: + result *= vec_i**mi_i + else: + result *= sym.unevaluated_pow(vec_i, mi_i) + return result -class RadialDerivativeTaker(ExprDerivativeTaker): - """Specialized derivative taker for radial expressions. - """ - def __init__(self, expr, var_list, rscale=1, sac=None): - """ - Takes the derivatives of a radial function. - """ - import sumpy.symbolic as sym - super().__init__(expr, var_list, rscale, sac) - empty_mi = (0,) * len(var_list) - self.cache_by_mi_q = {(empty_mi, 0): expr} - self.r = sym.sqrt(sum(v**2 for v in var_list)) - rsym = sym.Symbol("_r") - r_expr = expr.xreplace({self.r**2: rsym**2}) - self.is_radial = not any(r_expr.has(v) for v in var_list) - self.var_list_multiplied = [add_to_sac(sac, v * rscale) for v in var_list] - - def diff(self, mi, q=0): - """ - Implements the algorithm described in [Tausch2003] to take cartesian - derivatives of radial functions using recurrences. Cost of each derivative - is amortized linear in the degree. - - .. [Tausch2003]: Tausch, J., 2003. The fast multipole method for arbitrary - Green's functions. - Contemporary Mathematics, 329, pp.307-314. - """ - if not self.is_radial: - assert q == 0 - return ExprDerivativeTaker.diff(self, mi) - - try: - return self.cache_by_mi_q[(mi, q)] - except KeyError: - pass - - for i in range(self.dim): - if mi[i] == 1: - mi_minus_one = list(mi) - mi_minus_one[i] = 0 - mi_minus_one = tuple(mi_minus_one) - expr = self.var_list_multiplied[i] * self.diff(mi_minus_one, q=q+1) - self.cache_by_mi_q[(mi, q)] = expr - return expr - - for i in range(self.dim): - if mi[i] >= 2: - mi_minus_one = list(mi) - mi_minus_one[i] -= 1 - mi_minus_one = tuple(mi_minus_one) - mi_minus_two = list(mi) - mi_minus_two[i] -= 2 - mi_minus_two = tuple(mi_minus_two) - expr = (mi[i]-1)*self.diff(mi_minus_two, q=q+1) * self.rscale ** 2 - expr += self.var_list_multiplied[i] * self.diff(mi_minus_one, q=q+1) - expr = add_to_sac(self.sac, expr) - self.cache_by_mi_q[(mi, q)] = expr - return expr - - assert mi == (0,)*self.dim - assert q > 0 - - prev_expr = self.diff(mi, q=q-1) - # Need to get expr.diff(r)/r, but we can only do expr.diff(x) - # Use expr.diff(x) = expr.diff(r) * x / r - expr = prev_expr.diff(self.var_list[0])/self.var_list[0] - # We need to distribute the division above - expr = expr.expand(deep=False) - self.cache_by_mi_q[(mi, q)] = expr +def add_to_sac(sac: SymbolicAssignmentCollection | None, expr: sym.Expr): + if sac is None: return expr - -class HelmholtzDerivativeTaker(RadialDerivativeTaker): - """Specialized derivative taker for Helmholtz potential. - """ - - def diff(self, mi, q=0): - import sumpy.symbolic as sym - if q < 2 or mi != (0,)*self.dim: - return RadialDerivativeTaker.diff(self, mi, q) - - try: - return self.cache_by_mi_q[(mi, q)] - except KeyError: - pass - - if self.dim == 2: - # See https://dlmf.nist.gov/10.6.E6 - # and https://dlmf.nist.gov/10.6#E1 - k = self.orig_expr.args[1] / self.r - expr = (-2*(q - 1) * self.diff(mi, q - 1) - - k**2 * self.diff(mi, q - 2)) / self.r**2 - else: - # See reference [Tausch2003] in RadialDerivativeTaker.diff - # Note that there is a typo in the paper where - # -k**2/r is given instead of -k**2/r**2. - k = (self.orig_expr * self.r).args[-1] / sym.I / self.r - expr = (-(2*q - 1) * self.diff(mi, q - 1) - - k**2 * self.diff(mi, q - 2)) / self.r**2 - self.cache_by_mi_q[(mi, q)] = expr + from numbers import Number + if isinstance(expr, Number | sym.Number | sym.Symbol): return expr - -DerivativeCoeffDict = Dict[Tuple[int], Any] - - -@tag_dataclass -class DifferentiatedExprDerivativeTaker: - """Implements the :class:`ExprDerivativeTaker` interface - for an expression that is itself a linear combination of - derivatives of a base expression. To take the actual derivatives, - it makes use of an underlying derivative taker *taker*. - - .. attribute:: taker - A :class:`ExprDerivativeTaker` for the base expression. - - .. attribute:: derivative_coeff_dict - A dictionary mapping a derivative multi-index to a coefficient. - The expression represented by this derivative taker is the linear - combination of the derivatives of the expression for the - base expression. - """ - taker: ExprDerivativeTaker - derivative_coeff_dict: DerivativeCoeffDict - - def diff(self, mi, save_intermediate=lambda x: x): - # By passing `rscale` to the derivative taker we are taking a scaled - # version of the derivative which is `expr.diff(mi)*rscale**sum(mi)` - # which might be implemented efficiently for kernels like Laplace. - # One caveat is that we are taking more derivatives because of - # :attr:`derivative_coeff_dict` which would multiply the - # expression by more `rscale`s than necessary. This is corrected by - # dividing by `rscale`. - max_order = max(sum(extra_mi) for extra_mi in - self.derivative_coeff_dict.keys()) - - result = sum( - coeff * self.taker.diff(add_mi(mi, extra_mi)) - / self.taker.rscale ** (sum(extra_mi) - max_order) - for extra_mi, coeff in self.derivative_coeff_dict.items()) - - return result * save_intermediate(1 / self.taker.rscale ** max_order) - - -def diff_derivative_coeff_dict(derivative_coeff_dict: DerivativeCoeffDict, - variable_idx, variables): - """Differentiate a derivative transformation dictionary given by - *derivative_coeff_dict* using the variable given by **variable_idx** - and return a new derivative transformation dictionary. - """ - new_derivative_coeff_dict = defaultdict(lambda: 0) - for mi, coeff in derivative_coeff_dict.items(): - # In the case where we have x * u.diff(x), the result should - # be x.diff(x) + x * u.diff(x, x) - # Calculate the first term by differentiating the coefficients - new_coeff = sym.sympify(coeff).diff(variables[variable_idx]) - new_derivative_coeff_dict[mi] += new_coeff - # Next calculate the second term by differentiating the derivatives - new_mi = list(mi) - new_mi[variable_idx] += 1 - new_derivative_coeff_dict[tuple(new_mi)] += coeff - return {derivative: coeff for derivative, coeff in - new_derivative_coeff_dict.items() if coeff != 0} + name = sac.assign_temp("temp", expr) + return sym.Symbol(name) # }}} # {{{ get variables -class GatherAllVariables(WalkMapper): - def __init__(self): - self.vars = set() - - def map_variable(self, expr): - self.vars.add(expr) - - -def get_all_variables(expr): - mapper = GatherAllVariables() - mapper(expr) - return mapper.vars +def get_all_variables(expr: Expression) -> AbstractSet[Variable]: + return cast("AbstractSet[Variable]", DependencyMapper()(expr)) # }}} @@ -483,7 +211,7 @@ def build_matrix(op, dtype=None, shape=None): dtype = dtype or op.dtype from pytools import ProgressBar shape = shape or op.shape - rows, cols = shape + _rows, cols = shape pb = ProgressBar("matrix", cols) mat = np.zeros(shape, dtype) @@ -503,40 +231,17 @@ def build_matrix(op, dtype=None, shape=None): return mat -def vector_to_device(queue, vec): - from pytools.obj_array import obj_array_vectorize - - from pyopencl.array import to_device - - def to_dev(ary): - return to_device(queue, ary) - - return obj_array_vectorize(to_dev, vec) - - -def vector_from_device(queue, vec): - from pytools.obj_array import obj_array_vectorize - - def from_dev(ary): - from numbers import Number - if isinstance(ary, (np.number, Number)): - # zero, most likely - return ary - - return ary.get(queue=queue) - - return obj_array_vectorize(from_dev, vec) - - -def _merge_kernel_arguments(dictionary, arg): +def _merge_kernel_arguments( + dictionary: dict[str, KernelArgument], + arg: KernelArgument): # Check for strict equality until there's a usecase if dictionary.setdefault(arg.name, arg) != arg: msg = "Merging two different kernel arguments {} and {} with the same name" - raise ValueError(msg.format(arg.loopy_arg, dictionary[arg].loopy_arg)) + raise ValueError(msg.format(arg.loopy_arg, dictionary[arg.name].loopy_arg)) -def gather_arguments(kernel_likes): - result = {} +def gather_arguments(kernel_likes: Sequence[KernelLike]): + result: dict[str, KernelArgument] = {} for knl in kernel_likes: for arg in knl.get_args(): _merge_kernel_arguments(result, arg) @@ -544,20 +249,20 @@ def gather_arguments(kernel_likes): return sorted(result.values(), key=lambda arg: arg.name) -def gather_source_arguments(kernel_likes): - result = {} +def gather_source_arguments(kernel_likes: Sequence[KernelLike]): + result: dict[str, KernelArgument] = {} for knl in kernel_likes: - for arg in knl.get_args() + knl.get_source_args(): + for arg in [*knl.get_args(), *knl.get_source_args()]: _merge_kernel_arguments(result, arg) return sorted(result.values(), key=lambda arg: arg.name) -def gather_loopy_arguments(kernel_likes): +def gather_loopy_arguments(kernel_likes: Sequence[KernelLike]): return [arg.loopy_arg for arg in gather_arguments(kernel_likes)] -def gather_loopy_source_arguments(kernel_likes): +def gather_loopy_source_arguments(kernel_likes: Sequence[KernelLike]): return [arg.loopy_arg for arg in gather_source_arguments(kernel_likes)] @@ -568,16 +273,31 @@ class ScalingAssignmentTag(Tag): pass -class KernelComputation: - """Common input processing for kernel computations.""" +class KernelComputation(ABC): + """Common input processing for kernel computations. + + .. attribute:: name + .. attribute:: target_kernels + .. attribute:: source_kernels + .. attribute:: strength_usage - def __init__(self, ctx, target_kernels, source_kernels, strength_usage, - value_dtypes, name, device=None): + .. automethod:: get_kernel + """ + + def __init__(self, + target_kernels: list[ScalarKernel], + source_kernels: list[ScalarKernel], + strength_usage: list[int] | None = None, + value_dtypes: list[numpy.dtype[Any]] | None = None, + name: str | None = None) -> None: """ - :arg kernels: list of :class:`sumpy.kernel.Kernel` instances - :class:`sumpy.kernel.TargetDerivative` wrappers should be + :arg target_kernels: list of :class:`~sumpy.kernel.ScalarKernel` instances, + with :class:`sumpy.kernel.AxisTargetDerivative` as the outermost kernel wrappers, if present. - :arg strength_usage: A list of integers indicating which expression + :arg source_kernels: list of :class:`~sumpy.kernel.ScalarKernel` instances + with :class:`~sumpy.kernel.DirectionalSourceDerivative` as the + outermost kernel wrappers, if present. + :arg strength_usage: list of integers indicating which expression uses which density. This implicitly specifies the number of density arrays that need to be passed. Default: all kernels use the same density. @@ -589,11 +309,11 @@ def __init__(self, ctx, target_kernels, source_kernels, strength_usage, value_dtypes = [] for knl in target_kernels: if knl.is_complex_valued: - value_dtypes.append(np.complex128) + value_dtypes.append(np.dtype(np.complex128)) else: - value_dtypes.append(np.float64) + value_dtypes.append(np.dtype(np.float64)) - if not isinstance(value_dtypes, (list, tuple)): + if not isinstance(value_dtypes, Sequence): value_dtypes = [np.dtype(value_dtypes)] * len(target_kernels) value_dtypes = [np.dtype(vd) for vd in value_dtypes] @@ -610,12 +330,6 @@ def __init__(self, ctx, target_kernels, source_kernels, strength_usage, # }}} - if device is None: - device = ctx.devices[0] - - self.context = ctx - self.device = device - self.source_kernels = tuple(source_kernels) self.target_kernels = tuple(target_kernels) self.value_dtypes = value_dtypes @@ -624,19 +338,32 @@ def __init__(self, ctx, target_kernels, source_kernels, strength_usage, self.name = name or self.default_name + @property + def nresults(self): + return len(self.target_kernels) + + @property + @abstractmethod + def default_name(self) -> str: + pass + def get_kernel_scaling_assignments(self): from sumpy.symbolic import SympyToPymbolicMapper sympy_conv = SympyToPymbolicMapper() import loopy as lp return [ - lp.Assignment(id=None, + lp.Assignment(id=f"knl_{i}_scaling", assignee=f"knl_{i}_scaling", expression=sympy_conv(kernel.get_global_scaling_const()), temp_var_type=lp.Optional(dtype), tags=frozenset([ScalingAssignmentTag()])) for i, (kernel, dtype) in enumerate( - zip(self.target_kernels, self.value_dtypes))] + zip(self.target_kernels, self.value_dtypes, strict=True))] + + @abstractmethod + def get_kernel(self) -> lp.TranslationUnit: + pass # }}} @@ -647,80 +374,111 @@ def get_kernel_scaling_assignments(self): # Author: Raymond Hettinger # License: MIT -try: - from collections.abc import MutableSet -except ImportError: - from collections import MutableSet +from collections.abc import MutableSet + +Link: TypeAlias = "list[Any]" -class OrderedSet(MutableSet): - def __init__(self, iterable=None): +class OrderedSet(MutableSet[T]): + end: Link + map: dict[T, Link] + + def __init__(self, iterable: Iterable[T] | None = None) -> None: self.end = end = [] end += [None, end, end] # sentinel node for doubly linked list self.map = {} # key --> [key, prev, next] + if iterable is not None: self |= iterable - def __len__(self): + @override + def __len__(self) -> int: return len(self.map) - def __contains__(self, key): + @override + def __contains__(self, key: object) -> bool: return key in self.map - def add(self, key): - if key not in self.map: + @override + def add(self, value: T) -> None: + if value not in self.map: end = self.end curr = end[1] - curr[2] = end[1] = self.map[key] = [key, curr, end] + curr[2] = end[1] = self.map[value] = [value, curr, end] - def discard(self, key): - if key in self.map: - key, prev, next = self.map.pop(key) + @override + def discard(self, value: T) -> None: + if value in self.map: + _key, prev, next = self.map.pop(value) prev[2] = next next[1] = prev - def __iter__(self): + @override + def __iter__(self) -> Iterator[T]: end = self.end curr = end[2] while curr is not end: yield curr[0] curr = curr[2] - def __reversed__(self): + def __reversed__(self) -> Iterator[T]: end = self.end curr = end[1] while curr is not end: yield curr[0] curr = curr[1] - def pop(self, last=True): + @override + def pop(self, last: bool = True) -> T: if not self: raise KeyError("set is empty") + key = self.end[1][0] if last else self.end[2][0] self.discard(key) + return key - def __repr__(self): + @override + def __repr__(self) -> str: if not self: return f"{self.__class__.__name__}()" + return f"{self.__class__.__name__}({list(self)!r})" - def __eq__(self, other): + @override + def __eq__(self, other: object) -> bool: if isinstance(other, OrderedSet): return len(self) == len(other) and list(self) == list(other) + return set(self) == set(other) # }}} -class KernelCacheWrapper: +class KernelCacheMixin(ABC): + name: str + + @abstractmethod + def get_cache_key(self) -> tuple[Hashable, ...]: + ... + + @abstractmethod + def get_kernel(self, **kwargs: Any) -> lp.TranslationUnit: + ... + + @abstractmethod + def get_optimized_kernel(self, **kwargs: Any) -> lp.TranslationUnit: + ... + @memoize_method - def get_cached_optimized_kernel(self, **kwargs): - from sumpy import code_cache, CACHING_ENABLED, OPT_ENABLED + def get_cached_kernel(self, **kwargs) -> lp.TranslationUnit: + from sumpy import CACHING_ENABLED, NO_CACHE_KERNELS, OPT_ENABLED, code_cache - if CACHING_ENABLED: + if CACHING_ENABLED and not ( + NO_CACHE_KERNELS and self.name in NO_CACHE_KERNELS): import loopy.version + from sumpy.version import KERNEL_VERSION cache_key = ( self.get_cache_key() @@ -731,16 +489,16 @@ def get_cached_optimized_kernel(self, **kwargs): try: result = code_cache[cache_key] - logger.debug("{}: kernel cache hit [key={}]".format( - self.name, cache_key)) + logger.debug("%s: kernel cache hit [key=%s]", self.name, cache_key) return result except KeyError: pass logger.info("%s: kernel cache miss", self.name) - if CACHING_ENABLED: - logger.info("{}: kernel cache miss [key={}]".format( - self.name, cache_key)) + if CACHING_ENABLED and not ( + NO_CACHE_KERNELS and self.name in NO_CACHE_KERNELS): + logger.info("%s: kernel cache miss [key=%s]", + self.name, cache_key) from pytools import MinRecursionLimit with MinRecursionLimit(3000): @@ -749,27 +507,33 @@ def get_cached_optimized_kernel(self, **kwargs): else: knl = self.get_kernel() - if CACHING_ENABLED: + if CACHING_ENABLED and not ( + NO_CACHE_KERNELS and self.name in NO_CACHE_KERNELS): code_cache.store_if_not_present(cache_key, knl) return knl @staticmethod - def _allow_redundant_execution_of_knl_scaling(knl): + def _allow_redundant_execution_of_knl_scaling( + knl: lp.TranslationUnit + ) -> lp.TranslationUnit: from loopy.match import ObjTagged return lp.add_inames_for_unused_hw_axes( knl, within=ObjTagged(ScalingAssignmentTag())) +KernelCacheWrapper = KernelCacheMixin + + def is_obj_array_like(ary): return ( - isinstance(ary, (tuple, list)) + isinstance(ary, tuple | list) or (isinstance(ary, np.ndarray) and ary.dtype.char == "O")) # {{{ matrices -def reduced_row_echelon_form(m, atol=0): +def reduced_row_echelon_form(m: Array2D[Any], atol: float = 0): """Calculates a reduced row echelon form of a matrix `m`. @@ -807,12 +571,12 @@ def reduced_row_echelon_form(m, atol=0): pivot_cols.append(i) scale = mat[index, i] - if isinstance(scale, (int, sym.Integer)): + if isinstance(scale, int | sym.Integer): scale = int(scale) for j in range(mat.shape[1]): elem = mat[index, j] - if isinstance(scale, int) and isinstance(elem, (int, sym.Integer)): + if isinstance(scale, int) and isinstance(elem, int | sym.Integer): quo = int(elem) // scale if quo * scale == elem: mat[index, j] = quo @@ -832,7 +596,7 @@ def reduced_row_echelon_form(m, atol=0): return mat, pivot_cols -def nullspace(m, atol=0): +def nullspace(m: Array2D[Any], atol: float = 0): """Calculates the nullspace of a matrix `m`. :arg m: a 2D :class:`numpy.ndarray` or a list of lists or a sympy Matrix @@ -850,7 +614,7 @@ def nullspace(m, atol=0): vec = [0]*cols vec[free_var] = 1 for piv_row, piv_col in enumerate(pivot_cols): - for pos in pivot_cols[piv_row+1:] + [free_var]: + for pos in (*pivot_cols[piv_row+1:], free_var): if isinstance(mat[piv_row, pos], sym.Integer): vec[piv_col] -= int(mat[piv_row, pos]) else: @@ -863,7 +627,13 @@ def nullspace(m, atol=0): # {{{ FFT -def fft(seq, inverse=False, sac=None): +# FIXME(pyright): this function can take `ndarrays` and sequences of other types +# as well. The current types are just for uses in `sumpy.expansion.m2l` (same +# for `fft_toeplitz_upper_triangular` and `matvec_toeplitz_upper_triangular`) + +def fft(seq: Sequence[sym.Expr], + inverse: bool = False, + sac: SymbolicAssignmentCollection | None = None) -> Sequence[sym.Expr]: """ Return the discrete fourier transform of the sequence seq. seq should be a python iterable with tuples of length 2 @@ -872,21 +642,31 @@ def fft(seq, inverse=False, sac=None): from pymbolic.algorithm import fft as _fft, ifft as _ifft - def wrap(level, expr): + def wrap(level: int, expr: sym.Expr) -> sym.Expr: if isinstance(expr, np.ndarray): res = [wrap(level, a) for a in expr] return np.array(res, dtype=object).reshape(expr.shape) - return add_to_sac(sac, expr) + else: + return add_to_sac(sac, expr) if inverse: - return _ifft(np.array(seq), wrap_intermediate_with_level=wrap, - complex_dtype=np.complex128).tolist() + result = _ifft( + np.array(seq), + wrap_intermediate_with_level=wrap, + complex_dtype=np.complex128) else: - return _fft(np.array(seq), wrap_intermediate_with_level=wrap, - complex_dtype=np.complex128).tolist() + result = _fft( + np.array(seq), + wrap_intermediate_with_level=wrap, + complex_dtype=np.complex128) + + return result.tolist() -def fft_toeplitz_upper_triangular(first_row, x, sac=None): +def fft_toeplitz_upper_triangular( + first_row: Sequence[sym.Expr], + x: Sequence[sym.Expr], + sac: SymbolicAssignmentCollection | None = None) -> Sequence[sym.Expr]: """ Returns the matvec of the Toeplitz matrix given by the first row and the vector x using a Fourier transform @@ -896,27 +676,32 @@ def fft_toeplitz_upper_triangular(first_row, x, sac=None): v = list(first_row) v += [0]*(n-1) - x = list(reversed(x)) - x += [0]*(n-1) + y = list(reversed(x)) + y += [0]*(n-1) - v_fft = fft(v, sac) - x_fft = fft(x, sac) - res_fft = [add_to_sac(sac, a * b) for a, b in zip(v_fft, x_fft)] + v_fft = fft(v, sac=sac) # pyright: ignore[reportArgumentType] + x_fft = fft(y, sac=sac) # pyright: ignore[reportArgumentType] + res_fft = [add_to_sac(sac, a * b) for a, b in zip(v_fft, x_fft, strict=True)] res = fft(res_fft, inverse=True, sac=sac) return list(reversed(res[:n])) -def matvec_toeplitz_upper_triangular(first_row, vector): +def matvec_toeplitz_upper_triangular( + first_row: Sequence[sym.Expr], + vector: Sequence[sym.Expr], + ) -> Sequence[sym.Expr]: n = len(first_row) assert len(vector) == n - output = [0]*n + + output: list[sym.Expr] = [sym.sympify(0)] * n for row in range(n): - terms = tuple([first_row[col-row]*vector[col] for col in range(row, n)]) + terms = tuple(first_row[col-row]*vector[col] for col in range(row, n)) output[row] = sym.Add(*terms) + return output -to_complex_type_dict = { +to_complex_type_dict: dict[type[Any], type[np.complexfloating]] = { np.complex64: np.complex64, np.complex128: np.complex128, np.float32: np.complex64, @@ -924,12 +709,399 @@ def matvec_toeplitz_upper_triangular(first_row, vector): } -def to_complex_dtype(dtype): +def to_complex_dtype(dtype: DTypeLike) -> np.dtype[np.complexfloating]: np_type = np.dtype(dtype).type try: - return to_complex_type_dict[np_type] - except KeyError: - raise RuntimeError(f"Unknown dtype: {dtype}") + return np.dtype(to_complex_type_dict[np_type]) + except KeyError as err: + raise RuntimeError(f"Unknown dtype: {dtype}") from err + + +@dataclass(frozen=True) +class ProfileGetter: + start: int + end: int + + +def get_native_event(evt): + from pyopencl import Event + return evt if isinstance(evt, Event) else evt.native_event + + +class AggregateProfilingEvent: + """An object to hold a list of events and provides compatibility + with some of the functionality of :class:`pyopencl.Event`. + Assumes that the last event waits on all of the previous events. + """ + def __init__(self, events): + self.events = events[:] + self.native_event = get_native_event(events[-1]) + + @property + def profile(self): + total = sum(evt.profile.end - evt.profile.start for evt in self.events) + end = self.native_event.profile.end + return ProfileGetter(start=end - total, end=end) + + def wait(self): + return self.native_event.wait() + + +class MarkerBasedProfilingEvent: + """An object to hold two marker events and provides compatibility + with some of the functionality of :class:`pyopencl.Event`. + """ + def __init__(self, *, end_event, start_event): + self.native_event = end_event + self.start_event = start_event + + @property + def profile(self): + return ProfileGetter(start=self.start_event.profile.start, + end=self.native_event.profile.end) + + def wait(self): + return self.native_event.wait() + + +def loopy_fft( + n: int, + *, n_batch_dims: int, + inverse: bool, + complex_dtype: DTypeLike, + index_dtype: DTypeLike | None = None, + name: str | None = None + ): + from math import pi + + from pymbolic import var + from pymbolic.algorithm import find_factors + + complex_dtype = np.dtype(complex_dtype) + + sign = 1 if not inverse else -1 + + m = n + factors = [] + while m != 1: + N1, m = find_factors(m) # ruff:ignore[non-lowercase-variable-in-function] + factors.append(N1) + + nfft = n + + batch_dims = tuple(var(f"j{d}") for d in range(n_batch_dims)) + + domains = [ + "{[i]: 0<=i FFTBackend: + import os + + env_val = os.environ.get("SUMPY_FFT_BACKEND") + if env_val: + if env_val not in ["loopy", "pyvkfft"]: + raise ValueError("Expected 'loopy' or 'pyvkfft' for SUMPY_FFT_BACKEND. " + f"Found {env_val}.") + return FFTBackend[env_val] + + try: + import pyvkfft.opencl # ruff:ignore[unused-import] + except ImportError: + warnings.warn("VkFFT not found. FFT runs will be slower.", stacklevel=3) + return FFTBackend.loopy + + from pyopencl import command_queue_properties + + if queue.properties & command_queue_properties.OUT_OF_ORDER_EXEC_MODE_ENABLE: + warnings.warn( + "VkFFT does not support out of order queues yet. " + "Falling back to slower implementation.", stacklevel=3) + return FFTBackend.loopy + + import platform + import sys + + pocl_ver = get_pocl_version(queue.device.platform) + if pocl_ver is not None: + if pocl_ver >= (7,): + warnings.warn( + "PoCL>=7 miscompiles VkFFT. " + "See https://github.com/pocl/pocl/issues/2069 for details. " + "Falling back to slower implementation.", stacklevel=3) + return FFTBackend.loopy + + if (sys.platform == "darwin" + and platform.machine() == "x86_64"): + warnings.warn( + "PoCL crashes on some VkFFT kernels on MacOS. " + "See https://github.com/inducer/sumpy/issues/129. " + "Falling back to slower implementation.", stacklevel=3) + return FFTBackend.loopy + + return FFTBackend.pyvkfft + + +def get_opencl_fft_app( + actx: ArrayContext, + shape: tuple[int, ...], + dtype: numpy.dtype[Any], + inverse: bool) -> Any: + """Setup an object for out-of-place FFT on with given shape and dtype + on given queue. + """ + assert isinstance(actx, PyOpenCLArrayContext) + assert dtype.type in (np.float32, np.float64, np.complex64, + np.complex128) + + backend = _get_fft_backend(actx.queue) + + if backend == FFTBackend.loopy: + return loopy_fft( + shape[-1], + n_batch_dims=len(shape) - 1, + inverse=inverse, + complex_dtype=dtype.type), backend + elif backend == FFTBackend.pyvkfft: + from pyvkfft.opencl import VkFFTApp + app = VkFFTApp( + shape=shape, dtype=dtype, # pyright: ignore[reportArgumentType] + queue=actx.queue, ndim=1, inplace=False) + return app, backend + else: + raise RuntimeError(f"Unsupported FFT backend {backend}") + + +def run_opencl_fft( + actx: ArrayContext, + fft_app: tuple[Any, FFTBackend], + input_vec: Any, + inverse: bool = False, + wait_for: list[pyopencl.Event] | None = None + ) -> tuple[pyopencl.Event | MarkerBasedProfilingEvent, Any]: + """Runs an FFT on input_vec and returns a :class:`MarkerBasedProfilingEvent` + that indicate the end and start of the operations carried out and the output + vector. + Only supports in-order queues. + """ + assert isinstance(actx, PyOpenCLArrayContext) + + app, backend = fft_app + + if backend == FFTBackend.loopy: + evt, output_vec = app(actx.queue, y=input_vec, wait_for=wait_for) + return (evt, output_vec["x"]) + elif backend == FFTBackend.pyvkfft: + if wait_for is None: + wait_for = [] + + import pyopencl as cl + + queue = actx.queue + if queue.device.platform.name == "NVIDIA CUDA": + # NVIDIA OpenCL gives wrong event profile values with wait_for + # Not passing wait_for will wait for all events queued before + # and therefore correctness is preserved if it's the same queue + for evt in wait_for: + if evt.command_queue != queue: + raise RuntimeError( + "Different queues not supported with NVIDIA CUDA") + start_evt = cl.enqueue_marker(queue) + else: + start_evt = cl.enqueue_marker(queue, wait_for=wait_for[:]) + + if app.inplace: + raise RuntimeError("inplace fft is not supported") + else: + # FIXME: All very imperative. FFT functionality should move into the actx? + output_vec = actx.np.zeros_like(input_vec) + + meth = app.ifft if inverse else app.fft + + meth(input_vec, output_vec, queue=queue) + + if queue.device.platform.name == "NVIDIA CUDA": + end_evt = cl.enqueue_marker(queue) + else: + end_evt = cl.enqueue_marker(queue, wait_for=[start_evt]) + + output_vec.add_event(end_evt) + + return (MarkerBasedProfilingEvent(end_event=end_evt, start_event=start_evt), + output_vec) + else: + raise RuntimeError(f"Unsupported FFT backend {backend}") + +# }}} + + +# {{{ deprecations + +_depr_name_to_replacement_and_obj = { + "KernelCacheWrapper": ("KernelCacheMixin", 2023), + } + + +def __getattr__(name: str): + replacement_and_obj = _depr_name_to_replacement_and_obj.get(name) + if replacement_and_obj is not None: + replacement, obj, year = replacement_and_obj + from warnings import warn + warn(f"'sumpy.tools.{name}' is deprecated. " + f"Use '{replacement}' instead. " + f"'sumpy.tools.{name}' will continue to work until {year}.", + DeprecationWarning, stacklevel=2) + return obj + else: + raise AttributeError(name) # }}} diff --git a/sumpy/toys.py b/sumpy/toys.py index 3b8876dbc..8ff1f88c7 100644 --- a/sumpy/toys.py +++ b/sumpy/toys.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = """ Copyright (C) 2017 Andreas Kloeckner Copyright (C) 2017 Matt Wala @@ -23,15 +26,34 @@ THE SOFTWARE. """ -from pytools import memoize_method +import logging +from functools import partial from numbers import Number +from typing import TYPE_CHECKING, Any + +import numpy as np +from typing_extensions import override + +from pytools import memoize_method, obj_array + from sumpy.kernel import TargetTransformationRemover -import numpy as np # noqa: F401 -import loopy as lp # noqa: F401 -import pyopencl as cl -import logging +if TYPE_CHECKING: + from collections.abc import Mapping, Sequence + + from arraycontext import ArrayContext + + from sumpy.expansion import ( + ExpansionFactoryBase, + LocalExpansionFactory, + MultipoleExpansionFactory, + ) + from sumpy.expansion.m2l import M2LTranslationClassFactoryBase + from sumpy.kernel import ScalarKernel + from sumpy.visualization import FieldPlotter + + logger = logging.getLogger(__name__) __doc__ = """ @@ -52,6 +74,7 @@ .. autofunction:: combine_inner_outer .. autofunction:: combine_halfspace .. autofunction:: combine_halfspace_and_outer +.. autofunction:: l_inf These functions help with plotting: @@ -68,6 +91,7 @@ .. autoclass:: ExpansionPotentialSource .. autoclass:: MultipoleExpansion .. autoclass:: LocalExpansion +.. autoclass:: PotentialExpressionNode .. autoclass:: Sum .. autoclass:: Product .. autoclass:: SchematicVisitor @@ -83,28 +107,56 @@ class ToyContext: .. automethod:: __init__ """ - - def __init__(self, cl_context, kernel, - mpole_expn_class=None, - local_expn_class=None, - expansion_factory=None, - extra_source_kwargs=None, - extra_kernel_kwargs=None): - self.cl_context = cl_context - self.queue = cl.CommandQueue(self.cl_context) + kernel: ScalarKernel + no_target_deriv_kernel: ScalarKernel + + mpole_expn_class: MultipoleExpansionFactory + local_expn_class: LocalExpansionFactory + + extra_source_kwargs: Mapping[str, object] + extra_kernel_kwargs: Mapping[str, object] + extra_source_and_kernel_kwargs: Mapping[str, object] + + def __init__(self, + kernel: ScalarKernel, + mpole_expn_class: MultipoleExpansionFactory | None = None, + local_expn_class: LocalExpansionFactory | None = None, + expansion_factory: ExpansionFactoryBase | None = None, + extra_source_kwargs: Mapping[str, object] | None = None, + extra_kernel_kwargs: Mapping[str, object] | None = None, + m2l_use_fft: bool | None = None): self.kernel = kernel - self.no_target_deriv_kernel = TargetTransformationRemover()(kernel) if expansion_factory is None: from sumpy.expansion import DefaultExpansionFactory expansion_factory = DefaultExpansionFactory() + if mpole_expn_class is None: - mpole_expn_class = \ - expansion_factory.get_multipole_expansion_class(kernel) + mpole_expn_class = ( + expansion_factory.get_multipole_expansion_class(kernel)) + if local_expn_class is None: + from sumpy.expansion.m2l import ( + FFTM2LTranslationClassFactory, + NonFFTM2LTranslationClassFactory, + ) + if m2l_use_fft: + m2l_translation_class_factory: M2LTranslationClassFactoryBase = \ + FFTM2LTranslationClassFactory() + else: + m2l_translation_class_factory = NonFFTM2LTranslationClassFactory() local_expn_class = \ expansion_factory.get_local_expansion_class(kernel) + m2l_translation_class = \ + m2l_translation_class_factory.get_m2l_translation_class( + kernel, local_expn_class) + local_expn_class = partial(local_expn_class, + m2l_translation_override=m2l_translation_class()) + assert local_expn_class is not None + elif m2l_use_fft is not None: + raise ValueError("local_expn_class and m2l_use_fft are both supplied. " + "Use only one of these arguments") self.mpole_expn_class = mpole_expn_class self.local_expn_class = local_expn_class @@ -117,61 +169,104 @@ def __init__(self, cl_context, kernel, self.extra_source_kwargs = extra_source_kwargs self.extra_kernel_kwargs = extra_kernel_kwargs - extra_source_and_kernel_kwargs = extra_source_kwargs.copy() + extra_source_and_kernel_kwargs = dict(extra_source_kwargs) extra_source_and_kernel_kwargs.update(extra_kernel_kwargs) self.extra_source_and_kernel_kwargs = extra_source_and_kernel_kwargs @memoize_method def get_p2p(self): from sumpy.p2p import P2P - return P2P(self.cl_context, (self.kernel,), exclude_self=False) + return P2P((self.kernel,), exclude_self=False) @memoize_method - def get_p2m(self, order): + def get_p2m(self, order: int): from sumpy import P2EFromSingleBox - return P2EFromSingleBox(self.cl_context, + return P2EFromSingleBox( self.mpole_expn_class(self.no_target_deriv_kernel, order), - kernels=(self.kernel,)) + kernels=(self.no_target_deriv_kernel,)) @memoize_method - def get_p2l(self, order): + def get_p2l(self, order: int): from sumpy import P2EFromSingleBox - return P2EFromSingleBox(self.cl_context, + return P2EFromSingleBox( self.local_expn_class(self.no_target_deriv_kernel, order), - kernels=(self.kernel,)) + kernels=(self.no_target_deriv_kernel,)) @memoize_method - def get_m2p(self, order): + def get_m2p(self, order: int): from sumpy import E2PFromSingleBox - return E2PFromSingleBox(self.cl_context, + return E2PFromSingleBox( self.mpole_expn_class(self.no_target_deriv_kernel, order), (self.kernel,)) @memoize_method - def get_l2p(self, order): + def get_l2p(self, order: int): from sumpy import E2PFromSingleBox - return E2PFromSingleBox(self.cl_context, + return E2PFromSingleBox( self.local_expn_class(self.no_target_deriv_kernel, order), (self.kernel,)) @memoize_method - def get_m2m(self, from_order, to_order): + def get_m2m(self, from_order: int, to_order: int): from sumpy import E2EFromCSR - return E2EFromCSR(self.cl_context, + return E2EFromCSR( self.mpole_expn_class(self.no_target_deriv_kernel, from_order), self.mpole_expn_class(self.no_target_deriv_kernel, to_order)) @memoize_method - def get_m2l(self, from_order, to_order): - from sumpy import E2EFromCSR - return E2EFromCSR(self.cl_context, + def use_translation_classes_dependent_data(self): + l_expn = self.local_expn_class(self.no_target_deriv_kernel, 2) + return l_expn.m2l_translation.use_preprocessing + + @memoize_method + def use_fft(self): + l_expn = self.local_expn_class(self.no_target_deriv_kernel, 2) + return l_expn.m2l_translation.use_fft + + @memoize_method + def get_m2l(self, from_order: int, to_order: int): + from sumpy import E2EFromCSR, M2LUsingTranslationClassesDependentData + if self.use_translation_classes_dependent_data(): + m2l_class = M2LUsingTranslationClassesDependentData + else: + m2l_class = E2EFromCSR + return m2l_class( self.mpole_expn_class(self.no_target_deriv_kernel, from_order), self.local_expn_class(self.no_target_deriv_kernel, to_order)) @memoize_method - def get_l2l(self, from_order, to_order): + def get_m2l_translation_class_dependent_data_kernel(self, + from_order: int, + to_order: int): + from sumpy import M2LGenerateTranslationClassesDependentData + return M2LGenerateTranslationClassesDependentData( + self.mpole_expn_class(self.no_target_deriv_kernel, from_order), + self.local_expn_class(self.no_target_deriv_kernel, to_order)) + + @memoize_method + def get_m2l_expansion_size(self, from_order: int, to_order: int): + m_expn = self.mpole_expn_class(self.no_target_deriv_kernel, from_order) + l_expn = self.local_expn_class(self.no_target_deriv_kernel, to_order) + return l_expn.m2l_translation.preprocess_multipole_nexprs(l_expn, m_expn) + + @memoize_method + def get_m2l_preprocess_mpole_kernel(self, from_order: int, to_order: int): + from sumpy import M2LPreprocessMultipole + return M2LPreprocessMultipole( + self.mpole_expn_class(self.no_target_deriv_kernel, from_order), + self.local_expn_class(self.no_target_deriv_kernel, to_order)) + + @memoize_method + def get_m2l_postprocess_local_kernel(self, from_order: int, to_order: int): + from sumpy import M2LPostprocessLocal + return M2LPostprocessLocal( + self.mpole_expn_class(self.no_target_deriv_kernel, from_order), + self.local_expn_class(self.no_target_deriv_kernel, to_order)) + + @memoize_method + def get_l2l(self, from_order: int, to_order: int): from sumpy import E2EFromCSR - return E2EFromCSR(self.cl_context, + return E2EFromCSR( self.local_expn_class(self.no_target_deriv_kernel, from_order), self.local_expn_class(self.no_target_deriv_kernel, to_order)) @@ -180,51 +275,56 @@ def get_l2l(self, from_order, to_order): # {{{ helpers -def _p2e(psource, center, rscale, order, p2e, expn_class, expn_kwargs): - source_boxes = np.array([0], dtype=np.int32) - box_source_starts = np.array([0], dtype=np.int32) - box_source_counts_nonchild = np.array( - [psource.points.shape[-1]], dtype=np.int32) - +def _p2e(actx, psource, center, rscale, order: int, p2e, expn_class, expn_kwargs): toy_ctx = psource.toy_ctx + + source_boxes = actx.from_numpy( + np.array([0], dtype=np.int32)) + box_source_starts = actx.from_numpy( + np.array([0], dtype=np.int32)) + box_source_counts_nonchild = actx.from_numpy( + np.array([psource.points.shape[-1]], dtype=np.int32)) + center = np.asarray(center) - centers = np.array(center, dtype=np.float64).reshape( - toy_ctx.kernel.dim, 1) + centers = actx.from_numpy( + np.array(center, dtype=np.float64).reshape(toy_ctx.kernel.dim, 1)) - evt, (coeffs,) = p2e(toy_ctx.queue, + coeffs = p2e( + actx, source_boxes=source_boxes, box_source_starts=box_source_starts, box_source_counts_nonchild=box_source_counts_nonchild, centers=centers, - sources=psource.points, - strengths=(psource.weights,), + sources=actx.from_numpy(psource.points), + strengths=(actx.from_numpy(psource.weights),), rscale=rscale, nboxes=1, tgt_base_ibox=0, - - #flags="print_hl_cl", - out_host=True, **toy_ctx.extra_source_and_kernel_kwargs) - return expn_class(toy_ctx, center, rscale, order, coeffs[0], + return expn_class( + toy_ctx, center, rscale, order, + actx.to_numpy(coeffs[0]), derived_from=psource, **expn_kwargs) -def _e2p(psource, targets, e2p): - ntargets = targets.shape[-1] - - boxes = np.array([0], dtype=np.int32) - - box_target_starts = np.array([0], dtype=np.int32) - box_target_counts_nonchild = np.array([ntargets], dtype=np.int32) - +def _e2p(actx, psource, targets, e2p): toy_ctx = psource.toy_ctx - centers = np.array(psource.center, dtype=np.float64).reshape( - toy_ctx.kernel.dim, 1) - coeffs = np.array([psource.coeffs]) - evt, (pot,) = e2p( - toy_ctx.queue, + ntargets = targets.shape[-1] + boxes = actx.from_numpy( + np.array([0], dtype=np.int32)) + box_target_starts = actx.from_numpy( + np.array([0], dtype=np.int32)) + box_target_counts_nonchild = actx.from_numpy( + np.array([ntargets], dtype=np.int32)) + + centers = actx.from_numpy( + np.array(psource.center, dtype=np.float64).reshape(toy_ctx.kernel.dim, 1)) + + coeffs = actx.from_numpy(np.array([psource.coeffs])) + pot, = e2p( + actx, src_expansions=coeffs, src_base_ibox=0, target_boxes=boxes, @@ -232,59 +332,171 @@ def _e2p(psource, targets, e2p): box_target_counts_nonchild=box_target_counts_nonchild, centers=centers, rscale=psource.rscale, - targets=targets, - #flags="print_hl_cl", - out_host=True, **toy_ctx.extra_kernel_kwargs) + targets=actx.from_numpy(obj_array.new_1d(targets)), + **toy_ctx.extra_kernel_kwargs) - return pot + return actx.to_numpy(pot) -def _e2e(psource, to_center, to_rscale, to_order, e2e, expn_class, expn_kwargs): +def _e2e(actx: ArrayContext, + psource, to_center, to_rscale, to_order: int, e2e, expn_class, expn_kwargs, + extra_kernel_kwargs): toy_ctx = psource.toy_ctx - target_boxes = np.array([1], dtype=np.int32) - src_box_starts = np.array([0, 1], dtype=np.int32) - src_box_lists = np.array([0], dtype=np.int32) + target_boxes = actx.from_numpy( + np.array([1], dtype=np.int32)) + src_box_starts = actx.from_numpy( + np.array([0, 1], dtype=np.int32)) + src_box_lists = actx.from_numpy( + np.array([0], dtype=np.int32)) - centers = (np.array( + centers = actx.from_numpy( + np.array( [ # box 0: source psource.center, # box 1: target to_center, - ], - dtype=np.float64)).T.copy() - - coeffs = np.array([psource.coeffs]) - - evt, (to_coeffs,) = e2e( - toy_ctx.queue, - src_expansions=coeffs, - src_base_ibox=0, - tgt_base_ibox=0, - ntgt_level_boxes=2, + ], + dtype=np.float64).T.copy() + ) + + coeffs = actx.from_numpy(np.array([psource.coeffs])) + args = { + "actx": actx, + "src_expansions": coeffs, + "src_base_ibox": 0, + "tgt_base_ibox": 0, + "ntgt_level_boxes": 2, + "target_boxes": target_boxes, + "src_box_starts": src_box_starts, + "src_box_lists": src_box_lists, + "centers": centers, + "src_rscale": psource.rscale, + "tgt_rscale": to_rscale, + **extra_kernel_kwargs, + **toy_ctx.extra_kernel_kwargs, + } + + to_coeffs = e2e(**args) + return expn_class( + toy_ctx, to_center, to_rscale, to_order, + actx.to_numpy(to_coeffs[1]), + derived_from=psource, **expn_kwargs) - target_boxes=target_boxes, - src_box_starts=src_box_starts, - src_box_lists=src_box_lists, - centers=centers, +def _m2l(actx: ArrayContext, + psource, to_center, to_rscale, to_order, e2e, expn_class, expn_kwargs, + translation_classes_kwargs): + toy_ctx = psource.toy_ctx - src_rscale=psource.rscale, - tgt_rscale=to_rscale, + coeffs = actx.from_numpy(np.array([psource.coeffs])) + m2l_use_translation_classes_dependent_data = \ + toy_ctx.use_translation_classes_dependent_data() + + if m2l_use_translation_classes_dependent_data: + data_kernel = translation_classes_kwargs["data_kernel"] + preprocess_kernel = translation_classes_kwargs["preprocess_kernel"] + postprocess_kernel = translation_classes_kwargs["postprocess_kernel"] + expn_size = translation_classes_kwargs["m2l_expn_size"] + + # Preprocess the mpole expansion + preprocessed_src_expansions = actx.np.zeros((1, expn_size), dtype=np.complex128) + preprocess_kernel( + actx, + src_expansions=coeffs, + preprocessed_src_expansions=preprocessed_src_expansions, + src_rscale=np.float64(psource.rscale), + **toy_ctx.extra_kernel_kwargs) + + from sumpy.tools import get_opencl_fft_app, run_opencl_fft + + if toy_ctx.use_fft: + + fft_app = get_opencl_fft_app(actx, (1, expn_size,), + dtype=preprocessed_src_expansions.dtype, inverse=False) + ifft_app = get_opencl_fft_app(actx, (1, expn_size,), + dtype=preprocessed_src_expansions.dtype, inverse=True) + + _evt, preprocessed_src_expansions = run_opencl_fft(actx, fft_app, + preprocessed_src_expansions, inverse=False) + + # Compute translation classes data + m2l_translation_classes_lists = ( + actx.from_numpy(np.array([0], dtype=np.int32))) + dist = np.array(to_center - psource.center, dtype=np.float64) + dim = toy_ctx.kernel.dim + m2l_translation_vectors = actx.from_numpy(dist.reshape(dim, 1)) + m2l_translation_classes_dependent_data = ( + actx.np.zeros((1, expn_size), dtype=np.complex128)) + + data_kernel( + actx, + src_rscale=np.float64(psource.rscale), + ntranslation_classes=1, + translation_classes_level_start=0, + m2l_translation_vectors=m2l_translation_vectors, + m2l_translation_classes_dependent_data=( + m2l_translation_classes_dependent_data), + ntranslation_vectors=1, + **toy_ctx.extra_kernel_kwargs) + + if toy_ctx.use_fft: + _evt, m2l_translation_classes_dependent_data = run_opencl_fft( + actx, fft_app, + m2l_translation_classes_dependent_data, + inverse=False) + + ret = _e2e(actx, psource, to_center, to_rscale, to_order, + e2e, expn_class, expn_kwargs, + { + "src_expansions": preprocessed_src_expansions, + "m2l_translation_classes_lists": m2l_translation_classes_lists, + "m2l_translation_classes_dependent_data": ( + m2l_translation_classes_dependent_data), + "translation_classes_level_start": 0, + } + ) + + # Postprocess the local expansion + local_before = actx.from_numpy(np.array([ret.coeffs])) + to_coeffs = actx.np.zeros((1, len(data_kernel.tgt_expansion)), + dtype=coeffs.dtype) + + if toy_ctx.use_fft: + _evt, local_before = run_opencl_fft( + actx, ifft_app, + local_before, inverse=True) + + postprocess_kernel( + actx, + tgt_expansions_before_postprocessing=local_before, + tgt_expansions=to_coeffs, + src_rscale=np.float64(psource.rscale), + tgt_rscale=np.float64(to_rscale), + **toy_ctx.extra_kernel_kwargs) + + return expn_class( + toy_ctx, to_center, to_rscale, to_order, + actx.to_numpy(to_coeffs)[0], + derived_from=psource, **expn_kwargs) + else: + ret = _e2e( + actx, + psource, to_center, to_rscale, to_order, e2e, expn_class, + expn_kwargs, {}) - #flags="print_hl_cl", - out_host=True, **toy_ctx.extra_kernel_kwargs) + return ret - return expn_class(toy_ctx, to_center, to_rscale, to_order, to_coeffs[1], - derived_from=psource, **expn_kwargs) # }}} - # {{{ potential source classes +Number_ish = int | float | complex | np.number + + class PotentialSource: """A base class for all classes representing potentials that can be evaluated anywhere in space. @@ -302,17 +514,21 @@ class PotentialSource: .. automethod:: __rmul__ """ - def __init__(self, toy_ctx): + def __init__(self, toy_ctx: ToyContext): self.toy_ctx = toy_ctx - def eval(self, targets): + def eval(self, actx: ArrayContext, targets: np.ndarray) -> np.ndarray: + """ + :param targets: An array of shape ``(dim, ntargets)``. + :returns: an array of shape ``(ntargets,)``. + """ raise NotImplementedError() - def __neg__(self): + def __neg__(self) -> PotentialSource: return -1*self - def __add__(self, other): - if isinstance(other, (Number, np.number)): + def __add__(self, other: Number_ish | PotentialSource) -> PotentialSource: + if isinstance(other, Number | np.number): other = ConstantPotential(self.toy_ctx, other) elif not isinstance(other, PotentialSource): return NotImplemented @@ -321,14 +537,16 @@ def __add__(self, other): __radd__ = __add__ - def __sub__(self, other): + def __sub__(self, other: Number_ish | PotentialSource) -> PotentialSource: return self.__add__(-other) - def __rsub__(self, other): + def __rsub__(self, + other: Number_ish | PotentialSource + ) -> PotentialSource: return (-self).__add__(other) - def __mul__(self, other): - if isinstance(other, (Number, np.number)): + def __mul__(self, other: Number_ish | PotentialSource) -> PotentialSource: + if isinstance(other, Number | np.number): other = ConstantPotential(self.toy_ctx, other) elif not isinstance(other, PotentialSource): return NotImplemented @@ -340,14 +558,16 @@ def __mul__(self, other): class ConstantPotential(PotentialSource): """ + Inherits from :class:`PotentialSource`. + .. automethod:: __init__ """ - def __init__(self, toy_ctx, value): + def __init__(self, toy_ctx: ToyContext, value): super().__init__(toy_ctx) self.value = np.array(value) - def eval(self, targets): + def eval(self, actx: ArrayContext, targets: np.ndarray) -> np.ndarray: pot = np.empty(targets.shape[-1], dtype=self.value.dtype) pot.fill(self.value) return pot @@ -355,29 +575,40 @@ def eval(self, targets): class OneOnBallPotential(PotentialSource): """ + A potential that is the characteristic function on a ball. + + Inherits from :class:`PotentialSource`. + .. automethod:: __init__ """ - def __init__(self, toy_ctx, center, radius): + + def __init__(self, + toy_ctx: ToyContext, center: np.ndarray, radius: float) -> None: super().__init__(toy_ctx) self.center = np.asarray(center) self.radius = radius - def eval(self, targets): + @override + def eval(self, actx: ArrayContext, targets: np.ndarray) -> np.ndarray: dist_vec = targets - self.center[:, np.newaxis] return (np.sum(dist_vec**2, axis=0) < self.radius**2).astype(np.float64) class HalfspaceOnePotential(PotentialSource): """ + A potential that is the characteristic function of a halfspace. + .. automethod:: __init__ """ - def __init__(self, toy_ctx, center, axis, side=1): + + def __init__(self, toy_ctx: ToyContext, center: np.ndarray, + axis: int, side: int = 1) -> None: super().__init__(toy_ctx) self.center = np.asarray(center) self.axis = axis self.side = side - def eval(self, targets): + def eval(self, actx: ArrayContext, targets: np.ndarray) -> np.ndarray: return ( (self.side*(targets[self.axis] - self.center[self.axis])) >= 0 ).astype(np.float64) @@ -385,6 +616,8 @@ def eval(self, targets): class PointSources(PotentialSource): """ + Inherits from :class:`PotentialSource`. + .. attribute:: points ``[ndim, npoints]`` @@ -392,20 +625,25 @@ class PointSources(PotentialSource): .. automethod:: __init__ """ - def __init__(self, toy_ctx, points, weights, center=None): + def __init__(self, + toy_ctx: ToyContext, points: np.ndarray, weights: np.ndarray, + center: np.ndarray | None = None): super().__init__(toy_ctx) self.points = points self.weights = weights self._center = center - def eval(self, targets): - evt, (potential,) = self.toy_ctx.get_p2p()( - self.toy_ctx.queue, targets, self.points, [self.weights], - out_host=True, + @override + def eval(self, actx: ArrayContext, targets: np.ndarray) -> np.ndarray: + potential, = self.toy_ctx.get_p2p()( + actx, + actx.from_numpy(targets), + actx.from_numpy(self.points), + [actx.from_numpy(self.weights)], **self.toy_ctx.extra_source_and_kernel_kwargs) - return potential + return actx.to_numpy(potential) @property def center(self): @@ -417,6 +655,8 @@ def center(self): class ExpansionPotentialSource(PotentialSource): """ + Inherits from :class:`PotentialSource`. + .. attribute:: radius Not used mathematically. Just for visualization, purely advisory. @@ -426,8 +666,11 @@ class ExpansionPotentialSource(PotentialSource): Passed to :func:`matplotlib.pyplot.annotate`. Used for customizing the expansion label. Changing the label text is supported by passing the kwarg *s*. Just for visualization, purely advisory. + + .. automethod:: __init__ """ - def __init__(self, toy_ctx, center, rscale, order, coeffs, derived_from, + + def __init__(self, toy_ctx, center, rscale, order: int, coeffs, derived_from, radius=None, expn_style=None, text_kwargs=None): super().__init__(toy_ctx) self.center = np.asarray(center) @@ -447,17 +690,31 @@ def with_coeffs(self, coeffs): class MultipoleExpansion(ExpansionPotentialSource): - def eval(self, targets): - return _e2p(self, targets, self.toy_ctx.get_m2p(self.order)) + """ + Inherits from :class:`ExpansionPotentialSource`. + """ + + def eval(self, actx: ArrayContext, targets: np.ndarray) -> np.ndarray: + return _e2p(actx, self, targets, self.toy_ctx.get_m2p(self.order)) class LocalExpansion(ExpansionPotentialSource): - def eval(self, targets): - return _e2p(self, targets, self.toy_ctx.get_l2p(self.order)) + """ + Inherits from :class:`ExpansionPotentialSource`. + """ + + def eval(self, actx: ArrayContext, targets: np.ndarray) -> np.ndarray: + return _e2p(actx, self, targets, self.toy_ctx.get_l2p(self.order)) class PotentialExpressionNode(PotentialSource): - def __init__(self, psources): + """ + Inherits from :class:`PotentialSource`. + + .. automethod:: __init__ + """ + + def __init__(self, psources: Sequence[PotentialSource]) -> None: from pytools import single_valued super().__init__( single_valued(psource.toy_ctx for psource in psources)) @@ -465,7 +722,7 @@ def __init__(self, psources): self.psources = psources @property - def center(self): + def center(self) -> np.ndarray: for psource in self.psources: try: return psource.center @@ -476,82 +733,133 @@ def center(self): class Sum(PotentialExpressionNode): - def eval(self, targets): - result = 0 + """ + Inherits from :class:`PotentialExpressionNode`. + """ + + @override + def eval(self, actx: ArrayContext, targets: np.ndarray) -> np.ndarray: + result = np.zeros(targets.shape[1]) for psource in self.psources: - result = result + psource.eval(targets) + result = result + psource.eval(actx, targets) return result class Product(PotentialExpressionNode): - def eval(self, targets): - result = 1 + """ + Inherits from :class:`PotentialExpressionNode`. + """ + + @override + def eval(self, actx: ArrayContext, targets: np.ndarray) -> np.ndarray: + result = np.ones(targets.shape[1]) for psource in self.psources: - result = result * psource.eval(targets) + result = result * psource.eval(actx, targets) return result - # }}} -def multipole_expand(psource, center, order=None, rscale=1, **expn_kwargs): +def multipole_expand( + actx: ArrayContext, + psource: PotentialSource, + center: np.ndarray, *, + order: int | None = None, + rscale: float = 1, + **expn_kwargs: Any) -> MultipoleExpansion: if isinstance(psource, PointSources): if order is None: raise ValueError("order may not be None") - return _p2e(psource, center, rscale, order, psource.toy_ctx.get_p2m(order), + return _p2e(actx, + psource, center, rscale, order, psource.toy_ctx.get_p2m(order), MultipoleExpansion, expn_kwargs) elif isinstance(psource, MultipoleExpansion): if order is None: order = psource.order - return _e2e(psource, center, rscale, order, + return _e2e(actx, psource, center, rscale, order, psource.toy_ctx.get_m2m(psource.order, order), - MultipoleExpansion, expn_kwargs) + MultipoleExpansion, expn_kwargs, {}) else: raise TypeError(f"do not know how to expand '{type(psource).__name__}'") -def local_expand(psource, center, order=None, rscale=1, **expn_kwargs): +def local_expand( + actx: ArrayContext, + psource: PotentialSource, + center: np.ndarray, *, + order: int | None = None, + rscale: float = 1, + **expn_kwargs: Any) -> LocalExpansion: if isinstance(psource, PointSources): if order is None: raise ValueError("order may not be None") - return _p2e(psource, center, rscale, order, psource.toy_ctx.get_p2l(order), + return _p2e(actx, + psource, center, rscale, order, psource.toy_ctx.get_p2l(order), LocalExpansion, expn_kwargs) elif isinstance(psource, MultipoleExpansion): if order is None: order = psource.order - return _e2e(psource, center, rscale, order, - psource.toy_ctx.get_m2l(psource.order, order), - LocalExpansion, expn_kwargs) + toy_ctx = psource.toy_ctx + translation_classes_kwargs = {} + m2l_use_translation_classes_dependent_data = \ + toy_ctx.use_translation_classes_dependent_data() + + if m2l_use_translation_classes_dependent_data: + data_kernel = toy_ctx.get_m2l_translation_class_dependent_data_kernel( + psource.order, order) + preprocess_kernel = toy_ctx.get_m2l_preprocess_mpole_kernel( + psource.order, order) + postprocess_kernel = toy_ctx.get_m2l_postprocess_local_kernel( + psource.order, order) + translation_classes_kwargs["data_kernel"] = data_kernel + translation_classes_kwargs["preprocess_kernel"] = preprocess_kernel + translation_classes_kwargs["postprocess_kernel"] = postprocess_kernel + translation_classes_kwargs["m2l_expn_size"] = \ + toy_ctx.get_m2l_expansion_size(psource.order, order) + + return _m2l(actx, psource, center, rscale, order, + toy_ctx.get_m2l(psource.order, order), + LocalExpansion, expn_kwargs, + translation_classes_kwargs) elif isinstance(psource, LocalExpansion): if order is None: order = psource.order - return _e2e(psource, center, rscale, order, + return _e2e(actx, psource, center, rscale, order, psource.toy_ctx.get_l2l(psource.order, order), - LocalExpansion, expn_kwargs) + LocalExpansion, expn_kwargs, {}) else: raise TypeError(f"do not know how to expand '{type(psource).__name__}'") -def logplot(fp, psource, **kwargs): +def logplot( + actx: ArrayContext, + fp: FieldPlotter, + psource: PotentialSource, **kwargs) -> None: fp.show_scalar_in_matplotlib( - np.log10(np.abs(psource.eval(fp.points) + 1e-15)), **kwargs) + np.log10(np.abs(psource.eval(actx, fp.points) + 1e-15)), **kwargs) -def combine_inner_outer(psource_inner, psource_outer, radius, center=None): +def combine_inner_outer( + psource_inner: PotentialSource, + psource_outer: PotentialSource, + radius: float | None, + center: np.ndarray | None = None) -> PotentialSource: if center is None: + assert isinstance(psource_inner, ExpansionPotentialSource) center = psource_inner.center if radius is None: + assert isinstance(psource_inner, ExpansionPotentialSource) radius = psource_inner.radius ball_one = OneOnBallPotential(psource_inner.toy_ctx, center, radius) @@ -560,8 +868,11 @@ def combine_inner_outer(psource_inner, psource_outer, radius, center=None): + psource_outer * (1 - ball_one)) -def combine_halfspace(psource_pos, psource_neg, axis, center=None): +def combine_halfspace(psource_pos: PotentialSource, + psource_neg: PotentialSource, axis: int, + center: np.ndarray | None = None) -> PotentialSource: if center is None: + assert isinstance(psource_pos, ExpansionPotentialSource) center = psource_pos.center halfspace_one = HalfspaceOnePotential(psource_pos.toy_ctx, center, axis) @@ -570,12 +881,18 @@ def combine_halfspace(psource_pos, psource_neg, axis, center=None): + psource_neg * (1-halfspace_one)) -def combine_halfspace_and_outer(psource_pos, psource_neg, psource_outer, - axis, radius=None, center=None): +def combine_halfspace_and_outer( + psource_pos: PotentialSource, + psource_neg: PotentialSource, + psource_outer: PotentialSource, + axis: int, radius: float | None = None, + center: np.ndarray | None = None) -> PotentialSource: if center is None: + assert isinstance(psource_pos, ExpansionPotentialSource) center = psource_pos.center if radius is None: + assert isinstance(psource_pos, ExpansionPotentialSource) center = psource_pos.radius return combine_inner_outer( @@ -583,15 +900,18 @@ def combine_halfspace_and_outer(psource_pos, psource_neg, psource_outer, psource_outer, radius, center) -def l_inf(psource, radius, center=None, npoints=100, debug=False): +def l_inf(actx: ArrayContext, psource: PotentialSource, radius: float, + center: np.ndarray | None = None, npoints: int = 100, + debug: bool = False) -> np.number: if center is None: + assert isinstance(psource, ExpansionPotentialSource) center = psource.center restr = psource * OneOnBallPotential(psource.toy_ctx, center, radius) from sumpy.visualization import FieldPlotter fp = FieldPlotter(center, extent=2*radius, npoints=npoints) - z = restr.eval(fp.points) + z = restr.eval(actx, fp.points) if debug: fp.show_scalar_in_matplotlib( @@ -606,8 +926,8 @@ def l_inf(psource, radius, center=None, npoints=100, debug=False): # {{{ schematic visualization def draw_box(el, eh, **kwargs): - import matplotlib.pyplot as pt import matplotlib.patches as mpatches + import matplotlib.pyplot as pt from matplotlib.path import Path pathdata = [ @@ -618,7 +938,7 @@ def draw_box(el, eh, **kwargs): (Path.CLOSEPOLY, (el[0], el[1])), ] - codes, verts = zip(*pathdata) + codes, verts = zip(*pathdata, strict=True) path = Path(verts, codes) patch = mpatches.PathPatch(path, **kwargs) pt.gca().add_patch(patch) @@ -649,10 +969,10 @@ def draw_annotation(to_pt, from_pt, label, arrowprops=None, **kwargs): import matplotlib.pyplot as plt - my_arrowprops = dict( - facecolor="black", - edgecolor="black", - arrowstyle="->") + my_arrowprops = { + "facecolor": "black", + "edgecolor": "black", + "arrowstyle": "->"} my_arrowprops.update(arrowprops) @@ -689,7 +1009,7 @@ def visit_multipoleexpansion(self, psource): elif expn_style == "circle": draw_circle(psource.center, psource.radius, fill=None) else: - raise ValueError(f"unknown expn_style: {self.expn_style}") + raise ValueError(f"unknown expn_style: {expn_style}") if psource.derived_from is None: return @@ -698,9 +1018,9 @@ def visit_multipoleexpansion(self, psource): # # ------> M - text_kwargs = dict( - verticalalignment="center", - horizontalalignment="center") + text_kwargs = { + "verticalalignment": "center", + "horizontalalignment": "center"} label = "${}_{{{}}}$".format( type(psource).__name__[0].lower().replace("l", "\\ell"), @@ -711,15 +1031,15 @@ def visit_multipoleexpansion(self, psource): label = psource_text_kwargs_copy.pop("s", label) text_kwargs.update(psource_text_kwargs_copy) - shrinkB = 0 # noqa + shrinkB = 0 # ruff:ignore[non-lowercase-variable-in-function] if isinstance(psource.derived_from, ExpansionPotentialSource): # Avoid overlapping the tail of the arrow with any expansion labels that # are present at the tail. import matplotlib as mpl font_size = mpl.rcParams["font.size"] - shrinkB = 7/8 * font_size # noqa + shrinkB = 7/8 * font_size # ruff:ignore[non-lowercase-variable-in-function] - arrowprops = dict(shrinkB=shrinkB, arrowstyle="<|-") + arrowprops = {"shrinkB": shrinkB, "arrowstyle": "<|-"} draw_annotation(psource.derived_from.center, psource.center, label, arrowprops, **text_kwargs) diff --git a/sumpy/version.py b/sumpy/version.py index 74ea3737e..86f24b253 100644 --- a/sumpy/version.py +++ b/sumpy/version.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2014 Andreas Kloeckner" __license__ = """ @@ -20,29 +23,22 @@ THE SOFTWARE. """ -# {{{ find install- or run-time git revision +from importlib import metadata + +from pytools import find_module_git_revision -import os -if os.environ.get("AKPYTHON_EXEC_FROM_WITHIN_WITHIN_SETUP_PY") is not None: - # We're just being exec'd by setup.py. We can't import anything. - _git_rev = None -else: - import sumpy._git_rev as _git_rev_mod - _git_rev = _git_rev_mod.GIT_REVISION +def _parse_version(version: str) -> tuple[tuple[int, ...], str]: + import re - # If we're running from a dev tree, the last install (and hence the most - # recent update of the above git rev) could have taken place very long ago. - from pytools import find_module_git_revision - _runtime_git_rev = find_module_git_revision(__file__, n_levels_up=1) - if _runtime_git_rev is not None: - _git_rev = _runtime_git_rev + m = re.match(r"^([0-9.]+)([a-z0-9]*?)$", version) + assert m is not None -# }}} + return tuple(int(nr) for nr in m.group(1).split(".")), m.group(2) -VERSION = (2020, 2) -VERSION_STATUS = "beta1" -VERSION_TEXT = ".".join(str(x) for x in VERSION) + VERSION_STATUS +VERSION_TEXT = metadata.version("sumpy") +VERSION, VERSION_STATUS = _parse_version(VERSION_TEXT) -KERNEL_VERSION = (VERSION, _git_rev, 0) +_GIT_REVISION = find_module_git_revision(__file__, n_levels_up=1) +KERNEL_VERSION = (*VERSION, _GIT_REVISION, 2) diff --git a/sumpy/visualization.py b/sumpy/visualization.py index c5ce57109..f3ffacac8 100644 --- a/sumpy/visualization.py +++ b/sumpy/visualization.py @@ -1,3 +1,6 @@ +from __future__ import annotations + + __copyright__ = "Copyright (C) 2012 Andreas Kloeckner" __license__ = """ @@ -26,11 +29,22 @@ .. autoclass:: FieldPlotter """ +from typing import TYPE_CHECKING, Any, cast + import numpy as np -def separate_by_real_and_imag(data, real_only): - from pytools.obj_array import obj_array_real_copy, obj_array_imag_copy +if TYPE_CHECKING: + import pathlib + from collections.abc import Iterable, Iterator, Sequence + + import optype.numpy as onp + + +def separate_by_real_and_imag( + data: Iterable[tuple[str, onp.ArrayND[Any]]], *, + real_only: bool) -> Iterator[tuple[str, onp.ArrayND[Any]]]: + from pytools.obj_array import obj_array_imag_copy, obj_array_real_copy for name, field in data: try: @@ -48,7 +62,10 @@ def separate_by_real_and_imag(data, real_only): yield (f"{name}_i", obj_array_imag_copy(field)) -def make_field_plotter_from_bbox(bbox, h, extend_factor=0): +def make_field_plotter_from_bbox( + bbox: tuple[onp.Array1D[np.floating[Any]], onp.Array1D[np.floating[Any]]], + h: float | Sequence[float], + extend_factor: float = 0) -> FieldPlotter: """ :arg bbox: a tuple (low, high) of points represented as 1D numpy arrays indicating the low and high ends of the extent of a bounding box. @@ -60,74 +77,95 @@ def make_field_plotter_from_bbox(bbox, h, extend_factor=0): """ low, high = bbox - extent = (high-low) * (1 + extend_factor) - center = 0.5*(high+low) - + extent: onp.Array1D[np.floating[Any]] = (high-low) * (1 + extend_factor) + center: onp.Array1D[np.floating[Any]] = 0.5*(high+low) dimensions = len(center) + from numbers import Number - if isinstance(h, Number): - h = (h,)*dimensions + if isinstance(h, (int, float, Number)): + h = cast("Sequence[float]", (h,)*dimensions) else: if len(h) != dimensions: raise ValueError("length of 'h' must match number of dimensions") from math import ceil - npoints = tuple( - int(ceil(extent[i] / h[i])) - for i in range(dimensions)) - + npoints = tuple(ceil(float(extent[i]) / h[i]) for i in range(dimensions)) return FieldPlotter(center, extent, npoints) class FieldPlotter: """ + .. autoattribute:: dimensions + .. autoattribute:: npoints + .. autoattribute:: points + .. automethod:: set_matplotlib_limits .. automethod:: show_scalar_in_matplotlib .. automethod:: show_scalar_in_mayavi .. automethod:: write_vtk_file """ - def __init__(self, center, extent=1, npoints=1000): - center = np.asarray(center) - self.dimensions, = dim, = center.shape - self.a = a = center-extent*0.5 - self.b = b = center+extent*0.5 - from numbers import Number - if isinstance(npoints, Number): - npoints = dim*(npoints,) - else: - if len(npoints) != dim: - raise ValueError("length of npoints must match dimension") + dimensions: int + npoints: int + points: onp.Array2D[np.floating[Any]] - for i in range(dim): - if npoints[i] == 1: - a[i] = center[i] + a: onp.Array1D[np.floating[Any]] + b: onp.Array1D[np.floating[Any]] + nd_points: onp.ArrayND[np.floating[Any]] - mgrid_index = tuple( - slice(a[i], b[i], 1j*npoints[i]) - for i in range(dim)) - - mgrid = np.mgrid[mgrid_index] + def __init__(self, + center: onp.ToArray1D[np.floating[Any]], + extent: float | onp.Array1D[np.floating[Any]] = 1, + npoints: int | tuple[int, ...] = 1000, + points: onp.ArrayND[np.floating[Any]] | None = None) -> None: + center = np.asarray(center) + dim, = cast("tuple[int]", center.shape) + + self.dimensions = dim + self.a = a = center - 0.5 * extent + self.b = b = center + 0.5 * extent + + if points is None: + from numbers import Number + if isinstance(npoints, (int, Number)): + npoints = dim*(npoints,) + else: + if len(npoints) != dim: + raise ValueError("length of npoints must match dimension") + + for i in range(dim): + if npoints[i] == 1: + a[i] = center[i] + + mgrid_index = tuple( + slice(a[i], b[i], 1j*npoints[i]) + for i in range(dim)) + mgrid = np.mgrid[mgrid_index] + else: + mgrid = points # (axis, point x idx, point y idx, ...) self.nd_points = mgrid - self.points = self.nd_points.reshape(dim, -1).copy() + self.npoints = int(np.prod(mgrid.shape[1:])) - from pytools import product - self.npoints = product(npoints) - - def _get_nontrivial_dims(self): + def _get_nontrivial_dims(self) -> onp.Array1D[np.bool_]: return np.array(self.nd_points.shape[1:]) != 1 - def _get_squeezed_bounds(self): + def _get_squeezed_bounds( + self + ) -> tuple[onp.Array1D[np.floating[Any]], onp.Array1D[np.floating[Any]]]: nontriv_dims = self._get_nontrivial_dims() return self.a[nontriv_dims], self.b[nontriv_dims] - def show_scalar_in_matplotlib(self, fld, max_val=None, - func_name="imshow", **kwargs): + def show_scalar_in_matplotlib( + self, + fld: onp.ArrayND[Any], + max_val: float | None = None, + func_name: str = "imshow", + **kwargs: Any) -> Any: squeezed_points = self.points.squeeze() if len(squeezed_points.shape) != 2: @@ -155,30 +193,43 @@ def show_scalar_in_matplotlib(self, fld, max_val=None, import matplotlib.pyplot as pt return getattr(pt, func_name)(squeezed_fld.T, **kwargs) - def set_matplotlib_limits(self): + def set_matplotlib_limits(self) -> None: import matplotlib.pyplot as pt a, b = self._get_squeezed_bounds() pt.xlim((a[0], b[0])) pt.ylim((a[1], b[1])) - def show_vector_in_mayavi(self, fld, do_show=True, **kwargs): + def show_vector_in_mayavi( + self, + fld: onp.ArrayND[Any], + do_show: bool = True, + **kwargs: Any) -> None: c = self.points from mayavi import mlab - mlab.quiver3d(c[0], c[1], c[2], fld[0], fld[1], fld[2], - **kwargs) + + mlab.quiver3d(c[0], c[1], c[2], fld[0], fld[1], fld[2], **kwargs) if do_show: mlab.show() - def write_vtk_file(self, file_name, data, real_only=False, overwrite=False): + def write_vtk_file( + self, + file_name: str | pathlib.Path, + data: Iterable[tuple[str, onp.ArrayND[Any]]], *, + real_only: bool = False, + overwrite: bool = False) -> None: from pyvisfile.vtk import write_structured_grid write_structured_grid(file_name, self.nd_points, - point_data=list(separate_by_real_and_imag(data, real_only)), + point_data=list(separate_by_real_and_imag(data, real_only=real_only)), overwrite=overwrite) - def show_scalar_in_mayavi(self, fld, max_val=None, **kwargs): + def show_scalar_in_mayavi( + self, + fld: onp.ArrayND[Any], + max_val: float | None = None, + **kwargs: Any) -> None: if max_val is not None: fld[fld > max_val] = max_val fld[fld < -max_val] = -max_val diff --git a/test/test_fmm.py b/test/test_fmm.py deleted file mode 100644 index 3c5ec6d7c..000000000 --- a/test/test_fmm.py +++ /dev/null @@ -1,612 +0,0 @@ -__copyright__ = "Copyright (C) 2013 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - - -import sys -import numpy as np -import numpy.linalg as la -import pyopencl as cl -from pyopencl.tools import ( # noqa - pytest_generate_tests_for_pyopencl as pytest_generate_tests) -from sumpy.kernel import LaplaceKernel, HelmholtzKernel, YukawaKernel -from sumpy.expansion.multipole import ( - VolumeTaylorMultipoleExpansion, - H2DMultipoleExpansion, Y2DMultipoleExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion) -from sumpy.expansion.local import ( - VolumeTaylorLocalExpansion, - H2DLocalExpansion, Y2DLocalExpansion, - LinearPDEConformingVolumeTaylorLocalExpansion) -from sumpy.fmm import ( - SumpyTreeIndependentDataForWrangler, - SumpyExpansionWrangler, - SumpyTranslationClassesData, - SumpyTranslationClassesDataNotSuppliedWarning) - -import pytest -import warnings - -import logging -logger = logging.getLogger(__name__) - - -try: - import faulthandler -except ImportError: - pass -else: - faulthandler.enable() - - -@pytest.mark.parametrize("optimized_m2l, use_fft", - [(False, False), (True, False), (True, True)]) -@pytest.mark.parametrize( - ("knl", "local_expn_class", "mpole_expn_class", - "order_varies_with_level"), [ - (LaplaceKernel(2), VolumeTaylorLocalExpansion, - VolumeTaylorMultipoleExpansion, False), - (LaplaceKernel(2), LinearPDEConformingVolumeTaylorLocalExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion, False), - (LaplaceKernel(3), VolumeTaylorLocalExpansion, - VolumeTaylorMultipoleExpansion, False), - (LaplaceKernel(3), LinearPDEConformingVolumeTaylorLocalExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion, False), - (HelmholtzKernel(2), VolumeTaylorLocalExpansion, - VolumeTaylorMultipoleExpansion, False), - (HelmholtzKernel(2), LinearPDEConformingVolumeTaylorLocalExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion, False), - (HelmholtzKernel(2), H2DLocalExpansion, H2DMultipoleExpansion, False), - (HelmholtzKernel(2), H2DLocalExpansion, H2DMultipoleExpansion, True), - (HelmholtzKernel(3), VolumeTaylorLocalExpansion, - VolumeTaylorMultipoleExpansion, False), - (HelmholtzKernel(3), LinearPDEConformingVolumeTaylorLocalExpansion, - LinearPDEConformingVolumeTaylorMultipoleExpansion, False), - (YukawaKernel(2), Y2DLocalExpansion, Y2DMultipoleExpansion, - False), - ]) -def test_sumpy_fmm(ctx_factory, knl, local_expn_class, mpole_expn_class, - order_varies_with_level, optimized_m2l, use_fft): - logging.basicConfig(level=logging.INFO) - - if local_expn_class == VolumeTaylorLocalExpansion and use_fft: - pytest.skip("VolumeTaylorExpansion with FFT takes a lot of resources.") - - if local_expn_class in [H2DLocalExpansion, Y2DLocalExpansion] and use_fft: - pytest.skip("Fourier/Bessel based expansions with FFT is not supported yet.") - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) - - nsources = 1000 - ntargets = 300 - dtype = np.float64 - - from boxtree.tools import ( - make_normal_particle_array as p_normal) - - sources = p_normal(queue, nsources, knl.dim, dtype, seed=15) - if 1: - offset = np.zeros(knl.dim) - offset[0] = 0.1 - - targets = ( - p_normal(queue, ntargets, knl.dim, dtype, seed=18) - + offset) - - del offset - else: - from sumpy.visualization import FieldPlotter - fp = FieldPlotter(np.array([0.5, 0]), extent=3, npoints=200) - from pytools.obj_array import make_obj_array - targets = make_obj_array( - [fp.points[i] for i in range(knl.dim)]) - - from boxtree import TreeBuilder - tb = TreeBuilder(ctx) - - tree, _ = tb(queue, sources, targets=targets, - max_particles_in_box=30, debug=True) - - from boxtree.traversal import FMMTraversalBuilder - tbuild = FMMTraversalBuilder(ctx) - trav, _ = tbuild(queue, tree, debug=True) - - # {{{ plot tree - - if 0: - host_tree = tree.get(queue) - host_trav = trav.get(queue) - - if 0: - print("src_box", host_tree.find_box_nr_for_source(403)) - print("tgt_box", host_tree.find_box_nr_for_target(28)) - print(list(host_trav.target_or_target_parent_boxes).index(37)) - print(host_trav.get_box_list("sep_bigger", 22)) - - from boxtree.visualization import TreePlotter - plotter = TreePlotter(host_tree) - plotter.draw_tree(fill=False, edgecolor="black", zorder=10) - plotter.set_bounding_box() - plotter.draw_box_numbers() - - import matplotlib.pyplot as pt - pt.show() - - # }}} - - from pyopencl.clrandom import PhiloxGenerator - rng = PhiloxGenerator(ctx, seed=44) - weights = rng.uniform(queue, nsources, dtype=np.float64) - - logger.info("computing direct (reference) result") - - from pytools.convergence import PConvergenceVerifier - - pconv_verifier = PConvergenceVerifier() - - extra_kwargs = {} - dtype = np.float64 - order_values = [1, 2, 3] - if isinstance(knl, HelmholtzKernel): - extra_kwargs["k"] = 0.05 - dtype = np.complex128 - - if knl.dim == 3: - order_values = [1, 2] - elif knl.dim == 2 and issubclass(local_expn_class, H2DLocalExpansion): - order_values = [4, 5] - - elif isinstance(knl, YukawaKernel): - extra_kwargs["lam"] = 2 - dtype = np.complex128 - - if knl.dim == 3: - order_values = [1, 2] - elif knl.dim == 2 and issubclass(local_expn_class, Y2DLocalExpansion): - order_values = [10, 12] - - from functools import partial - for order in order_values: - target_kernels = [knl] - - if optimized_m2l: - translation_classes_data = SumpyTranslationClassesData(queue, trav) - else: - translation_classes_data = None - - tree_indep = SumpyTreeIndependentDataForWrangler( - ctx, - partial(mpole_expn_class, knl), - partial(local_expn_class, knl), - target_kernels, use_fft_for_m2l=use_fft) - - if order_varies_with_level: - def fmm_level_to_order(kernel, kernel_args, tree, lev): - return order + lev % 2 - else: - def fmm_level_to_order(kernel, kernel_args, tree, lev): - return order - - with warnings.catch_warnings(): - if not optimized_m2l: - warnings.simplefilter("ignore", - SumpyTranslationClassesDataNotSuppliedWarning) - wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, - fmm_level_to_order=fmm_level_to_order, - kernel_extra_kwargs=extra_kwargs, - translation_classes_data=translation_classes_data) - - from boxtree.fmm import drive_fmm - - pot, = drive_fmm(wrangler, (weights,)) - - from sumpy import P2P - p2p = P2P(ctx, target_kernels, exclude_self=False) - evt, (ref_pot,) = p2p(queue, targets, sources, (weights,), - **extra_kwargs) - - pot = pot.get() - ref_pot = ref_pot.get() - - rel_err = la.norm(pot - ref_pot, np.inf) / la.norm(ref_pot, np.inf) - logger.info("order %d -> relative l2 error: %g", order, rel_err) - - pconv_verifier.add_data_point(order, rel_err) - - print(pconv_verifier) - pconv_verifier() - - -def test_unified_single_and_double(ctx_factory): - """ - Test that running one FMM for single layer + double layer gives the - same result as running one FMM for each and adding the results together - at the end - """ - logging.basicConfig(level=logging.INFO) - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) - - knl = LaplaceKernel(2) - local_expn_class = LinearPDEConformingVolumeTaylorLocalExpansion - mpole_expn_class = LinearPDEConformingVolumeTaylorMultipoleExpansion - - nsources = 1000 - ntargets = 300 - dtype = np.float64 - - from boxtree.tools import ( - make_normal_particle_array as p_normal) - - sources = p_normal(queue, nsources, knl.dim, dtype, seed=15) - offset = np.zeros(knl.dim) - offset[0] = 0.1 - - targets = ( - p_normal(queue, ntargets, knl.dim, dtype, seed=18) - + offset) - - del offset - - from boxtree import TreeBuilder - tb = TreeBuilder(ctx) - - tree, _ = tb(queue, sources, targets=targets, - max_particles_in_box=30, debug=True) - - from boxtree.traversal import FMMTraversalBuilder - tbuild = FMMTraversalBuilder(ctx) - trav, _ = tbuild(queue, tree, debug=True) - - from pyopencl.clrandom import PhiloxGenerator - rng = PhiloxGenerator(ctx, seed=44) - weights = ( - rng.uniform(queue, nsources, dtype=np.float64), - rng.uniform(queue, nsources, dtype=np.float64), - ) - - logger.info("computing direct (reference) result") - - dtype = np.float64 - order = 3 - - from functools import partial - from sumpy.kernel import DirectionalSourceDerivative, AxisTargetDerivative - - deriv_knl = DirectionalSourceDerivative(knl, "dir_vec") - - target_kernels = [knl, AxisTargetDerivative(0, knl)] - source_kernel_vecs = [[knl], [deriv_knl], [knl, deriv_knl]] - strength_usages = [[0], [1], [0, 1]] - - alpha = np.linspace(0, 2*np.pi, nsources, np.float64) - dir_vec = np.vstack([np.cos(alpha), np.sin(alpha)]) - - results = [] - for source_kernels, strength_usage in zip(source_kernel_vecs, strength_usages): - source_extra_kwargs = {} - if deriv_knl in source_kernels: - source_extra_kwargs["dir_vec"] = dir_vec - tree_indep = SumpyTreeIndependentDataForWrangler( - ctx, - partial(mpole_expn_class, knl), - partial(local_expn_class, knl), - target_kernels=target_kernels, source_kernels=source_kernels, - strength_usage=strength_usage) - wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, - fmm_level_to_order=lambda kernel, kernel_args, tree, lev: order, - source_extra_kwargs=source_extra_kwargs, - translation_classes_data=SumpyTranslationClassesData(queue, trav)) - - from boxtree.fmm import drive_fmm - - pot = drive_fmm(wrangler, weights) - results.append(np.array([pot[0].get(), pot[1].get()])) - - ref_pot = results[0] + results[1] - pot = results[2] - rel_err = la.norm(pot - ref_pot, np.inf) / la.norm(ref_pot, np.inf) - - assert rel_err < 1e-12 - - -def test_sumpy_fmm_timing_data_collection(ctx_factory): - logging.basicConfig(level=logging.INFO) - - ctx = ctx_factory() - queue = cl.CommandQueue( - ctx, - properties=cl.command_queue_properties.PROFILING_ENABLE) - - nsources = 500 - dtype = np.float64 - - from boxtree.tools import ( - make_normal_particle_array as p_normal) - - knl = LaplaceKernel(2) - local_expn_class = VolumeTaylorLocalExpansion - mpole_expn_class = VolumeTaylorMultipoleExpansion - order = 1 - - sources = p_normal(queue, nsources, knl.dim, dtype, seed=15) - - from boxtree import TreeBuilder - tb = TreeBuilder(ctx) - - tree, _ = tb(queue, sources, - max_particles_in_box=30, debug=True) - - from boxtree.traversal import FMMTraversalBuilder - tbuild = FMMTraversalBuilder(ctx) - trav, _ = tbuild(queue, tree, debug=True) - - from pyopencl.clrandom import PhiloxGenerator - rng = PhiloxGenerator(ctx) - weights = rng.uniform(queue, nsources, dtype=np.float64) - - target_kernels = [knl] - - from functools import partial - - tree_indep = SumpyTreeIndependentDataForWrangler( - ctx, - partial(mpole_expn_class, knl), - partial(local_expn_class, knl), - target_kernels) - - wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, - fmm_level_to_order=lambda kernel, kernel_args, tree, lev: order, - translation_classes_data=SumpyTranslationClassesData(queue, trav)) - from boxtree.fmm import drive_fmm - - timing_data = {} - pot, = drive_fmm(wrangler, (weights,), timing_data=timing_data) - print(timing_data) - assert timing_data - - -def test_sumpy_fmm_exclude_self(ctx_factory): - logging.basicConfig(level=logging.INFO) - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) - - nsources = 500 - dtype = np.float64 - - from boxtree.tools import ( - make_normal_particle_array as p_normal) - - knl = LaplaceKernel(2) - local_expn_class = VolumeTaylorLocalExpansion - mpole_expn_class = VolumeTaylorMultipoleExpansion - order = 10 - - sources = p_normal(queue, nsources, knl.dim, dtype, seed=15) - - from boxtree import TreeBuilder - tb = TreeBuilder(ctx) - - tree, _ = tb(queue, sources, - max_particles_in_box=30, debug=True) - - from boxtree.traversal import FMMTraversalBuilder - tbuild = FMMTraversalBuilder(ctx) - trav, _ = tbuild(queue, tree, debug=True) - - from pyopencl.clrandom import PhiloxGenerator - rng = PhiloxGenerator(ctx) - weights = rng.uniform(queue, nsources, dtype=np.float64) - - target_to_source = np.arange(tree.ntargets, dtype=np.int32) - self_extra_kwargs = {"target_to_source": target_to_source} - - target_kernels = [knl] - - from functools import partial - - tree_indep = SumpyTreeIndependentDataForWrangler( - ctx, - partial(mpole_expn_class, knl), - partial(local_expn_class, knl), - target_kernels, - exclude_self=True) - - wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, - fmm_level_to_order=lambda kernel, kernel_args, tree, lev: order, - self_extra_kwargs=self_extra_kwargs, - translation_classes_data=SumpyTranslationClassesData(queue, trav)) - - from boxtree.fmm import drive_fmm - - pot, = drive_fmm(wrangler, (weights,)) - - from sumpy import P2P - p2p = P2P(ctx, target_kernels, exclude_self=True) - evt, (ref_pot,) = p2p(queue, sources, sources, (weights,), - **self_extra_kwargs) - - pot = pot.get() - ref_pot = ref_pot.get() - - rel_err = la.norm(pot - ref_pot) / la.norm(ref_pot) - logger.info("order %d -> relative l2 error: %g", order, rel_err) - - assert np.isclose(rel_err, 0, atol=1e-7) - - -def test_sumpy_axis_source_derivative(ctx_factory): - logging.basicConfig(level=logging.INFO) - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) - - nsources = 500 - dtype = np.float64 - - from boxtree.tools import ( - make_normal_particle_array as p_normal) - - knl = LaplaceKernel(2) - local_expn_class = VolumeTaylorLocalExpansion - mpole_expn_class = VolumeTaylorMultipoleExpansion - order = 10 - - sources = p_normal(queue, nsources, knl.dim, dtype, seed=15) - - from boxtree import TreeBuilder - tb = TreeBuilder(ctx) - - tree, _ = tb(queue, sources, - max_particles_in_box=30, debug=True) - - from boxtree.traversal import FMMTraversalBuilder - tbuild = FMMTraversalBuilder(ctx) - trav, _ = tbuild(queue, tree, debug=True) - - from pyopencl.clrandom import PhiloxGenerator - rng = PhiloxGenerator(ctx, seed=12) - weights = rng.uniform(queue, nsources, dtype=np.float64) - - target_to_source = np.arange(tree.ntargets, dtype=np.int32) - self_extra_kwargs = {"target_to_source": target_to_source} - - from functools import partial - - from sumpy.kernel import AxisTargetDerivative, AxisSourceDerivative - - pots = [] - for tgt_knl, src_knl in [(AxisTargetDerivative(0, knl), knl), - (knl, AxisSourceDerivative(0, knl))]: - - tree_indep = SumpyTreeIndependentDataForWrangler( - ctx, - partial(mpole_expn_class, knl), - partial(local_expn_class, knl), - target_kernels=[tgt_knl], - source_kernels=[src_knl], - exclude_self=True) - - wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, - fmm_level_to_order=lambda kernel, kernel_args, tree, lev: order, - self_extra_kwargs=self_extra_kwargs, - translation_classes_data=SumpyTranslationClassesData(queue, trav)) - - from boxtree.fmm import drive_fmm - - pot, = drive_fmm(wrangler, (weights,)) - pots.append(pot.get()) - - rel_err = la.norm(pots[0] + pots[1]) / la.norm(pots[0]) - logger.info("order %d -> relative l2 error: %g", order, rel_err) - - assert np.isclose(rel_err, 0, atol=1e-5) - - -@pytest.mark.parametrize("deriv_axes", [(), (0,), (1,)]) -def test_sumpy_target_point_multiplier(ctx_factory, deriv_axes): - logging.basicConfig(level=logging.INFO) - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) - - nsources = 500 - dtype = np.float64 - - from boxtree.tools import ( - make_normal_particle_array as p_normal) - - knl = LaplaceKernel(2) - local_expn_class = VolumeTaylorLocalExpansion - mpole_expn_class = VolumeTaylorMultipoleExpansion - order = 5 - - sources = p_normal(queue, nsources, knl.dim, dtype, seed=15) - - from boxtree import TreeBuilder - tb = TreeBuilder(ctx) - - tree, _ = tb(queue, sources, - max_particles_in_box=30, debug=True) - - from boxtree.traversal import FMMTraversalBuilder - tbuild = FMMTraversalBuilder(ctx) - trav, _ = tbuild(queue, tree, debug=True) - - from pyopencl.clrandom import PhiloxGenerator - rng = PhiloxGenerator(ctx, seed=12) - weights = rng.uniform(queue, nsources, dtype=np.float64) - - target_to_source = np.arange(tree.ntargets, dtype=np.int32) - self_extra_kwargs = {"target_to_source": target_to_source} - - from functools import partial - - from sumpy.kernel import TargetPointMultiplier, AxisTargetDerivative - - tgt_knls = [TargetPointMultiplier(0, knl), knl, knl] - for axis in deriv_axes: - tgt_knls[0] = AxisTargetDerivative(axis, tgt_knls[0]) - tgt_knls[1] = AxisTargetDerivative(axis, tgt_knls[1]) - - tree_indep = SumpyTreeIndependentDataForWrangler( - ctx, - partial(mpole_expn_class, knl), - partial(local_expn_class, knl), - target_kernels=tgt_knls, - source_kernels=[knl], - exclude_self=True) - - wrangler = SumpyExpansionWrangler(tree_indep, trav, dtype, - fmm_level_to_order=lambda kernel, kernel_args, tree, lev: order, - self_extra_kwargs=self_extra_kwargs, - translation_classes_data=SumpyTranslationClassesData(queue, trav)) - - from boxtree.fmm import drive_fmm - - pot0, pot1, pot2 = drive_fmm(wrangler, (weights,)) - pot0, pot1, pot2 = pot0.get(), pot1.get(), pot2.get() - if deriv_axes == (0,): - ref_pot = pot1 * sources[0].get() + pot2 - else: - ref_pot = pot1 * sources[0].get() - - rel_err = la.norm(pot0 - ref_pot) / la.norm(ref_pot) - logger.info("order %d -> relative l2 error: %g", order, rel_err) - - assert np.isclose(rel_err, 0, atol=1e-5) - - -# You can test individual routines by typing -# $ python test_fmm.py 'test_sumpy_fmm(cl.create_some_context, LaplaceKernel(2), -# VolumeTaylorLocalExpansion, VolumeTaylorMultipoleExpansion, False, False)' - -if __name__ == "__main__": - if len(sys.argv) > 1: - exec(sys.argv[1]) - else: - from pytest import main - main([__file__]) - -# vim: fdm=marker diff --git a/test/test_matrixgen.py b/test/test_matrixgen.py deleted file mode 100644 index ed90ee1e5..000000000 --- a/test/test_matrixgen.py +++ /dev/null @@ -1,259 +0,0 @@ -__copyright__ = "Copyright (C) 2018 Alexandru Fikl" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -import sys -import numpy as np -import numpy.linalg as la - -import pyopencl as cl -import pyopencl.array # noqa - -from sumpy.tools import vector_to_device - -import pytest -from pyopencl.tools import ( # noqa - pytest_generate_tests_for_pyopencl as pytest_generate_tests) - - -import logging -logger = logging.getLogger(__name__) - -import faulthandler -faulthandler.enable() - - -def _build_geometry(queue, ntargets, nsources, mode, target_radius=1.0): - # source points - t = np.linspace(0.0, 2.0 * np.pi, nsources, endpoint=False) - sources = np.array([np.cos(t), np.sin(t)]) - - # density - sigma = np.cos(mode * t) - - # target points - t = np.linspace(0.0, 2.0 * np.pi, ntargets, endpoint=False) - targets = target_radius * np.array([np.cos(t), np.sin(t)]) - - # target centers and expansion radii - h = 2.0 * np.pi * target_radius / ntargets - radius = 7.0 * h - centers = (1.0 - radius) * targets - expansion_radii = np.full(ntargets, radius) - - return (cl.array.to_device(queue, targets), - cl.array.to_device(queue, sources), - vector_to_device(queue, centers), - cl.array.to_device(queue, expansion_radii), - cl.array.to_device(queue, sigma)) - - -def _build_subset_indices(queue, ntargets, nsources, factor): - tgtindices = np.arange(0, ntargets) - srcindices = np.arange(0, nsources) - - rng = np.random.default_rng() - if abs(factor - 1.0) > 1.0e-14: - tgtindices = rng.choice(tgtindices, - size=int(factor * ntargets), replace=False) - srcindices = rng.choice(srcindices, - size=int(factor * nsources), replace=False) - else: - rng.shuffle(tgtindices) - rng.shuffle(srcindices) - - tgtindices, srcindices = np.meshgrid(tgtindices, srcindices) - return ( - cl.array.to_device(queue, tgtindices.ravel()).with_queue(None), - cl.array.to_device(queue, srcindices.ravel()).with_queue(None)) - - -@pytest.mark.parametrize("factor", [1.0, 0.6]) -@pytest.mark.parametrize("lpot_id", [1, 2]) -def test_qbx_direct(ctx_factory, factor, lpot_id): - logging.basicConfig(level=logging.INFO) - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) - - ndim = 2 - order = 12 - mode_nr = 25 - - from sumpy.kernel import LaplaceKernel, DirectionalSourceDerivative - if lpot_id == 1: - base_knl = LaplaceKernel(ndim) - knl = base_knl - elif lpot_id == 2: - base_knl = LaplaceKernel(ndim) - knl = DirectionalSourceDerivative(base_knl, dir_vec_name="dsource_vec") - else: - raise ValueError("unknow lpot_id") - - from sumpy.expansion.local import LineTaylorLocalExpansion - expn = LineTaylorLocalExpansion(knl, order) - - from sumpy.qbx import LayerPotential - lpot = LayerPotential(ctx, expansion=expn, source_kernels=(knl,), - target_kernels=(base_knl,)) - - from sumpy.qbx import LayerPotentialMatrixGenerator - mat_gen = LayerPotentialMatrixGenerator(ctx, expansion=expn, - source_kernels=(knl,), target_kernels=(base_knl,)) - - from sumpy.qbx import LayerPotentialMatrixSubsetGenerator - blk_gen = LayerPotentialMatrixSubsetGenerator(ctx, expansion=expn, - source_kernels=(knl,), target_kernels=(base_knl,)) - - for n in [200, 300, 400]: - targets, sources, centers, expansion_radii, sigma = \ - _build_geometry(queue, n, n, mode_nr, target_radius=1.2) - - h = 2 * np.pi / n - strengths = (sigma * h,) - tgtindices, srcindices = _build_subset_indices(queue, - ntargets=n, nsources=n, factor=factor) - - extra_kwargs = {} - if lpot_id == 2: - from pytools.obj_array import make_obj_array - extra_kwargs["dsource_vec"] = \ - vector_to_device(queue, make_obj_array(np.ones((ndim, n)))) - - _, (result_lpot,) = lpot(queue, - targets=targets, - sources=sources, - centers=centers, - expansion_radii=expansion_radii, - strengths=strengths, **extra_kwargs) - result_lpot = result_lpot.get() - - _, (mat,) = mat_gen(queue, - targets=targets, - sources=sources, - centers=centers, - expansion_radii=expansion_radii, **extra_kwargs) - mat = mat.get() - result_mat = mat.dot(strengths[0].get()) - - _, (blk,) = blk_gen(queue, - targets=targets, - sources=sources, - centers=centers, - expansion_radii=expansion_radii, - tgtindices=tgtindices, - srcindices=srcindices, **extra_kwargs) - blk = blk.get() - - tgtindices = tgtindices.get(queue) - srcindices = srcindices.get(queue) - - eps = 1.0e-10 * la.norm(result_lpot) - assert la.norm(result_mat - result_lpot) < eps - assert la.norm(blk - mat[tgtindices, srcindices]) < eps - - -@pytest.mark.parametrize("exclude_self", [True, False]) -@pytest.mark.parametrize("factor", [1.0, 0.6]) -@pytest.mark.parametrize("lpot_id", [1, 2]) -def test_p2p_direct(ctx_factory, exclude_self, factor, lpot_id): - logging.basicConfig(level=logging.INFO) - - ctx = ctx_factory() - queue = cl.CommandQueue(ctx) - - ndim = 2 - mode_nr = 25 - - from sumpy.kernel import LaplaceKernel, DirectionalSourceDerivative - if lpot_id == 1: - lknl = LaplaceKernel(ndim) - elif lpot_id == 2: - lknl = LaplaceKernel(ndim) - lknl = DirectionalSourceDerivative(lknl, dir_vec_name="dsource_vec") - else: - raise ValueError("unknow lpot_id") - - from sumpy.p2p import P2P - lpot = P2P(ctx, [lknl], exclude_self=exclude_self) - - from sumpy.p2p import P2PMatrixGenerator - mat_gen = P2PMatrixGenerator(ctx, [lknl], exclude_self=exclude_self) - - from sumpy.p2p import P2PMatrixSubsetGenerator - blk_gen = P2PMatrixSubsetGenerator(ctx, [lknl], exclude_self=exclude_self) - - for n in [200, 300, 400]: - targets, sources, _, _, sigma = \ - _build_geometry(queue, n, n, mode_nr, target_radius=1.2) - - h = 2 * np.pi / n - strengths = (sigma * h,) - tgtindices, srcindices = _build_subset_indices(queue, - ntargets=n, nsources=n, factor=factor) - - extra_kwargs = {} - if exclude_self: - extra_kwargs["target_to_source"] = \ - cl.array.arange(queue, 0, n, dtype=np.int32) - if lpot_id == 2: - from pytools.obj_array import make_obj_array - extra_kwargs["dsource_vec"] = \ - vector_to_device(queue, make_obj_array(np.ones((ndim, n)))) - - _, (result_lpot,) = lpot(queue, - targets=targets, - sources=sources, - strength=strengths, **extra_kwargs) - result_lpot = result_lpot.get() - - _, (mat,) = mat_gen(queue, - targets=targets, - sources=sources, **extra_kwargs) - mat = mat.get() - result_mat = mat.dot(strengths[0].get()) - - _, (blk,) = blk_gen(queue, - targets=targets, - sources=sources, - tgtindices=tgtindices, - srcindices=srcindices, **extra_kwargs) - blk = blk.get() - - tgtindices = tgtindices.get(queue) - srcindices = srcindices.get(queue) - - eps = 1.0e-10 * la.norm(result_lpot) - assert la.norm(result_mat - result_lpot) < eps - assert la.norm(blk - mat[tgtindices, srcindices]) < eps - - -# You can test individual routines by typing -# $ python test_kernels.py "test_p2p(cl.create_some_context)" - -if __name__ == "__main__": - if len(sys.argv) > 1: - exec(sys.argv[1]) - else: - from pytest import main - main([__file__]) - -# vim: fdm=marker diff --git a/test/test_misc.py b/test/test_misc.py deleted file mode 100644 index 42956bc6e..000000000 --- a/test/test_misc.py +++ /dev/null @@ -1,473 +0,0 @@ -__copyright__ = "Copyright (C) 2017 Andreas Kloeckner" - -__license__ = """ -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. -""" - -import sys -from dataclasses import dataclass -from typing import Any, Callable - -import numpy as np -import numpy.linalg as la - -import sumpy.toys as t -import sumpy.symbolic as sym - -import pytest -import pyopencl as cl # noqa: F401 -from pyopencl.tools import ( # noqa - pytest_generate_tests_for_pyopencl as pytest_generate_tests) - -from sumpy.kernel import (LaplaceKernel, HelmholtzKernel, - BiharmonicKernel, YukawaKernel, StokesletKernel, StressletKernel, - ElasticityKernel, LineOfCompressionKernel, ExpressionKernel) -from sumpy.expansion.diff_op import (make_identity_diff_op, gradient, - divergence, laplacian, concat, as_scalar_pde, curl, diff) - - -# {{{ pde check for kernels - -class KernelInfo: - def __init__(self, kernel, **kwargs): - self.kernel = kernel - self.extra_kwargs = kwargs - diff_op = self.kernel.get_pde_as_diff_op() - assert len(diff_op.eqs) == 1 - eq = diff_op.eqs[0] - self.eq = eq - - def pde_func(self, cp, pot): - subs_dict = {sym.Symbol(k): v for k, v in self.extra_kwargs.items()} - result = 0 - for ident, coeff in self.eq.items(): - lresult = pot - for axis, nderivs in enumerate(ident.mi): - lresult = cp.diff(axis, lresult, nderivs) - result += lresult*float(sym.sympify(coeff).xreplace(subs_dict)) - return result - - @property - def nderivs(self): - return max(sum(ident.mi) for ident in self.eq.keys()) - - -@pytest.mark.parametrize("knl_info", [ - KernelInfo(BiharmonicKernel(2)), - KernelInfo(BiharmonicKernel(3)), - KernelInfo(YukawaKernel(2), lam=5), - KernelInfo(YukawaKernel(3), lam=5), - KernelInfo(LaplaceKernel(2)), - KernelInfo(LaplaceKernel(3)), - KernelInfo(HelmholtzKernel(2), k=5), - KernelInfo(HelmholtzKernel(3), k=5), - KernelInfo(StokesletKernel(2, 0, 1), mu=5), - KernelInfo(StokesletKernel(2, 1, 1), mu=5), - KernelInfo(StokesletKernel(3, 0, 1), mu=5), - KernelInfo(StokesletKernel(3, 1, 1), mu=5), - KernelInfo(StressletKernel(2, 0, 0, 0), mu=5), - KernelInfo(StressletKernel(2, 0, 0, 1), mu=5), - KernelInfo(StressletKernel(3, 0, 0, 0), mu=5), - KernelInfo(StressletKernel(3, 0, 0, 1), mu=5), - KernelInfo(StressletKernel(3, 0, 1, 2), mu=5), - KernelInfo(ElasticityKernel(2, 0, 1), mu=5, nu=0.2), - KernelInfo(ElasticityKernel(2, 0, 0), mu=5, nu=0.2), - KernelInfo(ElasticityKernel(3, 0, 1), mu=5, nu=0.2), - KernelInfo(ElasticityKernel(3, 0, 0), mu=5, nu=0.2), - KernelInfo(LineOfCompressionKernel(3, 0), mu=5, nu=0.2), - KernelInfo(LineOfCompressionKernel(3, 1), mu=5, nu=0.2), - ]) -def test_pde_check_kernels(ctx_factory, knl_info, order=5): - dim = knl_info.kernel.dim - tctx = t.ToyContext(ctx_factory(), knl_info.kernel, - extra_source_kwargs=knl_info.extra_kwargs) - - np.random.seed(17) - pt_src = t.PointSources( - tctx, - np.random.rand(dim, 50) - 0.5, - np.ones(50)) - - from pytools.convergence import EOCRecorder - from sumpy.point_calculus import CalculusPatch - eoc_rec = EOCRecorder() - - for h in [0.1, 0.05, 0.025]: - cp = CalculusPatch(np.array([1, 0, 0])[:dim], h=h, order=order) - pot = pt_src.eval(cp.points) - - pde = knl_info.pde_func(cp, pot) - - err = la.norm(pde) - eoc_rec.add_data_point(h, err) - - print(eoc_rec) - assert eoc_rec.order_estimate() > order - knl_info.nderivs + 1 - 0.1 - -# }}} - - -@pytest.mark.parametrize("dim", [1, 2, 3]) -def test_pde_check(dim, order=4): - from sumpy.point_calculus import CalculusPatch - from pytools.convergence import EOCRecorder - - for iaxis in range(dim): - eoc_rec = EOCRecorder() - for h in [0.1, 0.01, 0.001]: - cp = CalculusPatch(np.array([3, 0, 0])[:dim], h=h, order=order) - df_num = cp.diff(iaxis, np.sin(10*cp.points[iaxis])) - df_true = 10*np.cos(10*cp.points[iaxis]) - - err = la.norm(df_num-df_true) - eoc_rec.add_data_point(h, err) - - print(eoc_rec) - assert eoc_rec.order_estimate() > order-2-0.1 - - -class FakeTree: - def __init__(self, dimensions, root_extent, stick_out_factor): - self.dimensions = dimensions - self.root_extent = root_extent - self.stick_out_factor = stick_out_factor - - -@pytest.mark.parametrize("knl", [ - LaplaceKernel(2), HelmholtzKernel(2), - LaplaceKernel(3), HelmholtzKernel(3)]) -def test_order_finder(knl): - from sumpy.expansion.level_to_order import SimpleExpansionOrderFinder - - ofind = SimpleExpansionOrderFinder(1e-5) - - tree = FakeTree(knl.dim, 200, 0.5) - orders = [ - ofind(knl, frozenset([("k", 5)]), tree, level) - for level in range(30)] - print(orders) - - # Order should not increase with level - assert (np.diff(orders) <= 0).all() - - -@pytest.mark.parametrize("knl", [ - LaplaceKernel(2), HelmholtzKernel(2), - LaplaceKernel(3), HelmholtzKernel(3)]) -def test_fmmlib_order_finder(knl): - pytest.importorskip("pyfmmlib") - from sumpy.expansion.level_to_order import FMMLibExpansionOrderFinder - - ofind = FMMLibExpansionOrderFinder(1e-5) - - tree = FakeTree(knl.dim, 200, 0.5) - orders = [ - ofind(knl, frozenset([("k", 5)]), tree, level) - for level in range(30)] - print(orders) - - # Order should not increase with level - assert (np.diff(orders) <= 0).all() - - -# {{{ expansion toys p2e2e2p test cases - -def approx_convergence_factor(orders, errors): - poly = np.polyfit(orders, np.log(errors), deg=1) - return np.exp(poly[0]) - - -@dataclass -class P2E2E2PTestCase: - source: np.ndarray - target: np.ndarray - center1: np.ndarray - center2: np.ndarray - expansion1: Callable[..., Any] - expansion2: Callable[..., Any] - conv_factor: str - - @property - def dim(self): - return len(self.source) - - -P2E2E2P_TEST_CASES = ( - # local to local, 3D - P2E2E2PTestCase( - source=np.array([3., 4., 5.]), - center1=np.array([1., 0., 0.]), - center2=np.array([1., 3., 0.]), - target=np.array([1., 1., 1.]), - expansion1=t.local_expand, - expansion2=t.local_expand, - conv_factor="norm(t-c1)/norm(s-c1)"), - - # multipole to multipole, 3D - P2E2E2PTestCase( - source=np.array([1., 1., 1.]), - center1=np.array([1., 0., 0.]), - center2=np.array([1., 0., 3.]), - target=np.array([3., 4., 5.]), - expansion1=t.multipole_expand, - expansion2=t.multipole_expand, - conv_factor="norm(s-c2)/norm(t-c2)"), - - # multipole to local, 3D - P2E2E2PTestCase( - source=np.array([-2., 2., 1.]), - center1=np.array([-2., 5., 3.]), - center2=np.array([0., 0., 0.]), - target=np.array([0., 0., -1]), - expansion1=t.multipole_expand, - expansion2=t.local_expand, - conv_factor="norm(t-c2)/(norm(c2-c1)-norm(c1-s))"), -) - -# }}} - - -ORDERS_P2E2E2P = (3, 4, 5) -RTOL_P2E2E2P = 1e-2 - - -@pytest.mark.parametrize("case", P2E2E2P_TEST_CASES) -def test_toy_p2e2e2p(ctx_factory, case): - dim = case.dim - - src = case.source.reshape(dim, -1) - tgt = case.target.reshape(dim, -1) - - from pymbolic import parse, evaluate - case_conv_factor = evaluate(parse(case.conv_factor), { - "s": case.source, - "c1": case.center1, - "c2": case.center2, - "t": case.target, - "norm": la.norm, - }) - - if not 0 <= case_conv_factor <= 1: - raise ValueError( - f"convergence factor not in valid range: {case_conv_factor}") - - from sumpy.expansion.local import VolumeTaylorLocalExpansion - from sumpy.expansion.multipole import VolumeTaylorMultipoleExpansion - - cl_ctx = ctx_factory() - ctx = t.ToyContext(cl_ctx, - LaplaceKernel(dim), - VolumeTaylorMultipoleExpansion, - VolumeTaylorLocalExpansion) - - errors = [] - - src_pot = t.PointSources(ctx, src, weights=np.array([1.])) - pot_actual = src_pot.eval(tgt).item() - - for order in ORDERS_P2E2E2P: - expn = case.expansion1(src_pot, case.center1, order=order) - expn2 = case.expansion2(expn, case.center2, order=order) - pot_p2e2e2p = expn2.eval(tgt).item() - errors.append(np.abs(pot_actual - pot_p2e2e2p)) - - conv_factor = approx_convergence_factor(1 + np.array(ORDERS_P2E2E2P), errors) - assert conv_factor <= min(1, case_conv_factor * (1 + RTOL_P2E2E2P)), \ - (conv_factor, case_conv_factor * (1 + RTOL_P2E2E2P)) - - -def test_cse_matvec(): - from sumpy.expansion import CSEMatVecOperator - input_coeffs = [ - [(0, 2)], - [], - [(1, 1)], - [(1, 9)], - ] - - output_coeffs = [ - [], - [(0, 3)], - [], - [(2, 7), (1, 5)], - ] - - op = CSEMatVecOperator(input_coeffs, output_coeffs, shape=(4, 2)) - m = np.array([[2, 0], [6, 0], [0, 1], [30, 16]]) - - vec = np.random.random(2) - expected_result = m @ vec - actual_result = op.matvec(vec) - assert np.allclose(expected_result, actual_result) - - vec = np.random.random(4) - expected_result = m.T @ vec - actual_result = op.transpose_matvec(vec) - assert np.allclose(expected_result, actual_result) - - -def test_diff_op_stokes(): - from sumpy.symbolic import symbols, Function - diff_op = make_identity_diff_op(3, 4) - u = diff_op[:3] - p = diff_op[3] - pde = concat(laplacian(u) - gradient(p), divergence(u)) - - actual_output = pde.to_sym() - x, y, z = syms = symbols("x0, x1, x2") - funcs = symbols("f0, f1, f2, f3", cls=Function) - u, v, w, p = (f(*syms) for f in funcs) - - eq1 = u.diff(x, x) + u.diff(y, y) + u.diff(z, z) - p.diff(x) - eq2 = v.diff(x, x) + v.diff(y, y) + v.diff(z, z) - p.diff(y) - eq3 = w.diff(x, x) + w.diff(y, y) + w.diff(z, z) - p.diff(z) - eq4 = u.diff(x) + v.diff(y) + w.diff(z) - - expected_output = [eq1, eq2, eq3, eq4] - - assert expected_output == actual_output - - -def test_as_scalar_pde_stokes(): - diff_op = make_identity_diff_op(3, 4) - u = diff_op[:3] - p = diff_op[3] - pde = concat(laplacian(u) - gradient(p), divergence(u)) - - # velocity components in Stokes should satisfy Biharmonic - for i in range(3): - print(as_scalar_pde(pde, i)) - print(laplacian(laplacian(u[i]))) - assert as_scalar_pde(pde, i) == laplacian(laplacian(u[0])) - - # pressure should satisfy Laplace - assert as_scalar_pde(pde, 3) == laplacian(u[0]) - - -def test_as_scalar_pde_maxwell(): - from sumpy.symbolic import symbols - op = make_identity_diff_op(3, 6, time_dependent=True) - E = op[:3] # noqa: N806 - B = op[3:] # noqa: N806 - mu, epsilon = symbols("mu, epsilon") - t = (0, 0, 0, 1) - - pde = concat(curl(E) + diff(B, t), curl(B) - mu*epsilon*diff(E, t), - divergence(E), divergence(B)) - as_scalar_pde(pde, 3) - - for i in range(6): - assert as_scalar_pde(pde, i) == \ - laplacian(op[0]) - mu*epsilon*diff(diff(op[0], t), t) - - -def test_as_scalar_pde_elasticity(): - - # Ref: https://doi.org/10.1006/jcph.1996.0102 - - diff_op = make_identity_diff_op(2, 5) - sigma_x = diff_op[0] - sigma_y = diff_op[1] - tau = diff_op[2] - u = diff_op[3] - v = diff_op[4] - - # Use numeric values as the expressions grow exponentially large otherwise - from sumpy.symbolic import symbols - lam, mu = symbols("lam, mu") - - x = (1, 0) - y = (0, 1) - - exprs = [ - diff(sigma_x, x) + diff(tau, y), - diff(tau, x) + diff(sigma_y, y), - sigma_x - (lam + 2*mu)*diff(u, x) - lam*diff(v, y), - sigma_y - (lam + 2*mu)*diff(v, y) - lam*diff(u, x), - tau - mu*(diff(u, y) + diff(v, x)), - ] - - pde = concat(*exprs) - assert pde.order == 1 - for i in range(5): - scalar_pde = as_scalar_pde(pde, i) - assert scalar_pde == laplacian(laplacian(diff_op[0])) - assert scalar_pde.order == 4 - - -def test_elasticity_new(): - from pickle import dumps, loads - stokes_knl = StokesletKernel(3, 0, 1, "mu1", 0.5) - stokes_knl2 = ElasticityKernel(3, 0, 1, "mu1", 0.5) - elasticity_knl = ElasticityKernel(3, 0, 1, "mu1", "nu") - elasticity_helper_knl = LineOfCompressionKernel(3, 0, "mu1", "nu") - - assert isinstance(stokes_knl2, StokesletKernel) - assert stokes_knl == stokes_knl2 - assert loads(dumps(stokes_knl)) == stokes_knl - - for knl in [elasticity_knl, elasticity_helper_knl]: - assert not isinstance(knl, StokesletKernel) - assert loads(dumps(knl)) == knl - - -w = make_identity_diff_op(2) - -pdes = [ - diff(w, (1, 1)) + diff(w, (2, 0)), - diff(w, (1, 1)) + diff(w, (0, 2)), -] - - -@pytest.mark.parametrize("pde", pdes) -def test_weird_kernel(pde): - class MyKernel(ExpressionKernel): - def __init__(self): - super().__init__(dim=2, expression=1, global_scaling_const=1, - is_complex_valued=False) - - def get_pde_as_diff_op(self): - return pde - - from sumpy.expansion import LinearPDEConformingVolumeTaylorExpansion - from operator import mul - from functools import reduce - - knl = MyKernel() - order = 10 - expn = LinearPDEConformingVolumeTaylorExpansion(kernel=knl, - order=order, use_rscale=False) - - coeffs = expn.get_coefficient_identifiers() - fft_size = reduce(mul, map(max, *coeffs), 1) - - assert fft_size == order - - -# You can test individual routines by typing -# $ python test_misc.py 'test_p2p(cl.create_some_context)' - -if __name__ == "__main__": - if len(sys.argv) > 1: - exec(sys.argv[1]) - else: - from pytest import main - main([__file__]) - -# vim: fdm=marker