Skip to content

Commit

Permalink
Extract bind_container to hpp
Browse files Browse the repository at this point in the history
  • Loading branch information
franzpoeschel committed Oct 26, 2022
1 parent bd68513 commit 17ff682
Show file tree
Hide file tree
Showing 4 changed files with 188 additions and 124 deletions.
136 changes: 136 additions & 0 deletions include/openPMD/binding/python/Container.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
/* Copyright 2018-2022 Axel Huebl and Franz Poeschel
*
* This file is part of openPMD-api.
*
* openPMD-api is free software: you can redistribute it and/or modify
* it under the terms of of either the GNU General Public License or
* the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* openPMD-api is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License and the GNU Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU General Public License
* and the GNU Lesser General Public License along with openPMD-api.
* If not, see <http://www.gnu.org/licenses/>.
*
* The function `bind_container` is based on std_bind.h in pybind11
* Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob
*
* BSD-style license, see pybind11 LICENSE file.
*/

#pragma once

#include "openPMD/backend/Attributable.hpp"

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>

#include <memory>
#include <string>
#include <utility>

namespace py = pybind11;

namespace openPMD::detail
{
/* based on std_bind.h in pybind11
*
* Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob
*
* BSD-style license, see pybind11 LICENSE file.
*/
template <typename Map, typename Class_>
Class_ bind_container(Class_ &cl, std::string const &name)
{
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;

// Register stream insertion operator (if possible)
py::detail::map_if_insertion_operator<Map, Class_>(cl, name);

cl.def(
"__bool__",
[](const Map &m) -> bool { return !m.empty(); },
"Check whether the container is nonempty");

cl.def(
"__iter__",
[](Map &m) { return py::make_key_iterator(m.begin(), m.end()); },
// keep container alive while iterator exists
py::keep_alive<0, 1>());

cl.def(
"items",
[](Map &m) { return py::make_iterator(m.begin(), m.end()); },
// keep container alive while iterator exists
py::keep_alive<0, 1>());

// keep same policy as Container class: missing keys are created
cl.def(
"__getitem__",
[](Map &m, KeyType const &k) -> MappedType & { return m[k]; },
// ref + keepalive
py::return_value_policy::reference_internal);

// Assignment provided only if the type is copyable
py::detail::map_assignment<Map, Class_>(cl);

cl.def("__delitem__", [](Map &m, KeyType const &k) {
auto it = m.find(k);
if (it == m.end())
throw py::key_error();
m.erase(it);
});

cl.def("__len__", &Map::size);

cl.def("_ipython_key_completions_", [](Map &m) {
auto l = py::list();
for (const auto &myPair : m)
l.append(myPair.first);
return l;
});

return cl;
}

template <
typename Map,
typename holder_type = std::unique_ptr<Map>,
typename... Args>
py::class_<Map, holder_type, Attributable> create_and_bind_container(
py::handle scope, std::string const &name, Args &&...args)
{
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;
using Class_ = py::class_<Map, holder_type, Attributable>;

// If either type is a non-module-local bound type then make the map
// binding non-local as well; otherwise (e.g. both types are either
// module-local or converting) the map will be module-local.
auto tinfo = py::detail::get_type_info(typeid(MappedType));
bool local = !tinfo || tinfo->module_local;
if (local)
{
tinfo = py::detail::get_type_info(typeid(KeyType));
local = !tinfo || tinfo->module_local;
}

Class_ cl(
scope,
name.c_str(),
py::module_local(local),
std::forward<Args>(args)...);

// maybe move this to bind_container
cl.def(py::init<Map const &>());
return bind_container<Map>(cl, name);
}
} // namespace openPMD::detail
47 changes: 32 additions & 15 deletions src/binding/python/BaseRecord.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include "openPMD/backend/Container.hpp"
#include "openPMD/backend/MeshRecordComponent.hpp"
#include "openPMD/backend/PatchRecordComponent.hpp"
#include "openPMD/binding/python/Container.hpp"
#include "openPMD/binding/python/UnitDimension.hpp"

