pysiglib 2.2.0__tar.gz → 2.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. {pysiglib-2.2.0 → pysiglib-2.3.0}/CMakeLists.txt +55 -3
  2. {pysiglib-2.2.0 → pysiglib-2.3.0}/PKG-INFO +1 -1
  3. {pysiglib-2.2.0 → pysiglib-2.3.0}/pyproject.toml +1 -1
  4. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/__init__.py +2 -2
  5. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/param_checks.py +14 -0
  6. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/sig_kernel.py +58 -70
  7. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/sig_kernel_backprop.py +3 -16
  8. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/sig_metrics.py +16 -16
  9. pysiglib-2.3.0/pysiglib/static_kernels.py +418 -0
  10. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/torch_api/__init__.py +1 -1
  11. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/torch_api/torch_api.py +30 -21
  12. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_bch.h +8 -8
  13. pysiglib-2.3.0/siglib/cpsig/cp_sig_kernel.h +432 -0
  14. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_tensor_poly.h +2 -2
  15. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_vector_funcs.h +156 -8
  16. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/macros.h +4 -0
  17. pysiglib-2.3.0/siglib/cpsig/multithreading.h +157 -0
  18. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_sig_kernel.cu +224 -249
  19. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_signature.cu +30 -9
  20. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_tensor_poly.cu +2 -2
  21. pysiglib-2.3.0/tests/test_static_kernels.py +615 -0
  22. pysiglib-2.2.0/pysiglib/static_kernels.py +0 -206
  23. pysiglib-2.2.0/siglib/cpsig/cp_sig_kernel.h +0 -739
  24. pysiglib-2.2.0/siglib/cpsig/multithreading.h +0 -196
  25. {pysiglib-2.2.0 → pysiglib-2.3.0}/.gitattributes +0 -0
  26. {pysiglib-2.2.0 → pysiglib-2.3.0}/.gitignore +0 -0
  27. {pysiglib-2.2.0 → pysiglib-2.3.0}/CITATION.cff +0 -0
  28. {pysiglib-2.2.0 → pysiglib-2.3.0}/LICENSE +0 -0
  29. {pysiglib-2.2.0 → pysiglib-2.3.0}/README.md +0 -0
  30. {pysiglib-2.2.0 → pysiglib-2.3.0}/avx_info/avx_info.cpp +0 -0
  31. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/README.md +0 -0
  32. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/cpu_vs_cuda/log_sig_times_cpu_vs_cuda_plot.py +0 -0
  33. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/cpu_vs_cuda/sig_backprop_times_cpu_vs_cuda_plot.py +0 -0
  34. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/cpu_vs_cuda/sig_coef_times_cpu_vs_cuda_plot.py +0 -0
  35. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/cpu_vs_cuda/sig_combine_times_cpu_vs_cuda_plot.py +0 -0
  36. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/cpu_vs_cuda/sig_kernel_backprop_times_cpu_vs_cuda_plot.py +0 -0
  37. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/cpu_vs_cuda/sig_kernel_times_cpu_vs_cuda_plot.py +0 -0
  38. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/cpu_vs_cuda/sig_times_cpu_vs_cuda_plot.py +0 -0
  39. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/log_sig_times_plot.py +0 -0
  40. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/plotting_params.py +0 -0
  41. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/sig_backprop_times.py +0 -0
  42. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/sig_backprop_times_plot.py +0 -0
  43. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/sig_combine_example.py +0 -0
  44. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/sig_kernel_backprop_times.py +0 -0
  45. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/sig_kernel_backprop_times_plot.py +0 -0
  46. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/sig_kernel_example.py +0 -0
  47. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/sig_kernel_times.py +0 -0
  48. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/sig_kernel_times_plot.py +0 -0
  49. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/sig_times.py +0 -0
  50. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/sig_times_plot.py +0 -0
  51. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/signature_example.py +0 -0
  52. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/small_example.py +0 -0
  53. {pysiglib-2.2.0 → pysiglib-2.3.0}/examples/timing_utils.py +0 -0
  54. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/README.md +0 -0
  55. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/data_handlers.py +0 -0
  56. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/dtypes.py +0 -0
  57. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/error_codes.py +0 -0
  58. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/load_siglib.py +0 -0
  59. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/log_sig.py +0 -0
  60. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/log_sig_backprop.py +0 -0
  61. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/log_sig_combine.py +0 -0
  62. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/sig.py +0 -0
  63. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/sig_backprop.py +0 -0
  64. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/sig_coef.py +0 -0
  65. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/sig_coef_backprop.py +0 -0
  66. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/sig_length.py +0 -0
  67. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/transform_path.py +0 -0
  68. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/transform_path_backprop.py +0 -0
  69. {pysiglib-2.2.0 → pysiglib-2.3.0}/pysiglib/words.py +0 -0
  70. {pysiglib-2.2.0 → pysiglib-2.3.0}/requirements.txt +0 -0
  71. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/.gitignore +0 -0
  72. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/README.md +0 -0
  73. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/CMakeLists.txt +0 -0
  74. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_bch.cpp +0 -0
  75. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_bch_data.h +0 -0
  76. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_log_signature.h +0 -0
  77. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_log_signatures.cpp +0 -0
  78. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_path.h +0 -0
  79. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_path_transforms.cpp +0 -0
  80. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_path_transforms.h +0 -0
  81. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_sig_coef.cpp +0 -0
  82. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_sig_coef.h +0 -0
  83. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_sig_kernel.cpp +0 -0
  84. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_signature.cpp +0 -0
  85. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_signature.h +0 -0
  86. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cp_tensor_poly.cpp +0 -0
  87. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cppch.cpp +0 -0
  88. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cppch.h +0 -0
  89. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cpsig.cpp +0 -0
  90. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/cpsig.h +0 -0
  91. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/log_sig_cache.cpp +0 -0
  92. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/log_sig_cache.h +0 -0
  93. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/sparse.h +0 -0
  94. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/words.cpp +0 -0
  95. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig/words.h +0 -0
  96. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cpsig_unit_testing/cp_unit_tests.cpp +0 -0
  97. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/CMakeLists.txt +0 -0
  98. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_atomic.h +0 -0
  99. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_log_sig_backprop.h +0 -0
  100. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_log_sig_cache.cu +0 -0
  101. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_log_sig_cache.h +0 -0
  102. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_log_sig_combine.cu +0 -0
  103. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_log_sig_combine.h +0 -0
  104. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_log_signature.cu +0 -0
  105. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_log_signature.h +0 -0
  106. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_macros.h +0 -0
  107. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_path_transforms.cu +0 -0
  108. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_path_transforms.h +0 -0
  109. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_sig_coef.cu +0 -0
  110. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_sig_coef.h +0 -0
  111. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_sig_kernel.h +0 -0
  112. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_signature.h +0 -0
  113. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cu_tensor_poly.h +0 -0
  114. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cuda_constants.h +0 -0
  115. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cupch.cpp +0 -0
  116. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cupch.h +0 -0
  117. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig/cusig.h +0 -0
  118. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/cusig_unit_testing/cu_unit_tests.cpp +0 -0
  119. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/shared/bch_data.h +0 -0
  120. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/siglib.sln +0 -0
  121. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/test_app/dll_funcs.cpp +0 -0
  122. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/test_app/dll_funcs.h +0 -0
  123. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/test_app/test_app.cpp +0 -0
  124. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/test_app/tests.cpp +0 -0
  125. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/test_app/tests.h +0 -0
  126. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/test_app/utils.cpp +0 -0
  127. {pysiglib-2.2.0 → pysiglib-2.3.0}/siglib/test_app/utils.h +0 -0
  128. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/conftest.py +0 -0
  129. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_apis_match.py +0 -0
  130. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_errors.py +0 -0
  131. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_log_sig.py +0 -0
  132. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_log_sig_backprop.py +0 -0
  133. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_log_sig_combine.py +0 -0
  134. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_log_sig_combine_backprop.py +0 -0
  135. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_log_sig_combine_cuda.py +0 -0
  136. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_backprop.py +0 -0
  137. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_coef.py +0 -0
  138. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_coef_backprop.py +0 -0
  139. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_combine.py +0 -0
  140. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_combine_backprop.py +0 -0
  141. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_kernel.py +0 -0
  142. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_kernel_backprop.py +0 -0
  143. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_kernel_backprop_grid.py +0 -0
  144. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_kernel_gram.py +0 -0
  145. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_kernel_gram_backprop.py +0 -0
  146. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_kernel_gram_backprop_grid.py +0 -0
  147. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_length.py +0 -0
  148. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_metrics.py +0 -0
  149. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_sig_metrics_backprop.py +0 -0
  150. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_signature.py +0 -0
  151. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_transform_path.py +0 -0
  152. {pysiglib-2.2.0 → pysiglib-2.3.0}/tests/test_words.py +0 -0
