jrpycpp 0.1.0__py3-none-any.whl

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 (96) hide show
  1. jrpycpp/__init__.py +3 -0
  2. jrpycpp/__version__.py +1 -0
  3. jrpycpp/examples/cpp_scripts/chapter2/demo/01-class.cpp +24 -0
  4. jrpycpp/examples/cpp_scripts/chapter2/demo/02-const.cpp +25 -0
  5. jrpycpp/examples/cpp_scripts/chapter2/exercise/stats.cpp +26 -0
  6. jrpycpp/examples/cpp_scripts/chapter2/solution/stats.cpp +44 -0
  7. jrpycpp/examples/cpp_scripts/chapter3/demo/01-free-function/bindings.cpp +11 -0
  8. jrpycpp/examples/cpp_scripts/chapter3/demo/01-free-function/pi.cpp +10 -0
  9. jrpycpp/examples/cpp_scripts/chapter3/demo/01-free-function/pi.h +3 -0
  10. jrpycpp/examples/cpp_scripts/chapter3/demo/01-free-function/pyproject.toml +11 -0
  11. jrpycpp/examples/cpp_scripts/chapter3/demo/01-free-function/setup.py +12 -0
  12. jrpycpp/examples/cpp_scripts/chapter3/demo/01-free-function/test_pi.py +11 -0
  13. jrpycpp/examples/cpp_scripts/chapter3/demo/02-class/bindings.cpp +17 -0
  14. jrpycpp/examples/cpp_scripts/chapter3/demo/02-class/pyproject.toml +11 -0
  15. jrpycpp/examples/cpp_scripts/chapter3/demo/02-class/setup.py +12 -0
  16. jrpycpp/examples/cpp_scripts/chapter3/demo/02-class/stats.cpp +26 -0
  17. jrpycpp/examples/cpp_scripts/chapter3/demo/02-class/stats.h +15 -0
  18. jrpycpp/examples/cpp_scripts/chapter3/demo/02-class/test_livstats.py +18 -0
  19. jrpycpp/examples/cpp_scripts/chapter3/exercise/bindings.cpp +12 -0
  20. jrpycpp/examples/cpp_scripts/chapter3/exercise/pyproject.toml +11 -0
  21. jrpycpp/examples/cpp_scripts/chapter3/exercise/setup.py +12 -0
  22. jrpycpp/examples/cpp_scripts/chapter3/exercise/stats.cpp +26 -0
  23. jrpycpp/examples/cpp_scripts/chapter3/exercise/stats.h +15 -0
  24. jrpycpp/examples/cpp_scripts/chapter3/solution/bindings.cpp +22 -0
  25. jrpycpp/examples/cpp_scripts/chapter3/solution/pyproject.toml +11 -0
  26. jrpycpp/examples/cpp_scripts/chapter3/solution/setup.py +12 -0
  27. jrpycpp/examples/cpp_scripts/chapter3/solution/stats.cpp +26 -0
  28. jrpycpp/examples/cpp_scripts/chapter3/solution/stats.h +15 -0
  29. jrpycpp/examples/cpp_scripts/chapter3/solution/test_livstats.py +18 -0
  30. jrpycpp/examples/cpp_scripts/chapter4/demo/arrays.cpp +13 -0
  31. jrpycpp/examples/cpp_scripts/chapter4/demo/arrays.h +7 -0
  32. jrpycpp/examples/cpp_scripts/chapter4/demo/bindings.cpp +73 -0
  33. jrpycpp/examples/cpp_scripts/chapter4/demo/pyproject.toml +11 -0
  34. jrpycpp/examples/cpp_scripts/chapter4/demo/setup.py +12 -0
  35. jrpycpp/examples/cpp_scripts/chapter4/demo/test_arrays.py +38 -0
  36. jrpycpp/examples/cpp_scripts/chapter4/exercise/arrays.cpp +13 -0
  37. jrpycpp/examples/cpp_scripts/chapter4/exercise/arrays.h +7 -0
  38. jrpycpp/examples/cpp_scripts/chapter4/exercise/bindings.cpp +36 -0
  39. jrpycpp/examples/cpp_scripts/chapter4/exercise/pyproject.toml +11 -0
  40. jrpycpp/examples/cpp_scripts/chapter4/exercise/setup.py +12 -0
  41. jrpycpp/examples/cpp_scripts/chapter4/solution/arrays.cpp +13 -0
  42. jrpycpp/examples/cpp_scripts/chapter4/solution/arrays.h +7 -0
  43. jrpycpp/examples/cpp_scripts/chapter4/solution/bindings.cpp +49 -0
  44. jrpycpp/examples/cpp_scripts/chapter4/solution/pyproject.toml +11 -0
  45. jrpycpp/examples/cpp_scripts/chapter4/solution/setup.py +12 -0
  46. jrpycpp/examples/cpp_scripts/chapter4/solution/test_arrays.py +19 -0
  47. jrpycpp/examples/cpp_scripts/chapter5/demo/01-cmake/CMakeLists.txt +22 -0
  48. jrpycpp/examples/cpp_scripts/chapter5/demo/01-cmake/include/stats.h +15 -0
  49. jrpycpp/examples/cpp_scripts/chapter5/demo/01-cmake/src/main.cpp +9 -0
  50. jrpycpp/examples/cpp_scripts/chapter5/demo/01-cmake/src/stats.cpp +26 -0
  51. jrpycpp/examples/cpp_scripts/chapter5/demo/01-cmake/tests/test_stats.cpp +20 -0
  52. jrpycpp/examples/cpp_scripts/chapter5/demo/02-python/CMakeLists.txt +28 -0
  53. jrpycpp/examples/cpp_scripts/chapter5/demo/02-python/include/stats.h +15 -0
  54. jrpycpp/examples/cpp_scripts/chapter5/demo/02-python/pyproject.toml +11 -0
  55. jrpycpp/examples/cpp_scripts/chapter5/demo/02-python/python/bindings.cpp +17 -0
  56. jrpycpp/examples/cpp_scripts/chapter5/demo/02-python/src/stats.cpp +26 -0
  57. jrpycpp/examples/cpp_scripts/chapter5/demo/02-python/tests/test_livstats.py +10 -0
  58. jrpycpp/examples/cpp_scripts/chapter5/demo/02-python/tests/test_stats.cpp +20 -0
  59. jrpycpp/examples/cpp_scripts/chapter5/exercise/CMakeLists.txt +17 -0
  60. jrpycpp/examples/cpp_scripts/chapter5/exercise/include/stats.h +15 -0
  61. jrpycpp/examples/cpp_scripts/chapter5/exercise/src/main.cpp +9 -0
  62. jrpycpp/examples/cpp_scripts/chapter5/exercise/src/stats.cpp +26 -0
  63. jrpycpp/examples/cpp_scripts/chapter5/exercise/tests/test_stats.cpp +16 -0
  64. jrpycpp/examples/cpp_scripts/chapter5/solution/CMakeLists.txt +22 -0
  65. jrpycpp/examples/cpp_scripts/chapter5/solution/include/stats.h +15 -0
  66. jrpycpp/examples/cpp_scripts/chapter5/solution/src/main.cpp +9 -0
  67. jrpycpp/examples/cpp_scripts/chapter5/solution/src/stats.cpp +26 -0
  68. jrpycpp/examples/cpp_scripts/chapter5/solution/tests/test_stats.cpp +20 -0
  69. jrpycpp/examples/cpp_scripts/chapter6/demo/01-map.cpp +19 -0
  70. jrpycpp/examples/cpp_scripts/chapter6/demo/02-lambda.cpp +22 -0
  71. jrpycpp/examples/cpp_scripts/chapter6/demo/03-move.cpp +14 -0
  72. jrpycpp/examples/cpp_scripts/chapter6/demo/04-unique.cpp +19 -0
  73. jrpycpp/examples/cpp_scripts/chapter6/exercise/copies.cpp +34 -0
  74. jrpycpp/examples/cpp_scripts/chapter6/exercise/integrate.cpp +20 -0
  75. jrpycpp/examples/cpp_scripts/chapter6/exercise/words.cpp +21 -0
  76. jrpycpp/examples/cpp_scripts/chapter6/solution/copies.cpp +37 -0
  77. jrpycpp/examples/cpp_scripts/chapter6/solution/integrate.cpp +25 -0
  78. jrpycpp/examples/cpp_scripts/chapter6/solution/words.cpp +21 -0
  79. jrpycpp/examples/templates/cpp-project/CMakeLists.txt +22 -0
  80. jrpycpp/examples/templates/cpp-project/README.md +68 -0
  81. jrpycpp/examples/templates/cpp-project/include/stats.h +15 -0
  82. jrpycpp/examples/templates/cpp-project/src/main.cpp +9 -0
  83. jrpycpp/examples/templates/cpp-project/src/stats.cpp +26 -0
  84. jrpycpp/examples/templates/cpp-project/tests/test_stats.cpp +20 -0
  85. jrpycpp/examples/templates/python-cpp-project/CMakeLists.txt +28 -0
  86. jrpycpp/examples/templates/python-cpp-project/README.md +72 -0
  87. jrpycpp/examples/templates/python-cpp-project/include/stats.h +15 -0
  88. jrpycpp/examples/templates/python-cpp-project/pyproject.toml +11 -0
  89. jrpycpp/examples/templates/python-cpp-project/python/bindings.cpp +17 -0
  90. jrpycpp/examples/templates/python-cpp-project/src/stats.cpp +26 -0
  91. jrpycpp/examples/templates/python-cpp-project/tests/test_livstats.py +10 -0
  92. jrpycpp/examples/templates/python-cpp-project/tests/test_stats.cpp +20 -0
  93. jrpycpp/examples.py +43 -0
  94. jrpycpp-0.1.0.dist-info/METADATA +16 -0
  95. jrpycpp-0.1.0.dist-info/RECORD +96 -0
  96. jrpycpp-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,22 @@
