Skip to content

Commit

Permalink
1105 redesign bindings structure to improve typing (SciCompMod#1106)
Browse files Browse the repository at this point in the history
include python bindings for models and memilio core in the same python module with submodules to enable using core types in the models
clean up directory structure: separate cpp binding code from python code
fix small bugs in python binding code
  • Loading branch information
MaxBetzDLR authored Aug 21, 2024
1 parent 896a04e commit 49ee2e8
Show file tree
Hide file tree
Showing 51 changed files with 319 additions and 153 deletions.
32 changes: 16 additions & 16 deletions pycode/memilio-simulation/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -43,45 +43,45 @@ function(add_pymio_module target_name)
pybind11_add_module(${target_name} MODULE ${PYBIND11_MODULE_SOURCES})
target_link_libraries(${target_name} PRIVATE ${PYBIND11_MODULE_LINKED_LIBRARIES})
target_include_directories(${target_name} PRIVATE memilio/simulation)
install(TARGETS ${target_name} LIBRARY DESTINATION memilio)
install(TARGETS ${target_name} LIBRARY DESTINATION memilio/simulation/bindings)
endfunction()


# build python extensions
add_pymio_module(_simulation_abm
LINKED_LIBRARIES memilio abm
SOURCES memilio/simulation/abm.cpp
SOURCES memilio/simulation/bindings/models/abm.cpp
)

add_pymio_module(_simulation
LINKED_LIBRARIES memilio
SOURCES memilio/simulation/simulation.cpp
memilio/simulation/epidemiology/damping_sampling.cpp
memilio/simulation/epidemiology/uncertain_matrix.cpp
memilio/simulation/mobility/metapopulation_mobility_instant.cpp
memilio/simulation/utils/date.cpp
memilio/simulation/utils/logging.cpp
memilio/simulation/utils/time_series.cpp
memilio/simulation/utils/parameter_distributions.cpp
memilio/simulation/utils/uncertain_value.cpp
SOURCES memilio/simulation/bindings/simulation.cpp
memilio/simulation/bindings/epidemiology/damping_sampling.cpp
memilio/simulation/bindings/epidemiology/uncertain_matrix.cpp
memilio/simulation/bindings/mobility/metapopulation_mobility_instant.cpp
memilio/simulation/bindings/utils/date.cpp
memilio/simulation/bindings/utils/logging.cpp
memilio/simulation/bindings/utils/time_series.cpp
memilio/simulation/bindings/utils/parameter_distributions.cpp
memilio/simulation/bindings/utils/uncertain_value.cpp
)

add_pymio_module(_simulation_osir
LINKED_LIBRARIES memilio ode_sir
SOURCES memilio/simulation/osir.cpp
SOURCES memilio/simulation/bindings/models/osir.cpp
)

add_pymio_module(_simulation_oseir
LINKED_LIBRARIES memilio ode_seir
SOURCES memilio/simulation/oseir.cpp
SOURCES memilio/simulation/bindings/models/oseir.cpp
)

add_pymio_module(_simulation_osecir
LINKED_LIBRARIES memilio ode_secir
SOURCES memilio/simulation/osecir.cpp
SOURCES memilio/simulation/bindings/models/osecir.cpp
)

add_pymio_module(_simulation_osecirvvs
LINKED_LIBRARIES memilio ode_secirvvs
SOURCES memilio/simulation/osecirvvs.cpp
)
SOURCES memilio/simulation/bindings/models/osecirvvs.cpp
)
40 changes: 26 additions & 14 deletions pycode/memilio-simulation/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,7 @@ To install the package, use the command (from the directory containing `setup.py
pip install .
```

This builds the C++ library and C++ Python extension module and copies everything required to your site-packages.

For developement of code use

```bash
pip install -e .[dev]
```

This command allows you to work on the code without having to reinstall the package after a change. Note that this only works for changes to Python code. If C++ code is modified, the install command has to be repeated every time. The command also installs all additional dependencies required for development and maintenance.

For development, it may be easier to use the alternative command `python setup.py <build|install|develop>` which provides better configuration and observation of the C++ build process.
This builds the C++ library and C++ Python extension module and copies everything required to your site-packages.

All the requirements of the [C++ library](../../cpp/README.md) must be met in order to build and use the python bindings. A virtual environment is recommended.

Expand All @@ -38,11 +28,33 @@ python setup.py install -- -DCMAKE_BUILD_TYPE=Debug -DMEMILIO_USE_BUNDLED_PYBIND

Alternatively, the `CMakeCache.txt` in the directory created by Scikit-Build can be edited to set the options.

## Development

For developement of the cpp bindings use

```bash
pip install -e .[dev]
```

This command allows you to work on the code without having to reinstall the package after a change. Note that this only works for changes to Python code. If C++ code is modified, the install command has to be repeated every time. The command also installs all additional dependencies required for development and maintenance.

For development, it may be easier to use the alternative command `python setup.py <build|install|develop>` which provides better configuration and observation of the C++ build process.

The [bindings](memilio/simulation/bindings/) folder contains all the C++ code to expose the MEmilio library to Python and follows its structure, expect for the models that are bundled in a subfolder.

In order to add a new model, the following steps need to be taken:
1. Add new bindings including a model file in the [models](memilio/simulation/bindings/models/) folder that defines the new module
2. Add the new module to the building process by modifying [CMakeLists.txt](CMakeLists.txt)
3. Add a python module file similar to the other models, e.g. [osir.py](memilio/simulation/osir.py) (needed for the structure of the python package) and modify getter function in [__init__.py](memilio/simulation/__init__.py)
4. Write new tests and examples

The bindings can also be expanded with pure Python code by expanding the existing python files or adding new ones underneath the [simulation](memilio/simulation/) folder.

## Stubs

A stub file is a file containing a skeleton of the public interface of that Python module including classes, variables, functions and their types. They help by enabling autocompletes and type annotations. `pybind11-stubgen` is used to generate the stub files for the MEmilio Python Bindings and provide them as a separate stubs-only package.
A stub file is a file containing a skeleton of the public interface of that Python module including classes, variables, functions and their types. They help by enabling autocompletes and type annotations. `mypy stubgen` is used to generate the stub files for the MEmilio Python Bindings and provide them as a separate stubs-only package.

For installing stubs you first need to install our package `memilio.simulation` and the external dependency `pybind11-stubgen` for your python interpreter. Then run [generate_stubs.py](tools/generate_stubs.py) to generate the stubs-only package and install it as `memilio-stubs`, e.g. from the [current folder](.)
For installing stubs you first need to install our package `memilio.simulation` (not in editable mode -e) and the external dependency `mypy` for your python interpreter. Then run [generate_stubs.py](tools/generate_stubs.py) to generate the stubs-only package and install it as `memilio-stubs`, e.g. from the [current folder](.)

```bash
python ./tools/generate_stubs.py
Expand All @@ -59,7 +71,7 @@ The package provides the following modules:
- `memilio.simulation.osecirvvs`: Extended ODE SECIHURD model to include vaccinations and multi-layered immunity, among other enhancements, with the ability to integrate new disease variants. Simulation includes demographic and geographic resolution, corresponds to the model in `cpp/models/ode_secirvvs`.
`memilio.simulation.abm`: Agent based model and simulation, corresponds to the model in `cpp/models/abm` (python model is incomplete and work in progress).

Detailed documentation under construction. See the scripts in the examples directory for more information.
Detailed documentation under construction. See the scripts in the [examples](../examples/simulation/) directory for more information.

## Testing

Expand Down
27 changes: 26 additions & 1 deletion pycode/memilio-simulation/memilio/simulation/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,29 @@
The Python bindings to the MEmilio C++ library.
"""

from memilio._simulation import *
from memilio.simulation._simulation import *


def __getattr__(attr):
"""! The __getattr__ function is used here to implement lazy loading for the submodules within the memilio.simulation package.
Submodules are only imported when they are first accessed, which can save memory and reduce startup time, if not all submodules
are needed for every execution.
"""
if attr == "abm":
import memilio.simulation.abm as abm
return abm
elif attr == "osir":
import memilio.simulation.osir as osir
return osir
elif attr == "oseir":
import memilio.simulation.oseir as oseir
return oseir
elif attr == "osecir":
import memilio.simulation.osecir as osecir
return osecir
elif attr == "osecirvvs":
import memilio.simulation.osecirvvs as osecirvvs
return osecirvvs

raise AttributeError("module {!r} has no attribute "
"{!r}".format(__name__, attr))
2 changes: 1 addition & 1 deletion pycode/memilio-simulation/memilio/simulation/abm.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,4 @@
Python bindings for MEmilio ABM model.
"""

from memilio._simulation_abm import *
from memilio.simulation._simulation_abm import *
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2020-2024 MEmilio
*
* Authors: Maximilian Betz
*
* Contact: Martin J. Kuehn <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef PYMIO_AGE_GROUP_H
#define PYMIO_AGE_GROUP_H

#include "memilio/epidemiology/age_group.h"

#include "pybind11/pybind11.h"

namespace pymio
{

template <>
inline std::string pretty_name<mio::AgeGroup>()
{
return "AgeGroup";
}

} // namespace pymio

#endif //PYMIO_AGE_GROUP_H
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ void bind_damping_members(DampingClass& damping_class)
.def_property(
"time",
[](const Damping& self) {
return self.get_time();
return double(self.get_time());
},
[](Damping& self, double v) {
self.get_time() = mio::SimulationTime(v);
Expand All @@ -66,7 +66,7 @@ void bind_damping_members(DampingClass& damping_class)
.def_property(
"type",
[](const Damping& self) {
return self.get_type();
return int(self.get_type());
},
[](Damping& self, int v) {
self.get_type() = mio::DampingType(v);
Expand All @@ -75,7 +75,7 @@ void bind_damping_members(DampingClass& damping_class)
.def_property(
"level",
[](const Damping& self) {
return self.get_level();
return int(self.get_level());
},
[](Damping& self, int v) {
self.get_level() = mio::DampingLevel(v);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (C) 2020-2024 MEmilio
*
* Authors: Maximilian Betz
*
* Contact: Martin J. Kuehn <[email protected]>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef PYMIO_SIMULATION_DAY_H
#define PYMIO_SIMULATION_DAY_H

#include "memilio/epidemiology/simulation_day.h"

#include "pybind11/pybind11.h"

namespace pymio
{

template <>
inline std::string pretty_name<mio::SimulationDay>()
{
return "SimulationDay";
}

} // namespace pymio

#endif //PYMIO_SIMULATION_DAY_H
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ void bind_SimulationNode(pybind11::module_& m, std::string const& name)
{
bind_class<mio::Node<mio::SimulationNode<Simulation>>, EnablePickling::IfAvailable>(m, name.c_str())
.def_property_readonly("id",
[](const mio::Node<Simulation>& self) {
[](const mio::Node<mio::SimulationNode<Simulation>>& self) {
return self.id;
})
.def_property_readonly(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,9 @@
//Includes from MEmilio
#include "abm/simulation.h"

#include "pybind11/pybind11.h"
#include "pybind11/attr.h"
#include "pybind11/cast.h"
#include "pybind11/pybind11.h"
#include "pybind11/operators.h"
#include <cstdint>
#include <type_traits>
Expand Down Expand Up @@ -231,6 +231,8 @@ PYBIND11_MODULE(_simulation_abm, m)
static_cast<void (mio::abm::Simulation::*)(mio::abm::TimePoint)>(&mio::abm::Simulation::advance),
py::arg("tmax"))
.def_property_readonly("model", py::overload_cast<>(&mio::abm::Simulation::get_model));

m.attr("__version__") = "dev";
}

PYMIO_IGNORE_VALUE_TYPE(decltype(std::declval<mio::abm::Model>().get_locations()))
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/*
* Copyright (C) 2020-2024 MEmilio
*
* Authors: Martin Siggel, Daniel Abele, Martin J. Kuehn, Jan Kleinert, Khoa Nguyen
* Authors: Martin Siggel, Daniel Abele, Martin J. Kuehn, Jan Kleinert
*
* Contact: Martin J. Kuehn <[email protected]>
*
Expand All @@ -19,11 +19,11 @@
*/

//Includes from pymio
#include "memilio/config.h"
#include "pybind_util.h"
#include "compartments/simulation.h"
#include "compartments/flow_simulation.h"
#include "compartments/compartmentalmodel.h"
#include "epidemiology/age_group.h"
#include "epidemiology/populations.h"
#include "utils/custom_index_array.h"
#include "utils/parameter_set.h"
Expand All @@ -43,6 +43,7 @@
#include "memilio/mobility/graph.h"
#include "memilio/io/mobility_io.h"
#include "memilio/io/epi_data.h"
#include "memilio/config.h"

#include "pybind11/pybind11.h"
#include "pybind11/stl_bind.h"
Expand Down Expand Up @@ -150,26 +151,19 @@ using MobilityGraph = mio::Graph<mio::SimulationNode<mio::osecir::Simulation<>>,

} // namespace

PYBIND11_MAKE_OPAQUE(std::vector<MobilityGraph>);

namespace pymio
{

//specialization of pretty_name
template <>
std::string pretty_name<mio::osecir::InfectionState>()
inline std::string pretty_name<mio::osecir::InfectionState>()
{
return "InfectionState";
}

template <>
std::string pretty_name<mio::AgeGroup>()
{
return "AgeGroup";
}

} // namespace pymio

PYBIND11_MAKE_OPAQUE(std::vector<MobilityGraph>);

PYBIND11_MODULE(_simulation_osecir, m)
{
// https://github.com/pybind/pybind11/issues/1153
Expand Down Expand Up @@ -289,7 +283,7 @@ PYBIND11_MODULE(_simulation_osecir, m)
mio::Graph<mio::osecir::Model<double>, mio::MobilityParameters<double>>& params_graph,
size_t contact_locations_size) {
auto mobile_comp = {mio::osecir::InfectionState::Susceptible, mio::osecir::InfectionState::Exposed,
mio::osecir::InfectionState::InfectedNoSymptoms,
mio::osecir::InfectionState::InfectedNoSymptoms,
mio::osecir::InfectionState::InfectedSymptoms, mio::osecir::InfectionState::Recovered};
auto weights = std::vector<ScalarType>{0., 0., 1.0, 1.0, 0.33, 0., 0.};
auto result = mio::set_edges<ContactLocation, mio::osecir::Model<double>, mio::MobilityParameters<double>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
#include "compartments/compartmentalmodel.h"
#include "mobility/graph_simulation.h"
#include "mobility/metapopulation_mobility_instant.h"
#include "epidemiology/age_group.h"
#include "epidemiology/populations.h"
#include "io/mobility_io.h"
#include "io/result_io.h"
Expand Down Expand Up @@ -155,13 +156,7 @@ namespace pymio
{
//specialization of pretty_name
template <>
std::string pretty_name<mio::AgeGroup>()
{
return "AgeGroup";
}

template <>
std::string pretty_name<mio::osecirvvs::InfectionState>()
inline std::string pretty_name<mio::osecirvvs::InfectionState>()
{
return "InfectionState";
}
Expand Down
Loading

0 comments on commit 49ee2e8

Please sign in to comment.