@@ -31,14 +31,32 @@ endif()
31
31
  # CUDA detection
32
32
  # ---------------------------------------------------------------------------
33
33
  set(_cuda_enabled FALSE)
34
+ set(_cuda_version "")
34
35
  if(PYSIGLIB_CUDA)
35
36
  check_language(CUDA)
36
37
  if(CMAKE_CUDA_COMPILER)
37
38
  enable_language(CUDA)
38
39
  find_package(CUDAToolkit REQUIRED)
39
40
  set(_cuda_enabled TRUE)
41
+ set(_cuda_version "${CUDAToolkit_VERSION}")
40
42
  else()
41
43
  message(STATUS "CUDA compiler not found — building without CUDA")
44
+ message(STATUS " CUDA_PATH env var = '$ENV{CUDA_PATH}'")
45
+ find_program(_nvcc_probe nvcc
46
+ HINTS "$ENV{CUDA_PATH}/bin" "$ENV{CUDA_PATH}/Library/bin")
47
+ if(_nvcc_probe)
48
+ message(STATUS " nvcc found at: ${_nvcc_probe}")
49
+ message(STATUS " nvcc was found but failed to compile a test program.")
50
+ message(STATUS " Common causes:")
51
+ message(STATUS " - cl.exe (MSVC) is not on PATH — install Visual Studio Build Tools")
52
+ message(STATUS " - CUDA Visual Studio integration files are missing — install the")
53
+ message(STATUS " CUDA Toolkit from https://developer.nvidia.com/cuda-downloads")
54
+ message(STATUS " (the conda cuda-toolkit package does not include VS integration)")
55
+ else()
56
+ message(STATUS " nvcc was not found on PATH or in CUDA_PATH")
57
+ message(STATUS " Install the CUDA Toolkit from https://developer.nvidia.com/cuda-downloads")
58
+ endif()
59
+ message(STATUS " CMAKE_GENERATOR = ${CMAKE_GENERATOR}")
42
60
  endif()