1
+ #include <algorithm>
2
+ #include <iostream>
3
+ #include <vector>
4
+
5
+ int main() {
6
+ std::vector<double> values = {3.0, 1.0, 4.0, 1.0, 5.0, 9.0};
7
+
8
+ std::sort(values.begin(), values.end());
9
+ for (double v : values) {
10
+ std::cout << v << " ";
11
+ }
12
+ std::cout << "\n";
13
+
14
+ std::sort(values.begin(), values.end(), [](double a, double b) {
15
+ return a > b;
16
+ });
17
+ for (double v : values) {
18
+ std::cout << v << " ";
19
+ }
20
+ std::cout << "\n";
21
+ return 0;
22
+ }
@@ -0,0 +1,14 @@
1
+ #include <cstddef>
2
+ #include <iostream>
3
+ #include <utility>
4
+ #include <vector>
5
+
6
+ int main() {
7
+ std::vector<double> a(4, 1.0);
8
+ std::cout << "a has " << a.size() << " elements\n";
9
+
10
+ std::vector<double> b = std::move(a);
11
+ std::cout << "b has " << b.size() << " elements\n";
12
+ std::cout << "a now has " << a.size() << "\n";
13
+ return 0;
14
+ }
@@ -0,0 +1,19 @@
1
+ #include <iostream>
2
+ #include <memory>
3
+
4
+ class Noisy {
5
+ public:
6
+ Noisy() { std::cout << "made\n"; }
7
+ ~Noisy() { std::cout << "destroyed\n"; }
8
+ void speak() const { std::cout << "here\n"; }
9
+ };
10
+
11
+ int main() {
12
+ {
13
+ std::unique_ptr<Noisy> p = std::make_unique<Noisy>();
14
+ p->speak();
15
+ std::cout << "leaving the block\n";
16
+ }
17
+ std::cout << "after the block\n";
18
+ return 0;
19
+ }
@@ -0,0 +1,34 @@
1
+ #include <chrono>
2
+ #include <cstddef>
3
+ #include <iostream>
4
+ #include <vector>
5
+
6
+ std::vector<double> stage_one(std::vector<double> v) {
7
+ v[0] += 1.0;
8
+ return v;
9
+ }
10
+
11
+ std::vector<double> stage_two(std::vector<double> v) {
12
+ v[1] += 1.0;
13
+ return v;
14
+ }
15
+
16
+ std::vector<double> stage_three(std::vector<double> v) {
17
+ v[2] += 1.0;
18
+ return v;
19
+ }
20
+
21
+ int main() {
22
+ const std::size_t n = 10000000;
23
+ std::vector<double> values(n, 1.0);
24
+
25
+ auto start = std::chrono::steady_clock::now();
26
+
27
+ std::vector<double> result = stage_three(stage_two(stage_one(values)));
28
+
29
+ auto end = std::chrono::steady_clock::now();
30
+ double elapsed = std::chrono::duration<double>(end - start).count();
31
+
32
+ std::cout << "result[0] " << result[0] << ", took " << elapsed << " s\n";
33
+ return 0;
34
+ }
@@ -0,0 +1,20 @@
1
+ #include <cmath>
2
+ #include <iostream>
3
+
4
+ double trapezium(double (*f)(double), double a, double b, int n) {
5
+ double h = (b - a) / n;
6
+ double total = 0.5 * (f(a) + f(b));
7
+ for (int i = 1; i < n; ++i) {
8
+ total += f(a + i * h);
9
+ }
10
+ return total * h;
11
+ }
12
+
13
+ double square(double x) {
14
+ return x * x;
15
+ }
16
+
17
+ int main() {
18
+ std::cout << "x squared from 0 to 1: " << trapezium(square, 0.0, 1.0, 1000) << "\n";
19
+ return 0;
20
+ }
@@ -0,0 +1,21 @@
1
+ #include <iostream>
2
+ #include <map>
3
+ #include <sstream>
4
+ #include <string>
5
+
6
+ int main() {
7
+ std::string text = "the cat sat on the mat the cat slept";
8
+ std::istringstream stream(text);
9
+
10
+ std::map<std::string, int> counts;
11
+ std::string word;
12
+
13
+ while (stream >> word) {
14
+ // Task 1: count this word
15
+ }
16
+
17
+ for (const auto& entry : counts) {
18
+ std::cout << entry.first << " " << entry.second << "\n";
19
+ }
20
+ return 0;
21
+ }
@@ -0,0 +1,37 @@
1
+ #include <chrono>
2
+ #include <cstddef>
3
+ #include <iostream>
4
+ #include <utility>
5
+ #include <vector>
6
+
7
+ std::vector<double> stage_one(std::vector<double> v) {
8
+ v[0] += 1.0;
9
+ return v;
10
+ }
11
+
12
+ std::vector<double> stage_two(std::vector<double> v) {
13
+ v[1] += 1.0;
14
+ return v;
15
+ }
16
+
17
+ std::vector<double> stage_three(std::vector<double> v) {
18
+ v[2] += 1.0;
19
+ return v;
20
+ }
21
+
22
+ int main() {
23
+ const std::size_t n = 10000000;
24
+ std::vector<double> values(n, 1.0);
25
+
26
+ auto start = std::chrono::steady_clock::now();
27
+
28
+ std::vector<double> result =
29
+ stage_three(stage_two(stage_one(std::move(values))));
30
+
31
+ auto end = std::chrono::steady_clock::now();
32
+ double elapsed = std::chrono::duration<double>(end - start).count();
33
+
34
+ std::cout << "result[0] " << result[0] << ", took " << elapsed << " s\n";
35
+ std::cout << "values now has " << values.size() << " elements\n";
36
+ return 0;
37
+ }
@@ -0,0 +1,25 @@
1
+ #include <cmath>
2
+ #include <functional>
3
+ #include <iostream>
4
+
5
+ double trapezium(const std::function<double(double)>& f, double a, double b, int n) {
6
+ double h = (b - a) / n;
7
+ double total = 0.5 * (f(a) + f(b));
8
+ for (int i = 1; i < n; ++i) {
9
+ total += f(a + i * h);
10
+ }
11
+ return total * h;
12
+ }
13
+
14
+ double square(double x) {
15
+ return x * x;
16
+ }
17
+
18
+ int main() {
19
+ std::cout << "x squared from 0 to 1: " << trapezium(square, 0.0, 1.0, 1000) << "\n";
20
+ std::cout << "x cubed from 0 to 1: "
21
+ << trapezium([](double x) { return x * x * x; }, 0.0, 1.0, 1000) << "\n";
22
+ std::cout << "sine from 0 to pi: "
23
+ << trapezium([](double x) { return std::sin(x); }, 0.0, M_PI, 1000) << "\n";
24
+ return 0;
25
+ }
@@ -0,0 +1,21 @@
1
+ #include <iostream>
2
+ #include <map>
3
+ #include <sstream>
4
+ #include <string>
5
+
6
+ int main() {
7
+ std::string text = "the cat sat on the mat the cat slept";
8
+ std::istringstream stream(text);
9
+
10
+ std::map<std::string, int> counts;
11
+ std::string word;
12
+
13
+ while (stream >> word) {
14
+ counts[word] += 1;
15
+ }
16
+
17
+ for (const auto& entry : counts) {
18
+ std::cout << entry.first << " " << entry.second << "\n";
19
+ }
20
+ return 0;
21
+ }
@@ -0,0 +1,22 @@
1
+ cmake_minimum_required(VERSION 3.16)
2
+ project(stats LANGUAGES CXX VERSION 0.1.0)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_CXX_EXTENSIONS OFF)
7
+
8
+ add_library(stats src/stats.cpp)
9
+ target_include_directories(stats PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
10
+ target_compile_options(stats PRIVATE -Wall -Wextra)
11
+
12
+ add_executable(report src/main.cpp)
13
+ target_link_libraries(report PRIVATE stats)
14
+
15
+ enable_testing()
16
+
17
+ find_package(Catch2 3 REQUIRED)
18
+ add_executable(test_stats tests/test_stats.cpp)
19
+ target_link_libraries(test_stats PRIVATE stats Catch2::Catch2WithMain)
20
+
21
+ include(Catch)
22
+ catch_discover_tests(test_stats)
@@ -0,0 +1,68 @@
1
+ # C++ project template
2
+
3
+ A small C++ library with a program that uses it and tests that check it.
4
+ Copy this directory, rename it, and start.
5
+
6
+ ## What is where
7
+
8
+ ```
9
+ include/stats.h the public header: what other code may use
10
+ src/stats.cpp the implementation
11
+ src/main.cpp a program that links the library
12
+ tests/test_stats.cpp the tests
13
+ CMakeLists.txt how to build all of it
14
+ ```
15
+
16
+ Nothing is built inside those directories. Everything CMake produces goes into
17
+ a build directory you can delete.
18
+
19
+ ## Build, run, test
20
+
21
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
22
+ cmake --build build
23
+ ./build/report
24
+ ctest --test-dir build --output-on-failure
25
+
26
+ The build type is not optional. With none, CMake passes no optimisation flags
27
+ at all, and you will be measuring something you would never ship. Use
28
+ `RelWithDebInfo` while developing and `Release` when you mean it. `Debug` when
29
+ you are in a debugger.
30
+
31
+ ## Renaming it for your project
32
+
33
+ Three places, and nothing else:
34
+
35
+ 1. `project(stats ...)` in `CMakeLists.txt`
36
+ 2. the library and target names, `stats`, `report`, `test_stats`
37
+ 3. the file names in `include/`, `src/` and `tests/`
38
+
39
+ ## Adding a library somebody else wrote
40
+
41
+ Two lines. Eigen, for a matrix library, is the one most people want first:
42
+
43
+ find_package(Eigen3 3.4 REQUIRED)
44
+ target_link_libraries(stats PUBLIC Eigen3::Eigen)
45
+
46
+ `find_package` looks for it where it is installed; nothing is copied into this
47
+ repository. `PUBLIC` means anything linking your library gets Eigen's headers
48
+ too, which is what you want if your public header mentions an Eigen type, and
49
+ `PRIVATE` is right when it does not.
50
+
51
+ Install it first with your package manager: `apt install libeigen3-dev`, or
52
+ `brew install eigen`. Catch2, used by the tests here, is found the same way.
53
+
54
+ ## Adding a test
55
+
56
+ Write another `TEST_CASE` in `tests/test_stats.cpp`, or another file added to
57
+ `add_executable`. You do not need to register it anywhere:
58
+ `catch_discover_tests` runs the binary at build time and asks it what tests it
59
+ has.
60
+
61
+ ## Two Catch2 behaviours that surprise people
62
+
63
+ Test order is randomised by default. A suite that passes in one order and fails
64
+ in another has found a real dependency between its tests, which is worth
65
+ knowing rather than hiding; `--order decl` restores declaration order.
66
+
67
+ On failure Catch2 exits 42 and `ctest` exits 8. Both matter if you wire this
68
+ into CI and neither before then.
@@ -0,0 +1,15 @@
1
+ #pragma once
2
+
3
+ #include <vector>
4
+
5
+ class Stats {
6
+ public:
7
+ Stats(const std::vector<double>& values);
8
+
9
+ double mean() const;
10
+ double stddev() const;
11
+ int count() const;
12
+
13
+ private:
14
+ std::vector<double> values_;
15
+ };
@@ -0,0 +1,9 @@
1
+ #include <iostream>
2
+
3
+ #include "stats.h"
4
+
5
+ int main() {
6
+ Stats s({2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0});
7
+ std::cout << "mean " << s.mean() << ", standard deviation " << s.stddev() << "\n";
8
+ return 0;
9
+ }
@@ -0,0 +1,26 @@
1
+ #include "stats.h"
2
+
3
+ #include <cmath>
4
+
5
+ Stats::Stats(const std::vector<double>& values) : values_(values) {}
6
+
7
+ double Stats::mean() const {
8
+ double total = 0.0;
9
+ for (double v : values_) {
10
+ total += v;
11
+ }
12
+ return total / values_.size();
13
+ }
14
+
15
+ double Stats::stddev() const {
16
+ double m = mean();
17
+ double total = 0.0;
18
+ for (double v : values_) {
19
+ total += (v - m) * (v - m);
20
+ }
21
+ return std::sqrt(total / values_.size());
22
+ }
23
+
24
+ int Stats::count() const {
25
+ return static_cast<int>(values_.size());
26
+ }
@@ -0,0 +1,20 @@
1
+ #include <catch2/catch_test_macros.hpp>
2
+ #include <catch2/catch_approx.hpp>
3
+
4
+ #include "stats.h"
5
+
6
+ using Catch::Approx;
7
+
8
+ TEST_CASE("mean of yesterday's numbers", "[stats]") {
9
+ Stats s({2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0});
10
+ REQUIRE(s.mean() == Approx(5.0));
11
+ }
12
+
13
+ TEST_CASE("standard deviation of yesterday's numbers", "[stats]") {
14
+ Stats s({2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0});
15
+ REQUIRE(s.stddev() == Approx(2.0));
16
+ }
17
+
18
+ TEST_CASE("count is the number of values", "[stats]") {
19
+ REQUIRE(Stats({1.0, 2.0, 3.0}).count() == 3);
20
+ }
@@ -0,0 +1,28 @@
1
+ cmake_minimum_required(VERSION 3.16)
2
+ project(livstats LANGUAGES CXX VERSION 0.1.0)
3
+
4
+ set(CMAKE_CXX_STANDARD 17)
5
+ set(CMAKE_CXX_STANDARD_REQUIRED ON)
6
+ set(CMAKE_CXX_EXTENSIONS OFF)
7
+
8
+ add_library(stats src/stats.cpp)
9
+ target_include_directories(stats PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
10
+ target_compile_options(stats PRIVATE -Wall -Wextra)
11
+ set_target_properties(stats PROPERTIES POSITION_INDEPENDENT_CODE ON)
12
+
13
+ set(PYBIND11_FINDPYTHON ON)
14
+ find_package(pybind11 CONFIG REQUIRED)
15
+
16
+ pybind11_add_module(livstats python/bindings.cpp)
17
+ target_link_libraries(livstats PRIVATE stats)
18
+
19
+ install(TARGETS livstats DESTINATION .)
20
+
21
+ if(NOT SKBUILD)
22
+ enable_testing()
23
+ find_package(Catch2 3 REQUIRED)
24
+ add_executable(test_stats tests/test_stats.cpp)
25
+ target_link_libraries(test_stats PRIVATE stats Catch2::Catch2WithMain)
26
+ include(Catch)
27
+ catch_discover_tests(test_stats)
28
+ endif()
@@ -0,0 +1,72 @@
1
+ # Python and C++ project template
2
+
3
+ One C++ library with two front doors: a C++ test suite, and a Python module
4
+ built from the same sources. Copy the directory and rename it, as with the
5
+ C++ template.
6
+
7
+ ## What is where
8
+
9
+ ```
10
+ include/stats.h the public header
11
+ src/stats.cpp the implementation
12
+ python/bindings.cpp the pybind11 layer: what Python sees
13
+ tests/test_stats.cpp C++ tests, for the library
14
+ tests/test_livstats.py Python tests, for the module
15
+ CMakeLists.txt how to build all of it
16
+ pyproject.toml how pip builds the module
17
+ ```
18
+
19
+ ## Build and test the Python module
20
+
21
+ pip install --no-build-isolation .
22
+ python -c "import livstats; print(livstats.Stats([1.0, 2.0, 3.0]).mean())"
23
+ pytest tests/
24
+
25
+ `pip install` runs CMake underneath, through scikit-build-core. There is no
26
+ second build system.
27
+
28
+ `--no-build-isolation` is worth understanding rather than copying. Without it,
29
+ pip builds in a fresh environment and fetches its build dependencies from the
30
+ network every time, even when your environment already has them. With it, pip
31
+ uses what you have installed. On a machine with no network the flag is the
32
+ difference between a build and an error.
33
+
34
+ For a development loop, an editable install rebuilds only what changed:
35
+
36
+ pip install --no-build-isolation -e .
37
+
38
+ ## Build and test the C++ side
39
+
40
+ cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo
41
+ cmake --build build
42
+ ctest --test-dir build --output-on-failure
43
+
44
+ The C++ tests are wired only when CMake is invoked directly, not when pip
45
+ drives it. That is the `if(NOT SKBUILD)` block: a wheel does not need a test
46
+ binary in it.
47
+
48
+ ## What to test where
49
+
50
+ The C++ tests own the mathematics. The Python tests own the boundary: that
51
+ arguments convert, that return types arrive as real Python objects, that
52
+ exceptions become the Python exceptions you meant. Re-testing the numerics
53
+ through Python is slower, less precise, and debugs in the wrong language.
54
+
55
+ ## One line you must not delete
56
+
57
+ set_target_properties(stats PROPERTIES POSITION_INDEPENDENT_CODE ON)
58
+
59
+ A static library linked into a shared Python module needs it. Without it the
60
+ link fails and tells you to recompile with `-fPIC`.
61
+
62
+ ## Renaming it
63
+
64
+ `project()` in `CMakeLists.txt`, the `name` in `pyproject.toml`, the target
65
+ names, and the module name in `PYBIND11_MODULE(...)`. The module name in the
66
+ C++ and the `install(TARGETS ...)` name must match, or `import` will not find
67
+ it.
68
+
69
+ ## Adding a library somebody else wrote
70
+
71
+ Same as the C++ template: `find_package`, then `target_link_libraries`. If the
72
+ library appears in your public header, link it `PUBLIC`, otherwise `PRIVATE`.
@@ -0,0 +1,15 @@
1
+ #pragma once
2
+
3
+ #include <vector>
4
+
5
+ class Stats {
6
+ public:
7
+ Stats(const std::vector<double>& values);
8
+
9
+ double mean() const;
10
+ double stddev() const;
11
+ int count() const;
12
+
13
+ private:
14
+ std::vector<double> values_;
15
+ };
@@ -0,0 +1,11 @@
1
+ [build-system]
2
+ requires = ["scikit-build-core>=0.11", "pybind11>=3.0"]
3
+ build-backend = "scikit_build_core.build"
4
+
5
+ [project]
6
+ name = "livstats"
7
+ version = "0.1.0"
8
+ requires-python = ">=3.9"
9
+
10
+ [tool.scikit-build]
11
+ minimum-version = "build-system.requires"
@@ -0,0 +1,17 @@
1
+ #include <pybind11/pybind11.h>
2
+ #include <pybind11/stl.h>
3
+
4
+ #include "stats.h"
5
+
6
+ namespace py = pybind11;
7
+
8
+ PYBIND11_MODULE(livstats, m) {
9
+ m.doc() = "The stats library, built by CMake";
10
+
11
+ py::class_<Stats>(m, "Stats")
12
+ .def(py::init<const std::vector<double>&>(), py::arg("values"),
13
+ "Build from a sequence of numbers")
14
+ .def("mean", &Stats::mean, "Arithmetic mean")
15
+ .def("stddev", &Stats::stddev, "Standard deviation, dividing by n")
16
+ .def("count", &Stats::count, "How many values");
17
+ }
@@ -0,0 +1,26 @@
1
+ #include "stats.h"
2
+
3
+ #include <cmath>
4
+
5
+ Stats::Stats(const std::vector<double>& values) : values_(values) {}
6
+
7
+ double Stats::mean() const {
8
+ double total = 0.0;
9
+ for (double v : values_) {
10
+ total += v;
11
+ }
12
+ return total / values_.size();
13
+ }
14
+
15
+ double Stats::stddev() const {
16
+ double m = mean();
17
+ double total = 0.0;
18
+ for (double v : values_) {
19
+ total += (v - m) * (v - m);
20
+ }
21
+ return std::sqrt(total / values_.size());
22
+ }
23
+
24
+ int Stats::count() const {
25
+ return static_cast<int>(values_.size());
26
+ }
@@ -0,0 +1,10 @@
1
+ import pytest
2
+
3
+ import livstats
4
+
5
+
6
+ def test_the_class_survived_the_move_to_cmake():
7
+ s = livstats.Stats([2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0])
8
+ assert s.mean() == pytest.approx(5.0)
9
+ assert s.stddev() == pytest.approx(2.0)
10
+ assert s.count() == 8
@@ -0,0 +1,20 @@
1
+ #include <catch2/catch_test_macros.hpp>
2
+ #include <catch2/catch_approx.hpp>
3
+
4
+ #include "stats.h"
5
+
6
+ using Catch::Approx;
7
+
8
+ TEST_CASE("mean of yesterday's numbers", "[stats]") {
9
+ Stats s({2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0});
10
+ REQUIRE(s.mean() == Approx(5.0));
11
+ }
12
+
13
+ TEST_CASE("standard deviation of yesterday's numbers", "[stats]") {
14
+ Stats s({2.0, 4.0, 4.0, 4.0, 5.0, 5.0, 7.0, 9.0});
15
+ REQUIRE(s.stddev() == Approx(2.0));
16
+ }
17
+
18
+ TEST_CASE("count is the number of values", "[stats]") {
19
+ REQUIRE(Stats({1.0, 2.0, 3.0}).count() == 3);
20
+ }
jrpycpp/examples.py ADDED
@@ -0,0 +1,43 @@
1
+ from pathlib import Path
2
+ from shutil import copytree, ignore_patterns
3
+
4
+
5
+ def _populate(name, target_path):
6
+ """Copy one bundled examples folder into the user's working directory."""
7
+ source_dir = Path(__file__).parent / "examples" / name
8
+
9
+ destination = Path.cwd() / target_path / name
10
+
11
+ if destination.exists():
12
+ raise FileExistsError(
13
+ f"A folder named '{name}' already exists in the target "
14
+ "directory. Rename this or populate a different directory instead."
15
+ )
16
+
17
+ # Installing the package byte-compiles the example .py files, so skip the
18
+ # __pycache__ folders that leaves behind
19
+ copytree(
20
+ src=source_dir,
21
+ dst=destination,
22
+ ignore=ignore_patterns("__pycache__"),
23
+ )
24
+
25
+ print(f"Copied to: {destination.resolve()}")
26
+
27
+
28
+ def populate_cpp_scripts(target_path=".."):
29
+ """Populate the C++ example scripts into the user's working directory.
30
+
31
+ The scripts are copied to the parent directory by default, so that they
32
+ sit alongside the notebooks rather than inside them.
33
+ """
34
+ _populate("cpp_scripts", target_path)
35
+
36
+
37
+ def populate_templates(target_path=".."):
38
+ """Populate the two project templates into the user's working directory.
39
+
40
+ These are the templates attendees take away: a C++ library with tests,
41
+ and the same library behind a Python module.
42
+ """
43
+ _populate("templates", target_path)
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.1
2
+ Name: jrpycpp
3
+ Version: 0.1.0
4
+ Summary: Jumping Rivers: C++ and Python
5
+ Author: Jumping Rivers
6
+ Author-email: info@jumpingrivers.com
7
+ Requires-Python: >=3.10
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Dist: numpy (>=2.0,<3.0)
13
+ Requires-Dist: pybind11 (>=3.0,<4.0)
14
+ Requires-Dist: pytest (>=8.0,<9.0)
15
+ Requires-Dist: scikit-build-core (>=0.11,<0.12)
16
+ Requires-Dist: setuptools (>=64)