namespace py = pybind11;
Expand All @@ -47,19 +48,35 @@ Returns true if this record only contains a single component.
// .def_property_readonly(
// "scalar", &BaseRecord<BaseRecordComponent>::scalar, doc_scalar);

py::class_<BaseRecord<RecordComponent>, Container<RecordComponent> >(
m, "Base_Record_Record_Component")
.def_property_readonly(
"scalar", &BaseRecord<RecordComponent>::scalar, doc_scalar);
py::class_<
BaseRecord<MeshRecordComponent>,
Container<MeshRecordComponent> >(m, "Base_Record_Mesh_Record_Component")
.def_property_readonly(
"scalar", &BaseRecord<MeshRecordComponent>::scalar, doc_scalar);
py::class_<
BaseRecord<PatchRecordComponent>,
Container<PatchRecordComponent> >(
m, "Base_Record_Patch_Record_Component")
.def_property_readonly(
"scalar", &BaseRecord<PatchRecordComponent>::scalar, doc_scalar);
{
auto cl =
py::class_<BaseRecord<RecordComponent>, RecordComponent>(
m, "Base_Record_Record_Component")
.def_property_readonly(
"scalar", &BaseRecord<RecordComponent>::scalar, doc_scalar);
detail::bind_container<BaseRecord<RecordComponent>>(
cl, "Base_Record_Record_Component");
}
{
auto cl =
py::class_<BaseRecord<MeshRecordComponent>, MeshRecordComponent>(
m, "Base_Record_Mesh_Record_Component")
.def_property_readonly(
"scalar",
&BaseRecord<MeshRecordComponent>::scalar,
doc_scalar);
detail::bind_container<BaseRecord<MeshRecordComponent>>(
cl, "Base_Record_Mesh_Record_Component");
}
{
auto cl =
py::class_<BaseRecord<PatchRecordComponent>, PatchRecordComponent>(
m, "Base_Record_Patch_Record_Component")
.def_property_readonly(
"scalar",
&BaseRecord<PatchRecordComponent>::scalar,
doc_scalar);
detail::bind_container<BaseRecord<PatchRecordComponent>>(
cl, "Base_Record_Patch_Record_Component");
}
}
119 changes: 15 additions & 104 deletions src/binding/python/Container.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,6 @@
*/

#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <pybind11/stl_bind.h>

#include "openPMD/Iteration.hpp"
#include "openPMD/Mesh.hpp"
Expand All @@ -39,102 +37,11 @@
#include "openPMD/backend/MeshRecordComponent.hpp"
#include "openPMD/backend/PatchRecord.hpp"
#include "openPMD/backend/PatchRecordComponent.hpp"

#include <memory>
#include <string>
#include <utility>
#include "openPMD/binding/python/Container.hpp"

namespace py = pybind11;
using namespace openPMD;

namespace detail
{
/* based on std_bind.h in pybind11
*
* Copyright (c) 2016 Sergey Lyskov and Wenzel Jakob
*
* BSD-style license, see pybind11 LICENSE file.
*/
template <
typename Map,
typename holder_type = std::unique_ptr<Map>,
typename... Args>
py::class_<Map, holder_type, Attributable>
bind_container(py::handle scope, std::string const &name, Args &&...args)
{
using KeyType = typename Map::key_type;
using MappedType = typename Map::mapped_type;
using Class_ = py::class_<Map, holder_type, Attributable>;

// If either type is a non-module-local bound type then make the map
// binding non-local as well; otherwise (e.g. both types are either
// module-local or converting) the map will be module-local.
auto tinfo = py::detail::get_type_info(typeid(MappedType));
bool local = !tinfo || tinfo->module_local;
if (local)
{
tinfo = py::detail::get_type_info(typeid(KeyType));
local = !tinfo || tinfo->module_local;
}

Class_ cl(
scope,
name.c_str(),
py::module_local(local),
std::forward<Args>(args)...);

cl.def(py::init<Map const &>());

// Register stream insertion operator (if possible)
py::detail::map_if_insertion_operator<Map, Class_>(cl, name);

cl.def(
"__bool__",
[](const Map &m) -> bool { return !m.empty(); },
"Check whether the container is nonempty");

cl.def(
"__iter__",
[](Map &m) { return py::make_key_iterator(m.begin(), m.end()); },
// keep container alive while iterator exists
py::keep_alive<0, 1>());

cl.def(
"items",
[](Map &m) { return py::make_iterator(m.begin(), m.end()); },
// keep container alive while iterator exists
py::keep_alive<0, 1>());

// keep same policy as Container class: missing keys are created
cl.def(
"__getitem__",
[](Map &m, KeyType const &k) -> MappedType & { return m[k]; },
// ref + keepalive
py::return_value_policy::reference_internal);

// Assignment provided only if the type is copyable
py::detail::map_assignment<Map, Class_>(cl);

cl.def("__delitem__", [](Map &m, KeyType const &k) {
auto it = m.find(k);
if (it == m.end())
throw py::key_error();
m.erase(it);
});

cl.def("__len__", &Map::size);

cl.def("_ipython_key_completions_", [](Map &m) {
auto l = py::list();
for (const auto &myPair : m)
l.append(myPair.first);
return l;
});

return cl;
}
} // namespace detail