43
61
  endif()
44
62
 
@@ -77,8 +95,39 @@ if(PYSIGLIB_AVX)
77
95
  endif()
78
96
  endif()
79
97
 
80
- message(STATUS "pysiglib — CUDA enabled : ${_cuda_enabled}")
81
- message(STATUS "pysiglib Vectorization: ${_enable_vec}")
98
+ message(STATUS "")
99
+ message(STATUS " ---- pysiglib build summary ----")
100
+ message(STATUS " System : ${CMAKE_SYSTEM_NAME} ${CMAKE_SYSTEM_PROCESSOR}")
101
+ message(STATUS " Generator : ${CMAKE_GENERATOR}")
102
+ message(STATUS " CXX compiler : ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")
103
+ if(CMAKE_CONFIGURATION_TYPES)
104
+ message(STATUS " Build configs : ${CMAKE_CONFIGURATION_TYPES}")
105
+ else()
106
+ message(STATUS " Build type : ${CMAKE_BUILD_TYPE}")
107
+ endif()
108
+ message(STATUS " CUDA enabled : ${_cuda_enabled}")
109
+ if(_cuda_enabled)
110
+ message(STATUS " CUDA compiler : ${CMAKE_CUDA_COMPILER}")
111
+ message(STATUS " CUDA version : ${_cuda_version}")
112
+ message(STATUS " CUDA arch : ${PYSIGLIB_CUDA_ARCH}")
113
+ endif()
114
+ message(STATUS " AVX enabled : ${_enable_vec}")
115
+ if(_enable_vec AND NOT APPLE)
116
+ if(_has_avx2)
117
+ set(_avx2_str "yes")
118
+ else()
119
+ set(_avx2_str "no")
120
+ endif()
121
+ if(_has_avx512f)
122
+ set(_avx512_str "yes")
123
+ else()
124
+ set(_avx512_str "no")
125
+ endif()
126
+ message(STATUS " AVX2 : ${_avx2_str}")
127
+ message(STATUS " AVX-512F : ${_avx512_str}")
128
+ endif()
129
+ message(STATUS " --------------------------------")
130
+ message(STATUS "")
82
131
 
83
132
  # ---------------------------------------------------------------------------
84
133
  # Generate _config.py (consumed by pysiglib.load_siglib at runtime)
@@ -99,7 +148,10 @@ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/_config.py"
99
148
  # It contains information about how the package was built.\n\
