diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..6804d11 --- /dev/null +++ b/.clang-format @@ -0,0 +1,107 @@ +--- +# BasedOnStyle: LLVM # but heavily modified +# Modified to resemble Black format for Python +# The if statements are sub-optimal, it needs dangling parenthesis (D33029). +# https://reviews.llvm.org/D33029 +Language: Cpp +AccessModifierOffset: -4 +AlignAfterOpenBracket: AlwaysBreak +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: false +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: Never +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: Empty +AllowShortLambdasOnASingleLine: All +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: Yes +BinPackArguments: false +BinPackParameters: false +BraceWrapping: + AfterCaseLabel: false + AfterClass: true + AfterControlStatement: false + AfterEnum: true + AfterFunction: true + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: true + AfterUnion: true + AfterExternBlock: false + BeforeCatch: true + BeforeElse: true + IndentBraces: false + SplitEmptyFunction: false + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: NonAssignment +BreakBeforeBraces: Custom +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: BeforeColon +BreakStringLiterals: false +ColumnLimit: 88 +CompactNamespaces: false +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DeriveLineEnding: true +DerivePointerAlignment: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +IndentCaseLabels: false +IndentGotoLabels: true +IndentPPDirectives: None +IndentWidth: 4 +IndentWrappedFunctionNames: false +KeepEmptyLinesAtTheStartOfBlocks: true +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Left +SortIncludes: false +SortUsingDeclarations: false +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyBlock: false +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInConditionalStatement: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +SpaceBeforeSquareBrackets: false +Standard: c++11 +TabWidth: 4 +UseCRLF: false +UseTab: Never +... diff --git a/.github/workflows/clang-format.yml b/.github/workflows/clang-format.yml new file mode 100644 index 0000000..58a4ad1 --- /dev/null +++ b/.github/workflows/clang-format.yml @@ -0,0 +1,18 @@ +name: Lint clang-format + +on: +- push +- pull_request + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Get clang-format-lint-action as image + run: | + docker build -t doozyx/clang-format-lint-action "github.com/DoozyX/clang-format-lint-action" + - name: Run clang-format lint + run: | + docker run --rm --workdir /src -v $(pwd):/src doozyx/clang-format-lint-action --clang-format-executable /clang-format/clang-format10 -r --exclude .git include/*.hpp src/*.cpp diff --git a/.gitmodules b/.gitmodules index 0467740..a31cbe4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,6 +1,6 @@ [submodule "pybind11"] path = pybind11 url = https://github.com/pybind/pybind11.git -[submodule "PoPS"] - path = PoPS - url = https://github.com/ncsu-landscape-dynamics/PoPS.git +[submodule "pops-core"] + path = pops-core + url = https://github.com/ncsu-landscape-dynamics/pops-core.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 8851b34..7db3d7b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -10,7 +10,7 @@ project(PyPoPS #include_directories("${PROJECT_SOURCE_DIR}/") add_subdirectory(pybind11) -add_subdirectory(PoPS) +add_subdirectory(pops-core) pybind11_add_module(_pypops src/pypops.cpp include/raster.hpp include/helpers.hpp) diff --git a/PoPS b/PoPS deleted file mode 160000 index 835748d..0000000 --- a/PoPS +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 835748d48c0626809cc735076792e647455b95db diff --git a/include/helpers.hpp b/include/helpers.hpp index ceb96ae..e05c523 100644 --- a/include/helpers.hpp +++ b/include/helpers.hpp @@ -7,22 +7,27 @@ #include #include -template -std::string to_string(const T& value) { +template +std::string to_string(const T& value) +{ std::stringstream stream; stream << value; return stream.str(); } -std::string to_string(const pybind11::buffer_info& info) { +std::string to_string(const pybind11::buffer_info& info) +{ std::stringstream stream; stream << "itemsize: " << info.itemsize << "\n" << "format: " << info.format << "\n" - << "ndim: " << info.ndim << "\n" + << "ndim: " << info.ndim + << "\n" // TODO: generalize << "shape[0], shape[1]: " << info.shape[0] << ", " << info.shape[1] << "\n" - << "strides[0], strides[1]: " << info.strides[0] << ", " << info.strides[1] << "\n" - << "strides / itemsize: " << info.strides[0] / info.itemsize << ", " << info.strides[1] / info.itemsize << "\n"; + << "strides[0], strides[1]: " << info.strides[0] << ", " << info.strides[1] + << "\n" + << "strides / itemsize: " << info.strides[0] / info.itemsize << ", " + << info.strides[1] / info.itemsize << "\n"; // TODO: tell if it is C or F order (silent otherwise) return stream.str(); } @@ -48,13 +53,20 @@ void compatible_scalar_type_or_throw(const pybind11::buffer_info& info) if (diff_format && !diff_size) throw std::runtime_error( - "Incompatible scalar format in array: expected " + expected_format + ", got " + provided_format + " (both have " + expected_size + " bytes, using C++ type with id: "+ expected_cpp_typeid + ")"); + "Incompatible scalar format in array: expected " + expected_format + + ", got " + provided_format + " (both have " + expected_size + + " bytes, using C++ type with id: " + expected_cpp_typeid + ")"); else if (!diff_format && diff_size) throw std::runtime_error( - "Incompatible scalar size in array: expected " + expected_size + ", got " + provided_size + " (both have " + expected_format + " format, using C++ type with id: "+ expected_cpp_typeid + ")"); + "Incompatible scalar size in array: expected " + expected_size + ", got " + + provided_size + " (both have " + expected_format + + " format, using C++ type with id: " + expected_cpp_typeid + ")"); else throw std::runtime_error( - "Incompatible scalar in array: expected " + expected_format + " format with " + expected_size + " bytes, got " + provided_format + " format with " + provided_size + " bytes (using C++ type with id: "+ expected_cpp_typeid + ")"); + "Incompatible scalar in array: expected " + expected_format + + " format with " + expected_size + " bytes, got " + provided_format + + " format with " + provided_size + + " bytes (using C++ type with id: " + expected_cpp_typeid + ")"); } template @@ -64,8 +76,10 @@ void raster_compatible_or_throw(const pybind11::buffer_info& info) if (info.ndim != 2) throw std::runtime_error("Incompatible buffer dimension: expected a 2D array!"); if (info.strides[0] != info.itemsize * info.shape[1] - && info.strides[1] != info.itemsize) - throw std::runtime_error("Incompatible order: expected row-major (C-style) order. Got " + to_string(info)); // TODO: tell if it is the F order + && info.strides[1] != info.itemsize) + throw std::runtime_error( + "Incompatible order: expected row-major (C-style) order. Got " + + to_string(info)); // TODO: tell if it is the F order } -#endif // HELPERS_HPP +#endif // HELPERS_HPP diff --git a/include/raster.hpp b/include/raster.hpp index 17d1f2b..0a021dc 100644 --- a/include/raster.hpp +++ b/include/raster.hpp @@ -67,7 +67,8 @@ RasterType py_buffer_info_to_raster(pybind11::buffer b) // TODO: The return from function does not work, so unused for now. // Perhaps a 3D array will be needed for interface anyway. template -std::vector py_buffers_info_to_rasters(std::vector& buffers) +std::vector +py_buffers_info_to_rasters(std::vector& buffers) { std::vector rasters; rasters.reserve(buffers.size()); @@ -93,33 +94,29 @@ pybind11::buffer_info raster_to_py_buffer_info(RasterType& raster) // Buffer dimensions {raster.rows(), raster.cols()}, // Strides (in bytes) for each index, row-major order - {sizeof(NumberType) * raster.cols(), - sizeof(NumberType) * 1} - ); + {sizeof(NumberType) * raster.cols(), sizeof(NumberType) * 1}); } // We use template parameter instead of py::module to avoid this // implementation detail by duck typing. template -void raster_class(PythonModule& m, const std::string& name) { +void raster_class(PythonModule& m, const std::string& name) +{ pybind11::class_(m, name.c_str(), pybind11::buffer_protocol()) - .def(pybind11::init([](pybind11::buffer b) { - return py_buffer_info_to_raster(b); - // return by raw pointer - // or: return std::make_unique(...); // return by holder - // or: return Foo(...); // return by value (move constructor) - })) + .def(pybind11::init([](pybind11::buffer b) { + return py_buffer_info_to_raster(b); + // return by raw pointer + // or: return std::make_unique(...); // return by holder + // or: return Foo(...); // return by value (move constructor) + })) - .def_buffer( - raster_to_py_buffer_info - ); + .def_buffer(raster_to_py_buffer_info); } template void test_compatibility(PythonModule m, std::string name) { - m.def(name.c_str(), - [](pybind11::buffer b) { + m.def(name.c_str(), [](pybind11::buffer b) { pybind11::buffer_info info = b.request(); raster_compatible_or_throw(info); }); @@ -131,17 +128,16 @@ pybind11::object get_float_raster_scalar_type() // https://docs.scipy.org/doc/numpy/user/basics.types.html pybind11::object dtype = np.attr("dtype"); std::vector candidates = { -// numpy.attr("double").attr("itemsize"), -// numpy.attr("single"), -// numpy.attr("longdouble"), -// numpy.attr("float16"), -// numpy.attr("half") + // numpy.attr("double").attr("itemsize"), + // numpy.attr("single"), + // numpy.attr("longdouble"), + // numpy.attr("float16"), + // numpy.attr("half") dtype("float64"), dtype("float32"), dtype("double"), dtype("single"), - dtype("longdouble") - }; + dtype("longdouble")}; for (const auto candidate : candidates) { if (sizeof(Float) == pybind11::int_(candidate.attr("itemsize"))) return candidate; @@ -166,4 +162,4 @@ pybind11::object get_integer_raster_scalar_type() throw std::logic_error("Cannot find a compatible scalar type"); } -#endif // RASTER_HPP +#endif // RASTER_HPP diff --git a/pops-core b/pops-core new file mode 160000 index 0000000..8edb661 --- /dev/null +++ b/pops-core @@ -0,0 +1 @@ +Subproject commit 8edb661313dc946f0d9406872bf5fb4e06705b11 diff --git a/pypops/simulation.py b/pypops/simulation.py index 6372946..59c1eaa 100644 --- a/pypops/simulation.py +++ b/pypops/simulation.py @@ -12,22 +12,36 @@ """PyPoPS - Main simulation interface """ +import json + import _pypops +from _pypops import Config int_type = _pypops.get_integer_raster_scalar_type() float_type = _pypops.get_float_raster_scalar_type() +def json_to_config(config): + """Run one PoPS simulation""" + with open(config) as config_file: + config_values = json.load(config_file) + result = Config() + for key, value in config_values.items(): + setattr(result, key, value) + return result + + def pops( random_seed, - steps, use_lethal_temperature=False, lethal_temperature=None, infected=None, susceptible=None, + exposed=None, total_plants=None, mortality_tracker=None, + died=None, # dispersers=dispersers, weather=False, temperature=None, @@ -50,13 +64,14 @@ def pops( """Run one PoPS simulation""" result = _pypops.test_simulation( random_seed=random_seed, - steps=steps, use_lethal_temperature=use_lethal_temperature, lethal_temperature=lethal_temperature, infected=infected, susceptible=susceptible, + exposed=exposed, total_plants=total_plants, mortality_tracker=mortality_tracker, + died=died, weather=weather, temperature=temperature, weather_coefficient=weather_coefficient, diff --git a/src/pypops.cpp b/src/pypops.cpp index 3a3ab25..2dc0074 100644 --- a/src/pypops.cpp +++ b/src/pypops.cpp @@ -3,6 +3,7 @@ #include #include +#include #include #include @@ -39,8 +40,8 @@ FloatRaster return_new_raster(double start_number) struct Result { - //IntegerRaster infected; - //IntegerRaster susceptible; + // IntegerRaster infected; + // IntegerRaster susceptible; std::vector> outside_dispersers; }; @@ -49,71 +50,89 @@ struct Result // or the customizable output functions will take care of it by a custom // context which can be these vectors or write functions Result test_simulation( - int random_seed, - int steps, - bool use_lethal_temperature, - double lethal_temperature, - IntegerRaster infected, - IntegerRaster susceptible, - IntegerRaster total_plants, - IntegerRaster mortality_tracker, - bool weather, - std::vector temperature, - std::vector weather_coefficient, - double ew_res, - double ns_res, - double reproductive_rate, - std::string natural_kernel_type, - double natural_scale, - std::string natural_direction, - double natural_kappa, - bool use_anthropogenic_kernel, - double percent_natural_dispersal, - std::string anthro_kernel_type, - double anthro_scale, - std::string anthro_direction, - double anthro_kappa - ) + int random_seed, + bool use_lethal_temperature, + double lethal_temperature, + IntegerRaster infected, + IntegerRaster susceptible, + std::vector exposed, + IntegerRaster total_plants, + std::vector mortality_tracker, + IntegerRaster died, + bool weather, + std::vector temperatures, + std::vector weather_coefficient, + double ew_res, + double ns_res, + double reproductive_rate, + std::string natural_kernel_type, + double natural_scale, + std::string natural_direction, + double natural_kappa, + bool use_anthropogenic_kernel, + double percent_natural_dispersal, + std::string anthro_kernel_type, + double anthro_scale, + std::string anthro_direction, + double anthro_kappa) { IntegerRaster dispersers(infected.rows(), infected.cols(), 0); + IntegerRaster zeros(infected.rows(), infected.cols(), 0); + IntegerRaster resistant(infected.rows(), infected.cols(), 0); + IntegerRaster quarantine_areas(infected.rows(), infected.cols(), 0); std::vector> outside_dispersers; + std::vector> movements; - DispersalKernelType natural_kernel = kernel_type_from_string(natural_kernel_type); - DispersalKernelType anthro_kernel = kernel_type_from_string(anthro_kernel_type); + Config config; + config.random_seed = random_seed; + // TODO: all time related here! + config.model_type = "SI"; + config.use_lethal_temperature = use_lethal_temperature; + config.lethal_temperature = lethal_temperature; + config.weather = weather; + config.ew_res = ew_res; + config.ns_res = ns_res; + config.rows = infected.rows(); + config.cols = infected.cols(); + config.reproductive_rate = reproductive_rate; + config.natural_kernel_type = natural_kernel_type; + config.natural_scale = natural_scale; + config.natural_direction = natural_direction; + config.natural_kappa = natural_kappa; + config.use_anthropogenic_kernel = use_anthropogenic_kernel; + config.percent_natural_dispersal = percent_natural_dispersal; + config.anthro_kernel_type = anthro_kernel_type; + config.anthro_scale = anthro_scale; + config.anthro_direction = anthro_direction; + config.anthro_kappa = anthro_kappa; - RadialDispersalKernel natural_radial_kernel( - ew_res, ns_res, - natural_kernel, - natural_scale, - direction_from_string(natural_direction), - natural_kappa); - RadialDispersalKernel long_radial_kernel( - ew_res, ns_res, - anthro_kernel, - anthro_scale, - direction_from_string(anthro_direction), - anthro_kappa); - UniformDispersalKernel uniform_kernel(infected.rows(), infected.cols()); - SwitchDispersalKernel natural_selectable_kernel( - natural_kernel, - natural_radial_kernel, uniform_kernel); - SwitchDispersalKernel anthro_selectable_kernel( - anthro_kernel, - long_radial_kernel, uniform_kernel); - DispersalKernel dispersal_kernel(natural_selectable_kernel, - anthro_selectable_kernel, - use_anthropogenic_kernel, - percent_natural_dispersal); + config.create_schedules(); - Simulation simulation(random_seed, infected.rows(), infected.cols()); - for (int step = 0; step < steps; ++step) { - if (use_lethal_temperature) - simulation.remove(infected, susceptible, temperature[step], lethal_temperature); - simulation.generate(dispersers, infected, weather, weather_coefficient[step], reproductive_rate); - simulation.disperse(dispersers, susceptible, infected, - mortality_tracker, total_plants, - outside_dispersers, weather, weather_coefficient[step], - dispersal_kernel); + Treatments treatments(config.scheduler()); + SpreadRate spread_rate( + infected, config.ew_res, config.ns_res, config.rate_num_steps()); + QuarantineEscape quarantine(zeros, config.ew_res, config.ns_res, 0); + + Model model(config); + for (unsigned step = 0; step < config.scheduler().get_num_steps(); ++step) { + model.run_step( + step, + infected, + susceptible, + total_plants, + dispersers, + exposed, + mortality_tracker, + died, + temperatures, + weather_coefficient[step], + treatments, + resistant, + outside_dispersers, + spread_rate, + quarantine, + quarantine_areas, + movements); } return {outside_dispersers}; } @@ -123,90 +142,90 @@ Result test_simulation( // or the customizable output functions will take care of it by a custom // context which can be these vectors or write functions Result test_simulation_wrapper( - int random_seed, - int steps, - bool use_lethal_temperature, - double lethal_temperature, - py::buffer infected, - py::buffer susceptible, - py::buffer total_plants, - py::buffer mortality_tracker, - bool weather, - std::vector temperature, - std::vector weather_coefficient, - double ew_res, - double ns_res, - double reproductive_rate, - std::string natural_kernel_type, - double natural_scale, - std::string natural_direction, - double natural_kappa, - bool use_anthropogenic_kernel, - double percent_natural_dispersal, - std::string anthro_kernel_type, - double anthro_scale, - std::string anthro_direction, - double anthro_kappa - ) + int random_seed, + bool use_lethal_temperature, + double lethal_temperature, + py::buffer infected, + py::buffer susceptible, + std::vector exposed, + py::buffer total_plants, + std::vector mortality_tracker, + py::buffer died, + bool weather, + std::vector temperature, + std::vector weather_coefficient, + double ew_res, + double ns_res, + double reproductive_rate, + std::string natural_kernel_type, + double natural_scale, + std::string natural_direction, + double natural_kappa, + bool use_anthropogenic_kernel, + double percent_natural_dispersal, + std::string anthro_kernel_type, + double anthro_scale, + std::string anthro_direction, + double anthro_kappa) { return test_simulation( - random_seed, - steps, - use_lethal_temperature, - lethal_temperature, - py_buffer_info_to_raster(infected), - py_buffer_info_to_raster(susceptible), - py_buffer_info_to_raster(total_plants), - py_buffer_info_to_raster(mortality_tracker), - weather, - py_buffers_info_to_rasters(temperature), - py_buffers_info_to_rasters(weather_coefficient), - ew_res, - ns_res, - reproductive_rate, - natural_kernel_type, - natural_scale, - natural_direction, - natural_kappa, - use_anthropogenic_kernel, - percent_natural_dispersal, - anthro_kernel_type, - anthro_scale, - anthro_direction, - anthro_kappa - ); + random_seed, + use_lethal_temperature, + lethal_temperature, + py_buffer_info_to_raster(infected), + py_buffer_info_to_raster(susceptible), + py_buffers_info_to_rasters(exposed), + py_buffer_info_to_raster(total_plants), + py_buffers_info_to_rasters(mortality_tracker), + py_buffer_info_to_raster(died), + weather, + py_buffers_info_to_rasters(temperature), + py_buffers_info_to_rasters(weather_coefficient), + ew_res, + ns_res, + reproductive_rate, + natural_kernel_type, + natural_scale, + natural_direction, + natural_kappa, + use_anthropogenic_kernel, + percent_natural_dispersal, + anthro_kernel_type, + anthro_scale, + anthro_direction, + anthro_kappa); } // TODO: an output type seems to be reasonable // or a higher level object for the model/simulation // or the customizable output functions will take care of it by a custom // context which can be these vectors or write functions -IntegerRaster test_simulation2( - IntegerRaster infected -) +IntegerRaster test_simulation2(IntegerRaster infected) { infected(1, 1) = 200; auto result = 5 * infected; return result; } -IntegerRaster test_simulation2_buffer_wrapper(py::buffer b) { +IntegerRaster test_simulation2_buffer_wrapper(py::buffer b) +{ py::buffer_info info = b.request(); - if (info.format == py::format_descriptor::format() && info.itemsize == sizeof(Integer)) { + if (info.format == py::format_descriptor::format() + && info.itemsize == sizeof(Integer)) { raster_compatible_or_throw(info); - return test_simulation2(IntegerRaster(static_cast(info.ptr), info.shape[0], info.shape[1])); + return test_simulation2(IntegerRaster( + static_cast(info.ptr), info.shape[0], info.shape[1])); } raster_compatible_or_throw(info); throw std::logic_error("Raster-array scalar type incompatibility not identified"); } -PYBIND11_MODULE(_pypops, m) { +PYBIND11_MODULE(_pypops, m) +{ m.doc() = "C++ PoPS wrapper for PyPoPS"; - m.def("get_float_raster_scalar_type", - &get_float_raster_scalar_type); - m.def("get_integer_raster_scalar_type", - &get_integer_raster_scalar_type); + m.def("get_float_raster_scalar_type", &get_float_raster_scalar_type); + m.def("get_integer_raster_scalar_type", &get_integer_raster_scalar_type); raster_class(m, "FloatRaster"); raster_class(m, "IntegerRaster"); @@ -227,62 +246,93 @@ PYBIND11_MODULE(_pypops, m) { // pypops.test_compatibility_long(np.array([[2,1,0], [4,6,7]], dtype=np.long)) // expected q, got l (both have 8 bytes, using C++ type with id: l) - m.def("return_new_raster", - &return_new_raster, - "start_number"_a); + m.def("return_new_raster", &return_new_raster, "start_number"_a); - m.def("modify_existing_raster", - [](py::buffer b, Float x) { + m.def("modify_existing_raster", [](py::buffer b, Float x) { // we convert Float scalar to Integer when needed py::buffer_info info = b.request(); - if (info.format == py::format_descriptor::format() && info.itemsize == sizeof(Float)) { + if (info.format == py::format_descriptor::format() + && info.itemsize == sizeof(Float)) { raster_compatible_or_throw(info); - modify_existing_raster(FloatRaster(static_cast(info.ptr), info.shape[0], info.shape[1]), x); - } else if (info.format == py::format_descriptor::format() && info.itemsize == sizeof(Integer)) { + modify_existing_raster( + FloatRaster( + static_cast(info.ptr), info.shape[0], info.shape[1]), + x); + } + else if ( + info.format == py::format_descriptor::format() + && info.itemsize == sizeof(Integer)) { raster_compatible_or_throw(info); - modify_existing_raster(IntegerRaster(static_cast(info.ptr), info.shape[0], info.shape[1]), Integer(x)); - } else { + modify_existing_raster( + IntegerRaster( + static_cast(info.ptr), info.shape[0], info.shape[1]), + Integer(x)); + } + else { // throw raster_compatible_or_throw(info); } }); + py::class_(m, "Config") + .def(py::init<>()) + .def_readwrite("random_seed", &Config::random_seed) + .def_readwrite("rows", &Config::rows) + .def_readwrite("cols", &Config::cols) + .def_readwrite("ew_res", &Config::ew_res) + .def_readwrite("ns_res", &Config::ns_res) + .def_readwrite("use_lethal_temperature", &Config::use_lethal_temperature) + .def_readwrite("lethal_temperature", &Config::lethal_temperature) + .def_readwrite("weather", &Config::weather) + .def_readwrite("reproductive_rate", &Config::reproductive_rate) + .def_readwrite("natural_kernel_type", &Config::natural_kernel_type) + .def_readwrite("natural_scale", &Config::natural_scale) + .def_readwrite("natural_direction", &Config::natural_direction) + .def_readwrite("natural_kappa", &Config::natural_kappa) + .def_readwrite("use_anthropogenic_kernel", &Config::use_anthropogenic_kernel) + .def_readwrite("percent_natural_dispersal", &Config::percent_natural_dispersal) + .def_readwrite("anthro_kernel_type", &Config::anthro_kernel_type) + .def_readwrite("anthro_scale", &Config::anthro_scale) + .def_readwrite("anthro_direction", &Config::anthro_direction) + .def_readwrite("anthro_kappa", &Config::anthro_kappa); + // pybind11::class_(m, "Model"). + // .def_readwrite("random_seed", &Config::random_seed); + py::class_(m, "Result") - //.def_readonly("infected", &Result::infected) - //.def_readonly("susceptible", &Result::susceptible) - .def_readonly("outside_dispersers", &Result::outside_dispersers) - ; + //.def_readonly("infected", &Result::infected) + //.def_readonly("susceptible", &Result::susceptible) + .def_readonly("outside_dispersers", &Result::outside_dispersers); // Tip: if you get "number of annotations does not match" // but parameters seems to be right, check the commas. - m.def("test_simulation", - &test_simulation_wrapper, - "random_seed"_a, - "steps"_a, - "use_lethal_temperature"_a, - "lethal_temperature"_a, - "infected"_a, - "susceptible"_a, - "total_plants"_a, - "mortality_tracker"_a, - "weather"_a, - "temperature"_a, - "weather_coefficient"_a, - "ew_res"_a, - "ns_res"_a, - "reproductive_rate"_a, - "natural_kernel_type"_a, - "natural_scale"_a, - "natural_direction"_a, - "natural_kappa"_a, - "use_anthropogenic_kernel"_a, - "percent_natural_dispersal"_a, - "anthro_kernel_type"_a, - "anthro_scale"_a, - "anthro_direction"_a, - "anthro_kappa"_a - ); + m.def( + "test_simulation", + &test_simulation_wrapper, + "random_seed"_a, + "use_lethal_temperature"_a, + "lethal_temperature"_a, + "infected"_a, + "susceptible"_a, + "exposed"_a, + "total_plants"_a, + "mortality_tracker"_a, + "died"_a, + "weather"_a, + "temperature"_a, + "weather_coefficient"_a, + "ew_res"_a, + "ns_res"_a, + "reproductive_rate"_a, + "natural_kernel_type"_a, + "natural_scale"_a, + "natural_direction"_a, + "natural_kappa"_a, + "use_anthropogenic_kernel"_a, + "percent_natural_dispersal"_a, + "anthro_kernel_type"_a, + "anthro_scale"_a, + "anthro_direction"_a, + "anthro_kappa"_a); - m.def("test_simulation2", - &test_simulation2_buffer_wrapper); + m.def("test_simulation2", &test_simulation2_buffer_wrapper); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index cc9c4f8..f52cda2 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -1,3 +1,5 @@ +# Only tests of C++ code are handled here (_pypops not pypops). + find_package(Python3 COMPONENTS Interpreter) add_test(NAME test_new_and_modify_array diff --git a/tests/data/simple_config.json b/tests/data/simple_config.json new file mode 100644 index 0000000..393e923 --- /dev/null +++ b/tests/data/simple_config.json @@ -0,0 +1,19 @@ +{ + "random_seed": 42, + "use_lethal_temperature": true, + "lethal_temperature": -1.5, + "weather": true, + "ew_res": 100.0, + "ns_res": 100.0, + "reproductive_rate": 400.4, + "natural_kernel_type": "cauchy", + "natural_scale": 20, + "natural_direction": "none", + "natural_kappa": 0, + "use_anthropogenic_kernel": false, + "percent_natural_dispersal": 0, + "anthro_kernel_type": "cauchy", + "anthro_scale": 0, + "anthro_direction": "none", + "anthro_kappa": 0 + } \ No newline at end of file diff --git a/tests/test_config.py b/tests/test_config.py new file mode 100644 index 0000000..c32aef5 --- /dev/null +++ b/tests/test_config.py @@ -0,0 +1,10 @@ +import pytest + +from pypops.simulation import json_to_config + + +def test_load_json(shared_datadir): + config = json_to_config(shared_datadir / "simple_config.json") + assert config.random_seed == 42 + assert config.use_lethal_temperature is True + assert config.lethal_temperature == pytest.approx(-1.5, abs=0.1) diff --git a/tests/test_simulation_function.py b/tests/test_simulation_function.py index 6169d32..cbe824a 100644 --- a/tests/test_simulation_function.py +++ b/tests/test_simulation_function.py @@ -10,21 +10,27 @@ def test_simulation_function(): infected = np.array([[5, 0, 0], [0, 0, 0]], dtype=np.int32) mortality_tracker = np.array([[0, 0, 0], [0, 0, 0]], dtype=int_type) + died = np.array([[0, 0, 0], [0, 0, 0]], dtype=int_type) # dispersers = np.array([[0, 0, 0], [0, 0, 0]], dtype=int_type) susceptible = np.array([[10, 6, 20], [14, 15, 20]], dtype=int_type) total_plants = np.array([[15, 6, 20], [14, 15, 25]], dtype=int_type) temperature = np.array([[5, 0, 5], [0, 0, 5]], dtype=float_type) weather_coefficient = np.array([[0.6, 0.8, 0.7], [0.2, 0.8, 0.5]], dtype=float_type) + latency_period_steps = 0 + exposed_size = latency_period_steps + 1 + exposed = [np.array([[0, 0, 0], [0, 0, 0]], dtype=int_type)] * exposed_size + a = pypops.test_simulation( random_seed=42, - steps=2, use_lethal_temperature=False, lethal_temperature=-1.5, infected=infected, susceptible=susceptible, + exposed=exposed, total_plants=total_plants, - mortality_tracker=mortality_tracker, + mortality_tracker=[mortality_tracker, mortality_tracker], + died=died, # dispersers=dispersers, weather=True, temperature=[temperature, temperature], diff --git a/tests/test_simulation_pops.py b/tests/test_simulation_pops.py index 40f1a6c..64e5a10 100644 --- a/tests/test_simulation_pops.py +++ b/tests/test_simulation_pops.py @@ -11,6 +11,7 @@ def test_types_are_different(): def test_simulation_function(): infected = np.array([[5, 0, 0], [0, 0, 0]], dtype=int_type) mortality_tracker = np.array([[0, 0, 0], [0, 0, 0]], dtype=int_type) + died = np.array([[0, 0, 0], [0, 0, 0]], dtype=int_type) # dispersers = np.array([[0, 0, 0], [0, 0, 0]], dtype=int_type) susceptible = np.array([[10, 6, 20], [14, 15, 20]], dtype=int_type) total_plants = np.array([[15, 6, 20], [14, 15, 25]], dtype=int_type) @@ -18,15 +19,20 @@ def test_simulation_function(): temperature = np.array([[5, 0, 5], [0, 0, 5]], dtype=float_type) weather_coefficient = np.array([[0.6, 0.8, 0.7], [0.2, 0.8, 0.5]], dtype=float_type) + latency_period_steps = 0 + exposed_size = latency_period_steps + 1 + exposed = [np.array([[0, 0, 0], [0, 0, 0]], dtype=int_type)] * exposed_size + result = pops( random_seed=42, - steps=2, use_lethal_temperature=False, lethal_temperature=-1.5, infected=infected, susceptible=susceptible, + exposed=exposed, total_plants=total_plants, - mortality_tracker=mortality_tracker, + mortality_tracker=[mortality_tracker, mortality_tracker], + died=died, # dispersers=dispersers, weather=True, temperature=[temperature, temperature],