using PyIterationContainer = Container<Iteration, uint64_t>;
using PyMeshContainer = Container<Mesh>;
using PyPartContainer = Container<ParticleSpecies>;
Expand All @@ -158,19 +65,23 @@ PYBIND11_MAKE_OPAQUE(PyBaseRecordComponentContainer)

void init_Container(py::module &m)
{
::detail::bind_container<PyIterationContainer>(m, "Iteration_Container");
::detail::bind_container<PyMeshContainer>(m, "Mesh_Container");
::detail::bind_container<PyPartContainer>(m, "Particle_Container");
::detail::bind_container<PyPatchContainer>(m, "Particle_Patches_Container");
::detail::bind_container<PyRecordContainer>(m, "Record_Container");
::detail::bind_container<PyPatchRecordContainer>(
::detail::create_and_bind_container<PyIterationContainer>(
m, "Iteration_Container");
::detail::create_and_bind_container<PyMeshContainer>(m, "Mesh_Container");
::detail::create_and_bind_container<PyPartContainer>(
m, "Particle_Container");
::detail::create_and_bind_container<PyPatchContainer>(
m, "Particle_Patches_Container");
::detail::create_and_bind_container<PyRecordContainer>(
m, "Record_Container");
::detail::create_and_bind_container<PyPatchRecordContainer>(
m, "Patch_Record_Container");
::detail::bind_container<PyRecordComponentContainer>(
::detail::create_and_bind_container<PyRecordComponentContainer>(
m, "Record_Component_Container");
::detail::bind_container<PyMeshRecordComponentContainer>(
::detail::create_and_bind_container<PyMeshRecordComponentContainer>(
m, "Mesh_Record_Component_Container");
::detail::bind_container<PyPatchRecordComponentContainer>(
::detail::create_and_bind_container<PyPatchRecordComponentContainer>(
m, "Patch_Record_Component_Container");
::detail::bind_container<PyBaseRecordComponentContainer>(
::detail::create_and_bind_container<PyBaseRecordComponentContainer>(
m, "Base_Record_Component_Container");
}
10 changes: 5 additions & 5 deletions src/binding/python/openPMD.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -91,21 +91,21 @@ PYBIND11_MODULE(openpmd_api_cxx, m)
init_Chunk(m);
init_Container(m);
init_Error(m);
init_BaseRecord(m);
init_Dataset(m);
init_Datatype(m);
init_Helper(m);
init_Iteration(m);
init_IterationEncoding(m);
init_Mesh(m);
init_BaseRecordComponent(m);
init_RecordComponent(m);
init_MeshRecordComponent(m);
init_ParticlePatches(m);
init_PatchRecord(m);
init_PatchRecordComponent(m);
init_ParticleSpecies(m);
init_BaseRecord(m);
init_Mesh(m);
init_Record(m);
init_PatchRecord(m);
init_ParticlePatches(m);
init_ParticleSpecies(m);
init_Series(m);

// API runtime version
Expand Down

0 comments on commit 17ff682

Please sign in to comment.