100
149
  SYSTEM = '${CMAKE_SYSTEM_NAME}'\n\
101
150
  BUILT_WITH_CUDA = ${_cuda_py}\n\
102
- BUILT_WITH_AVX = ${_vec_py}\n")
151
+ BUILT_WITH_AVX = ${_vec_py}\n\
152
+ CUDA_VERSION = '${_cuda_version}'\n\
153
+ CXX_COMPILER = '${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}'\n\
154
+ CMAKE_GENERATOR = '${CMAKE_GENERATOR}'\n")
103
155
 
104
156
  install(FILES "${CMAKE_CURRENT_BINARY_DIR}/_config.py" DESTINATION pysiglib)
105
157
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pysiglib
3
- Version: 2.2.0
3
+ Version: 2.3.0
4
4
  Summary: Fast Signature Computations on CPU and GPU
5
5
  Author-Email: Daniil Shmelev <daniil.shmelev23@imperial.ac.uk>
6
6
  License-Expression: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "scikit_build_core.build"
4
4
 
5
5
  [project]
6
6
  name = "pysiglib"
7
- version = "2.2.0"
7
+ version = "2.3.0"
8
8
  description = "Fast Signature Computations on CPU and GPU"
9
9
  requires-python = ">=3.9"
10
10
  license = "Apache-2.0"
@@ -28,10 +28,10 @@ from .sig_kernel_backprop import sig_kernel_backprop, sig_kernel_gram_backprop
28
28
  from .sig_metrics import sig_score, expected_sig_score, sig_mmd
29
29
  from .transform_path import transform_path
30
30
  from .transform_path_backprop import transform_path_backprop
31
- from .static_kernels import Context, StaticKernel, LinearKernel, ScaledLinearKernel, RBFKernel
31
+ from .static_kernels import Context, StaticKernel, LinearKernel, ScaledLinearKernel, RBFKernel, PolynomialKernel, Matern12Kernel, Matern32Kernel, Matern52Kernel, RationalQuadraticKernel
32
32
 
33
33
  signature = sig
34
34
 
35
35
  import pysiglib.torch_api
36
36
 
37
- __version__ = "2.2.0"
37
+ __version__ = "2.3.0"
@@ -87,6 +87,20 @@ def ensure_own_contiguous_storage(arr):
87
87
 
88
88
  raise TypeError("Unexpected error in ensure_own_contiguous_storage: arr must be of type torch.Tensor or numpy.ndarray")
89
89
 
90
+ def parse_dyadic_order(dyadic_order):
91
+ if isinstance(dyadic_order, tuple) and len(dyadic_order) == 2:
92
+ do1, do2 = dyadic_order
93
+ elif isinstance(dyadic_order, int):
94
+ do1 = do2 = dyadic_order
95
+ else:
96
+ raise TypeError("dyadic_order must be an integer or a tuple of length 2")
97
+ if do1 < 0 or do2 < 0:
98
+ raise ValueError("dyadic_order must be a non-negative integer or tuple of non-negative integers")
99
+ return do1, do2
100
+
101
+ def dyadic_grid_length(path_length, dyadic_order):
102
+ return ((path_length - 1) << dyadic_order) + 1
103
+
90
104
  def check_log_sig_method(method):
91
105
  if method < 0 or method > 3:
92
106
  raise ValueError("method must be one of 0, 1, 2 or 3. Got " + str(method) + " instead.")
@@ -20,7 +20,7 @@ import numpy as np
20
20
  import torch
21
21
 
22
22
  from .transform_path import transform_path
23
- from .param_checks import check_type
23
+ from .param_checks import check_type, parse_dyadic_order, dyadic_grid_length
24
24
  from .error_codes import err_msg
25
25
  from .dtypes import CPSIG_BATCH_SIG_KERNEL, DTYPES, CUSIG_BATCH_SIG_KERNEL_CUDA
26
26
  from .data_handlers import MultiplePathInputHandler, ScalarOutputHandler, GridOutputHandler
@@ -67,7 +67,8 @@ def sig_kernel(
67
67
  lead_lag : bool = False,
68
68
  end_time : float = 1.,
69
69
  n_jobs : int = 1,
70
- return_grid: bool = False
70
+ return_grid: bool = False,
71
+ normalize : bool = False
71
72
  ) -> Union[np.ndarray, torch.tensor]:
72
73
  """
73
74
  Computes a single signature kernel or a batch of signature kernels.
@@ -121,6 +122,9 @@ def sig_kernel(
121
122
  :type n_jobs: int
122
123
  :param return_grid: If ``True``, returns the entire PDE grid.
123
124
  :type return_grid: bool
125
+ :param normalize: If ``True``, normalizes the signature kernel so that :math:`k(x, x) = 1`
126
+ by dividing by :math:`\\sqrt{k(x, x) \\cdot k(y, y)}`. Cannot be used with ``return_grid=True``.
127
+ :type normalize: bool
124
128
  :return: Single signature kernel or batch of signature kernels
125
129
  :rtype: numpy.ndarray | torch.tensor
126
130
 
@@ -175,18 +179,10 @@ def sig_kernel(
175
179
  check_type(n_jobs, "n_jobs", int)
176
180
  if n_jobs == 0:
177
181
  raise ValueError("n_jobs cannot be 0")
182
+ if normalize and return_grid:
183
+ raise ValueError("normalize=True cannot be used with return_grid=True")
178
184
 
179
- if isinstance(dyadic_order, tuple) and len(dyadic_order) == 2:
180
- dyadic_order_1 = dyadic_order[0]
181
- dyadic_order_2 = dyadic_order[1]
182
- elif isinstance(dyadic_order, int):
183
- dyadic_order_1 = dyadic_order
184
- dyadic_order_2 = dyadic_order
185
- else:
186
- raise TypeError("dyadic_order must be an integer or a tuple of length 2")
187
-
188
- if dyadic_order_1 < 0 or dyadic_order_2 < 0:
189
- raise ValueError("dyadic_order must be a non-negative integer or tuple of non-negative integers")
185
+ dyadic_order_1, dyadic_order_2 = parse_dyadic_order(dyadic_order)
190
186
 
191
187
  if time_aug or lead_lag:
192
188
  path1 = transform_path(path1, time_aug, lead_lag, end_time, n_jobs)
@@ -198,8 +194,8 @@ def sig_kernel(
198
194
  if not return_grid:
199
195
  result = ScalarOutputHandler(data)
200
196
  else:
201
- dyadic_len_1 = ((data.length[0] - 1) << dyadic_order_1) + 1
202
- dyadic_len_2 = ((data.length[1] - 1) << dyadic_order_2) + 1
197
+ dyadic_len_1 = dyadic_grid_length(data.length[0], dyadic_order_1)
198
+ dyadic_len_2 = dyadic_grid_length(data.length[1], dyadic_order_2)
203
199
  result = GridOutputHandler(dyadic_len_1, dyadic_len_2, data)
204
200
 
205
201
  torch_path1 = torch.as_tensor(data.path[0]) # Avoids data copy
@@ -238,6 +234,15 @@ def sig_kernel(
238
234
  stacklevel=2
239
235
  )
240
236
 
237
+ if normalize:
238
+ # Paths are already transformed at this point, so pass time_aug=False, lead_lag=False
239
+ k1 = sig_kernel(path1, path1, dyadic_order, static_kernel, n_jobs=n_jobs)
240
+ k2 = sig_kernel(path2, path2, dyadic_order, static_kernel, n_jobs=n_jobs)
241
+ if isinstance(result.data, np.ndarray):
242
+ result.data = result.data / np.sqrt(np.maximum(k1 * k2, 1e-30))
243
+ else:
244
+ result.data = result.data / torch.sqrt(torch.clamp(k1 * k2, min=1e-30))
245
+
241
246
  return result.data
242
247
 
243
248
 
@@ -251,7 +256,8 @@ def sig_kernel_gram(
251
256
  end_time : float = 1.,
252
257
  n_jobs : int = 1,
253
258
  max_batch : int = -1,
254
- return_grid : bool = False
259
+ return_grid : bool = False,
260
+ normalize : bool = False
255
261
  ) -> Union[np.ndarray, torch.tensor]:
256
262
  """
257
263
  Given batches of paths :math:`\\{x_i\\}_{i=1}^B` and :math:`\\{y_i\\}_{i=1}^B`, computes the gram matrix of signature kernels
@@ -314,6 +320,9 @@ def sig_kernel_gram(
314
320
  :type max_batch: int
315
321
  :param return_grid: If ``True``, returns the entire PDE grid.
316
322
  :type return_grid: bool
323
+ :param normalize: If ``True``, normalizes the gram matrix so that :math:`K(x, x) = 1` by
324
+ dividing each entry by :math:`\\sqrt{K(x_i, x_i) \\cdot K(y_j, y_j)}`. Cannot be used with ``return_grid=True``.
325
+ :type normalize: bool
317
326
  :return: Gram matrix of signature kernels
318
327
  :rtype: numpy.ndarray | torch.tensor
319
328
 
@@ -368,6 +377,8 @@ def sig_kernel_gram(
368
377
  check_type(max_batch, "max_batch", int)
369
378
  if max_batch == 0 or max_batch < -1:
370
379
  raise ValueError("max_batch must be a positive integer or -1")
380
+ if normalize and return_grid:
381
+ raise ValueError("normalize=True cannot be used with return_grid=True")
371
382
 
372
383
  data = MultiplePathInputHandler([path1, path2], time_aug, lead_lag, end_time, ["path1", "path2"], False)
373
384
 
@@ -388,44 +399,41 @@ def sig_kernel_gram(
388
399
  # Now run computation in batches
389
400
  ####################################
390
401
 
402
+ do1, do2 = parse_dyadic_order(dyadic_order)
403
+
404
+ if return_grid:
405
+ gl1 = dyadic_grid_length(data.length[0], do1)
406
+ gl2 = dyadic_grid_length(data.length[1], do2)
407
+
391
408
  if symmetric:
392
409
  # Symmetric case: only compute upper triangle pairs, mirror to lower.
393
- # This guarantees exact symmetry and halves the number of kernel evaluations.
394
410
  # Cannot mirror grids when dyadic orders differ (gl1 != gl2), so fall through.
395
- if return_grid:
396
- if isinstance(dyadic_order, tuple) and len(dyadic_order) == 2:
397
- do1, do2 = dyadic_order
398
- else:
399
- do1 = do2 = dyadic_order
400
- gl1 = ((data.length[0] - 1) << do1) + 1
401
- gl2 = ((data.length[1] - 1) << do2) + 1
402
- if gl1 != gl2:
403
- symmetric = False
411
+ if return_grid and gl1 != gl2:
412
+ symmetric = False
404
413
 
405
414
  if symmetric:
406
415
  idx_i, idx_j = torch.triu_indices(batch1, batch1, device=path1.device)
407
- n_pairs = idx_i.shape[0]
408
- chunk_size = max_batch * max_batch
409
-
410
- if return_grid:
411
- res = torch.empty(batch1, batch1, gl1, gl2, dtype=path1.dtype, device=path1.device)
412
- else:
413
- res = torch.empty(batch1, batch1, dtype=path1.dtype, device=path1.device)
414
-
415
- for start in range(0, n_pairs, chunk_size):
416
- end = min(start + chunk_size, n_pairs)
417
- ci = idx_i[start:end]
418
- cj = idx_j[start:end]
416
+ src1, src2 = path1, path1
417
+ else:
418
+ idx_i = torch.arange(batch1, device=path1.device).repeat_interleave(batch2)
419
+ idx_j = torch.arange(batch2, device=path2.device).repeat(batch1)
420
+ src1, src2 = path1, path2
419
421
 
420
- path1_ = path1[ci]
421
- path2_ = path1[cj]
422
+ if return_grid:
423
+ res = torch.empty(batch1, batch2, gl1, gl2, dtype=path1.dtype, device=path1.device)
424
+ else:
425
+ res = torch.empty(batch1, batch2, dtype=path1.dtype, device=path1.device)
422
426
 
423
- k = sig_kernel(path1_, path2_, dyadic_order, static_kernel, time_aug, lead_lag, end_time, n_jobs, return_grid)
427
+ chunk_size = max_batch * max_batch
428
+ for start in range(0, idx_i.shape[0], chunk_size):
429
+ end = min(start + chunk_size, idx_i.shape[0])
430
+ ci = idx_i[start:end]
431
+ cj = idx_j[start:end]
424
432
 
425
- # Place in upper triangle
426
- res[ci, cj] = k
433
+ k = sig_kernel(src1[ci], src2[cj], dyadic_order, static_kernel, time_aug, lead_lag, end_time, n_jobs, return_grid)
434
+ res[ci, cj] = k
427
435
 
428
- # Mirror off-diagonal to lower triangle
436
+ if symmetric:
429
437
  off = ci != cj
430
438
  if off.any():
431
439
  k_mirror = k[off]
@@ -433,31 +441,11 @@ def sig_kernel_gram(
433
441
  k_mirror = k_mirror.transpose(-2, -1)
434
442
  res[cj[off], ci[off]] = k_mirror
435
443
 
436
- return res
437
-
438
- # Asymmetric case: pair-based indexing
439
- idx_i = torch.arange(batch1, device=path1.device).repeat_interleave(batch2)
440
- idx_j = torch.arange(batch2, device=path2.device).repeat(batch1)
441
- n_pairs = idx_i.shape[0]
442
- chunk_size = max_batch * max_batch
443
-
444
- if return_grid:
445
- if isinstance(dyadic_order, tuple) and len(dyadic_order) == 2:
446
- do1, do2 = dyadic_order
447
- else:
448
- do1 = do2 = dyadic_order
449
- gl1 = ((data.length[0] - 1) << do1) + 1
450
- gl2 = ((data.length[1] - 1) << do2) + 1
451
- res = torch.empty(batch1, batch2, gl1, gl2, dtype=path1.dtype, device=path1.device)
452
- else:
453
- res = torch.empty(batch1, batch2, dtype=path1.dtype, device=path1.device)
454
-
455
- for start in range(0, n_pairs, chunk_size):
456
- end = min(start + chunk_size, n_pairs)
457
- ci = idx_i[start:end]
458
- cj = idx_j[start:end]
459
-
460
- k = sig_kernel(path1[ci], path2[cj], dyadic_order, static_kernel, time_aug, lead_lag, end_time, n_jobs, return_grid)
461
- res[ci, cj] = k
444
+ if normalize:
445
+ d1 = sig_kernel(path1, path1, dyadic_order, static_kernel, time_aug, lead_lag, end_time, n_jobs)
446
+ d2 = sig_kernel(path2, path2, dyadic_order, static_kernel, time_aug, lead_lag, end_time, n_jobs) if not symmetric else d1
447
+ res = res / torch.sqrt(torch.clamp(d1.unsqueeze(1) * d2.unsqueeze(0), min=1e-30))
462
448
 
449
+ if data.type_ == "numpy":
450
+ return res.numpy()
463
451
  return res
@@ -22,7 +22,7 @@ import torch
22
22
  from .transform_path import transform_path
23
23
  from .transform_path_backprop import transform_path_backprop
24
24
  from .sig_kernel import sig_kernel
25
- from .param_checks import check_type
25
+ from .param_checks import check_type, parse_dyadic_order
26
26
  from .error_codes import err_msg
27
27
  from .dtypes import CPSIG_BATCH_SIG_KERNEL_BACKPROP, DTYPES, CUSIG_BATCH_SIG_KERNEL_BACKPROP_CUDA
28
28
  from .data_handlers import MultiplePathInputHandler, ScalarInputHandler, GridOutputHandler, PathInputHandler
@@ -203,17 +203,7 @@ def sig_kernel_backprop(
203
203
  if not (left_deriv or right_deriv):
204
204
  return None, None
205
205
 
206
- if isinstance(dyadic_order, tuple) and len(dyadic_order) == 2:
207
- dyadic_order_1 = dyadic_order[0]
208
- dyadic_order_2 = dyadic_order[1]
209
- elif isinstance(dyadic_order, int):
210
- dyadic_order_1 = dyadic_order
211
- dyadic_order_2 = dyadic_order
212
- else:
213
- raise TypeError("dyadic_order must be an integer or a tuple of length 2")
214
-
215
- if dyadic_order_1 < 0 or dyadic_order_2 < 0:
216
- raise ValueError("dyadic_order must be a non-negative integer or tuple of non-negative integers")
206
+ dyadic_order_1, dyadic_order_2 = parse_dyadic_order(dyadic_order)
217
207
 
218
208
  if time_aug or lead_lag:
219
209
  path1 = transform_path(path1, time_aug, lead_lag, end_time, n_jobs)
@@ -444,10 +434,7 @@ def sig_kernel_gram_backprop(
444
434
  chunk_size = max_batch * max_batch
445
435
 
446
436
  # Check if k_grid can be transposed for symmetric off-diagonal pairs
447
- if isinstance(dyadic_order, tuple) and len(dyadic_order) == 2:
448
- do1, do2 = dyadic_order
449
- else:
450
- do1 = do2 = dyadic_order
437
+ do1, do2 = parse_dyadic_order(dyadic_order)
451
438
  can_transpose_k = (do1 == do2)
452
439
 
453
440
  for start in range(0, n_pairs, chunk_size):
@@ -3,8 +3,6 @@ import numpy as np
3
3
  import torch
4
4
 
5
5
  from .param_checks import check_type_multiple
6
- from .data_handlers import MultiplePathInputHandler
7
-
8
6
  from .static_kernels import StaticKernel
9
7
  from .sig_kernel import sig_kernel_gram
10
8
 
@@ -112,25 +110,25 @@ def sig_score(
112
110
  check_type_multiple(sample, "sample", (np.ndarray, torch.Tensor))
113
111
  check_type_multiple(y, "y", (np.ndarray, torch.Tensor))
114
112
 
115
- # Use torch for simplicity
113
+ is_numpy = isinstance(sample, np.ndarray)
116
114
  sample = torch.as_tensor(sample)
117
115
  y = torch.as_tensor(y)
118
116
  if len(y.shape) == 2:
119
117
  y = y.unsqueeze(0).contiguous().clone()
120
118
 
121
- data = MultiplePathInputHandler([sample, y], time_aug, lead_lag, end_time, ["sample_paths", "y"], False)
122
-
123
119
  B = sample.shape[0]
120
+ if B < 2:
121
+ raise ValueError("sig_score requires at least 2 sample paths (got {}).".format(B))
124
122
 
125
123
  xx = sig_kernel_gram(sample, sample, dyadic_order, static_kernel, time_aug, lead_lag, end_time, n_jobs, max_batch, False)
126
124
  xy = sig_kernel_gram(sample, y, dyadic_order, static_kernel, time_aug, lead_lag, end_time, n_jobs, max_batch, False)
127
125
 
128
- xx_sum = (torch.sum(xx) - torch.sum(torch.diag(xx))) / (B * (B - 1.))
129
- xy_sum = torch.sum(xy, dim = 0) * (2. / B)
126
+ xx_sum = (torch.sum(xx) - torch.trace(xx)) / (B * (B - 1.))
127
+ xy_sum = torch.sum(xy, dim=0) * (2. / B)
130
128
 
131
129
  res = lam * xx_sum - xy_sum
132
130
 
133
- if data.type_ == "numpy":
131
+ if is_numpy:
134
132
  return res.numpy()
135
133
  return res
136
134
 
@@ -341,25 +339,27 @@ def sig_mmd(
341
339
  print(mmd)
342
340
 
343
341
  """
344
- data = MultiplePathInputHandler([sample1, sample2], time_aug, lead_lag, end_time, ["sample1", "sample2"], False)
345
-
346
- # Use torch for simplicity
347
- sample1 = torch.as_tensor(data.path[0])
348
- sample2 = torch.as_tensor(data.path[1])
342
+ is_numpy = isinstance(sample1, np.ndarray)
343
+ sample1 = torch.as_tensor(sample1)
344
+ sample2 = torch.as_tensor(sample2)
349
345
 
350
346
  m = sample1.shape[0]
351
347
  n = sample2.shape[0]
348
+ if m < 2:
349
+ raise ValueError("sig_mmd requires at least 2 paths in sample1 (got {}).".format(m))
350
+ if n < 2:
351
+ raise ValueError("sig_mmd requires at least 2 paths in sample2 (got {}).".format(n))
352
352
 
353
353
  xx = sig_kernel_gram(sample1, sample1, dyadic_order, static_kernel, time_aug, lead_lag, end_time, n_jobs, max_batch, False)
354
354
  xy = sig_kernel_gram(sample1, sample2, dyadic_order, static_kernel, time_aug, lead_lag, end_time, n_jobs, max_batch, False)
355
355
  yy = sig_kernel_gram(sample2, sample2, dyadic_order, static_kernel, time_aug, lead_lag, end_time, n_jobs, max_batch, False)
356
356
 
357
- xx_sum = (torch.sum(xx) - torch.sum(torch.diag(xx))) / (m * (m - 1))
357
+ xx_sum = (torch.sum(xx) - torch.trace(xx)) / (m * (m - 1))
358
358
  xy_sum = 2. * torch.mean(xy)
359
- yy_sum = (torch.sum(yy) - torch.sum(torch.diag(yy))) / (n * (n - 1))
359
+ yy_sum = (torch.sum(yy) - torch.trace(yy)) / (n * (n - 1))
360
360
 
361
361
  res = xx_sum - xy_sum + yy_sum
362
362
 
363
- if data.type_ == "numpy":
363
+ if is_numpy:
364
364
  return res.numpy()
365
365
  return res