Closed kwabenantim closed 1 year ago
Also add support for zero_vector
and unit_vector
:
Cylindrical2dNodesOnlyMesh::SetUpBoxCollection
has a unit_vector
argumentPeriodicNodesOnlyMesh::SetUpBoxCollection
has a zero_vector
argumentThe existing PYBIND11_CVECTOR_TYPECASTER3
macro can be used to solve this issue. It should be called in any cppwg.cpp
file which contains methods using c_vector<double, 3>
.
E.g, to solve the example described in this issue,
PlaneBoundaryCondition2_2.cppwg.cpp
should include PythonObjectConverters.hpp
and the line
PYBIND11_CVECTOR_TYPECASTER3();
should be added to the file. This will enable automatic conversion between c_vector<double, 3>
and python lists withing this file.
The solution provided in the previous comment only appears to fix some of the failures.
Further investigation shows that the load
method of the typecaster is called and successfully returns true without error. Locating the error message given in the pybind11 source code has led to identifying the cause of failure as a reference_cast_error
being caught at ~line 930 of pybind11.h
.
Using the test script
import os, signal
import numpy as np
import chaste
import chaste.cell_based
import chaste.mesh
from io import StringIO
from wurlitzer import pipes, STDOUT
n = chaste.mesh.Node3(0, [0.0, 0.0, 0.0])
out = StringIO()
with pipes(stdout=out, stderr=out):
try:
n.AddAppliedForceContribution(np.array([0.1, 0.0, 1.0]))
except TypeError:
print("reference error caught")
print(out.getvalue())
The program throws a reference_cast_error
if the second value of the parameter to AddAppliedForceContribution
is 0.0
.
I have investigated the call stack to this point with gdb, and for both zero and non-zero values, the types involved are identical. The only difference is that in the non-zero ones, the base type_caster_generic::value
has been set. The next step is to investigate where & why this is/isn't set.
GDB trace for non-zero value given below:
942 result = func.impl(call);
(gdb) s
0x00007fffeeb87485 in pybind11::cpp_function::initialize<pybind11::cpp_function::initialize<void, Node<3u>, boost::numeric::ublas::c_vector<double, 3ul> const&, pybind11::name, pybind11::is_method, pybind11::sibling, char [2], pybind11::arg>(void (Node<3u>::*)(boost::numeric::ublas::c_vector<double, 3ul> const&), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&, char const (&) [2], pybind11::arg const&)::{lambda(Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&)#1}, void, Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&, pybind11::name, pybind11::is_method, pybind11::sibling, char [2], pybind11::arg>(pybind11::cpp_function::initialize<void, Node<3u>, boost::numeric::ublas::c_vector<double, 3ul> const&, pybind11::name, pybind11::is_method, pybind11::sibling, char [2], pybind11::arg>(void (Node<3u>::*)(boost::numeric::ublas::c_vector<double, 3ul> const&), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&, char const (&) [2], pybind11::arg const&)::{lambda(Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&)#1}&&, void (*)(Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&, char const (&) [2], pybind11::arg const&)::{lambda(pybind11::detail::function_call&)#3}::_FUN(pybind11::detail::function_call&) () at /atlas/RSE/CHASTE/PyChaste/dynamic/pybind11/include/pybind11/pybind11.h:224
224 rec->impl = [](function_call &call) -> handle {
(gdb) s
0x00007fffeeb86d1b in pybind11::cpp_function::initialize<pybind11::cpp_function::initialize<void, Node<3u>, boost::numeric::ublas::c_vector<double, 3ul> const&, pybind11::name, pybind11::is_method, pybind11::sibling, char [2], pybind11::arg>(void (Node<3u>::*)(boost::numeric::ublas::c_vector<double, 3ul> const&), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&, char const (&) [2], pybind11::arg const&)::{lambda(Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&)#1}, void, Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&, pybind11::name, pybind11::is_method, pybind11::sibling, char [2], pybind11::arg>(pybind11::cpp_function::initialize<void, Node<3u>, boost::numeric::ublas::c_vector<double, 3ul> const&, pybind11::name, pybind11::is_method, pybind11::sibling, char [2], pybind11::arg>(void (Node<3u>::*)(boost::numeric::ublas::c_vector<double, 3ul> const&), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&, char const (&) [2], pybind11::arg const&)::{lambda(Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&)#1}&&, void (*)(Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&, char const (&) [2], pybind11::arg const&)::{lambda(pybind11::detail::function_call&)#3}::operator()(pybind11::detail::function_call&) const (__closure=__closure@entry=0x0, call=...) at /atlas/RSE/CHASTE/PyChaste/dynamic/pybind11/include/pybind11/pybind11.h:224
224 rec->impl = [](function_call &call) -> handle {
(gdb) s
225 cast_in args_converter;
(gdb) n
227 printf("Constructed cast_in\n");
(gdb) n
230 if (!args_converter.load_args(call)) {
(gdb) n
235 printf("Load args succeeded\n");
(gdb) n
238 process_attributes<Extra...>::precall(call);
(gdb) n
243 printf("Got data pointer\n");
(gdb) n
245 printf("Got capture pointer\n");
(gdb) n
250 printf("Set return value policy\n");
(gdb) n
257 = cast_out::cast(std::move(args_converter).template call<Return, Guard>(cap->f),
(gdb) s
pybind11::detail::argument_loader<Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&>::call<void, pybind11::detail::void_type, pybind11::cpp_function::cpp_function<void, Node<3u>, boost::numeric::ublas::c_vector<double, 3ul> const&, pybind11::name, pybind11::is_method, pybind11::sibling, char [2], pybind11::arg>(void (Node<3u>::*)(boost::numeric::ublas::c_vector<double, 3ul> const&), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&, char const (&) [2], pybind11::arg const&)::{lambda(Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&)#1}&>(pybind11::cpp_function::cpp_function<void, Node<3u>, boost::numeric::ublas::c_vector<double, 3ul> const&, pybind11::name, pybind11::is_method, pybind11::sibling, char [2], pybind11::arg>(void (Node<3u>::*)(boost::numeric::ublas::c_vector<double, 3ul> const&), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&, char const (&) [2], pybind11::arg const&)::{lambda(Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&)#1}&) && (f=..., this=0x7fffffffd370) at /atlas/RSE/CHASTE/PyChaste/dynamic/pybind11/include/pybind11/cast.h:1417
1417 std::move(*this).template call_impl<remove_cv_t<Return>>(
(gdb) s
pybind11::detail::argument_loader<Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&>::call_impl<void, pybind11::cpp_function::cpp_function<void, Node<3u>, boost::numeric::ublas::c_vector<double, 3ul> const&, pybind11::name, pybind11::is_method, pybind11::sibling, char [2], pybind11::arg>(void (Node<3u>::*)(boost::numeric::ublas::c_vector<double, 3ul> const&), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&, char const (&) [2], pybind11::arg const&)::{lambda(Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&)#1}&, 0ul, 1ul, pybind11::detail::void_type>(pybind11::cpp_function::cpp_function<void, Node<3u>, boost::numeric::ublas::c_vector<double, 3ul> const&, pybind11::name, pybind11::is_method, pybind11::sibling, char [2], pybind11::arg>(void (Node<3u>::*)(boost::numeric::ublas::c_vector<double, 3ul> const&), pybind11::name const&, pybind11::is_method const&, pybind11::sibling const&, char const (&) [2], pybind11::arg const&)::{lambda(Node<3u>*, boost::numeric::ublas::c_vector<double, 3ul> const&)#1}&, std::integer_sequence<unsigned long, 0ul, 1ul>, pybind11::detail::void_type&&) && (f=..., this=0x7fffffffd370) at /atlas/RSE/CHASTE/PyChaste/dynamic/pybind11/include/pybind11/cast.h:1442
1442 Return call_impl(Func &&f, index_sequence<Is...>, Guard &&) && {
(gdb) s
1443 printf("In call_impl\n");
(gdb) n
233 _M_head(_Head_base& __b) noexcept { return __b._M_head_impl; }
(gdb) s
pybind11::detail::cast_op<boost::numeric::ublas::c_vector<double, 3ul> const&> (caster=...) at /atlas/RSE/CHASTE/PyChaste/dynamic/pybind11/include/pybind11/cast.h:48
48 printf("Using cast_op add rvalue reference on arg\n");
(gdb) print caster
$1 = (pybind11::detail::make_caster &&) @0x7fffffffd370: {<pybind11::detail::type_caster_base<boost::numeric::ublas::c_vector<double, 3> >> = {<pybind11::detail::type_caster_generic> = {typeinfo = 0x3,
cpptype = 0x3fb999999999999a, value = 0x3fb999999999999a}, static name = {text = "%"}}, <No data fields>}
(gdb) s
printf (__fmt=0x7fffeecda358 "Using cast_op add rvalue reference on arg\n") at /usr/include/x86_64-linux-gnu/bits/stdio2.h:112
112 return __printf_chk (__USE_FORTIFY_LEVEL - 1, __fmt, __va_arg_pack ());
(gdb) n
pybind11::detail::cast_op<boost::numeric::ublas::c_vector<double, 3ul> const&> (caster=...) at /atlas/RSE/CHASTE/PyChaste/dynamic/pybind11/include/pybind11/cast.h:50
50 template cast_op_type<typename std::add_rvalue_reference<T>::type>();
(gdb) s
pybind11::detail::type_caster_base<boost::numeric::ublas::c_vector<double, 3ul> >::operator boost::numeric::ublas::c_vector<double, 3ul>& (this=0x7fffffffd370)
at /atlas/RSE/CHASTE/PyChaste/dynamic/pybind11/include/pybind11/detail/type_caster_base.h:978
978 operator itype &() {
(gdb) print value
$2 = (void *) 0x3fb999999999999a
(gdb)
Possible Workaround
class PlaneBoundaryCondition2_2Factory
{
public:
static PlaneBoundaryCondition2_2 create(AbstractCellPopulation<2, 2> * pCellPopulation, py::array_t<double> point_array, py::array_t<double> normal_array)
{
c_vector<double, 2> point_c_vector;
c_vector<double, 2> normal_c_vector;
// Do conversion from point_array to point_c_vector
// Do conversion from normal_array to normal_c_vector
return PlaneBoundaryCondition2_2(pCellPopulation, point_c_vector, normal_c_vector);
}
};
...
py::class_< PlaneBoundaryCondition2_2 >(m, "PlaneBoundaryCondition2_2")
.def(py::init(&PlaneBoundaryCondition2_2Factory::create))
Simplified example with typecaster
A simplified example seems to work fine with the current typecaster:
#include <pybind11/numpy.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <boost/numeric/ublas/vector.hpp>
#include <memory>
#include <string>
#include <vector>
using boost::numeric::ublas::c_vector;
// Typecaster for c_vector<double, 2>
namespace pybind11
{
namespace detail
{
template <>
struct type_caster<c_vector<double, 2>>
{
public:
typedef c_vector<double, 2> c_vector_double_2_t;
PYBIND11_TYPE_CASTER(c_vector_double_2_t, const_name("c_vector_double_2_t"));
bool load(handle src, bool convert)
{
if (!convert && !array_t<double>::check_(src)) { return false; }
auto buf = array_t<double, array::c_style | array::forcecast>::ensure(src);
if (!buf) { return false; }
if (buf.ndim() != 1 or buf.shape()[0] != 2) { return false; }
value.resize(2);
for (int i = 0; i < 2; i++)
{
value[i] = buf.data()[i];
}
return true;
}
static handle cast(const c_vector<double, 2> &src, return_value_policy policy, handle parent)
{
std::vector<size_t> shape(1, 2);
std::vector<size_t> strides(1, sizeof(double));
double *data = src.size() ? const_cast<double *>(&src[0]) : static_cast<double *>(NULL);
array a(std::move(shape), std::move(strides), data);
return a.release();
}
};
}
}
// Simplified CellPopulation
template <unsigned SPACE_DIM>
class CellPopulation
{
public:
unsigned getDim() { return SPACE_DIM; }
};
template class CellPopulation<2>;
// Simplified BoundaryCondition
template <unsigned ELEMENT_DIM, unsigned SPACE_DIM = ELEMENT_DIM>
class BoundaryCondition
{
public:
BoundaryCondition(CellPopulation<SPACE_DIM> *population, c_vector<double, 2> point, c_vector<double, 2> normal)
{
_total = point[0] + point[1] + normal[0] + normal[1] + population->getDim();
}
double getTotal() { return _total; }
private:
double _total;
};
template class BoundaryCondition<2, 2>;
// Pybind11 Bindings
namespace py = pybind11;
PYBIND11_MODULE(example, m)
{
py::class_<CellPopulation<2>>(m, "CellPopulation2")
.def(py::init<>())
.def("getDim", &CellPopulation<2>::getDim);
py::class_<BoundaryCondition<2, 2>>(m, "BoundaryCondition2_2")
.def(py::init<CellPopulation<2> *, c_vector<double, 2>, c_vector<double, 2>>())
.def("getTotal", &BoundaryCondition<2, 2>::getTotal);
}
Python:
import numpy as np
from example import CellPopulation2, BoundaryCondition2_2
pop = CellPopulation2()
print(pop.getDim())
point = np.array([0.0,0.0])
normal = np.array([0.1,0.0])
bc = BoundaryCondition2_2(pop, point, normal)
print(bc.getTotal())
Passing Tests
TestVertexBasedCellSimulationsPythonTutorial Runs into a segmentation fault.
Error
[0]PETSC ERROR: ------------------------------------------------------------------------
[0]PETSC ERROR: Caught signal number 11 SEGV: Segmentation Violation, probably memory access out of range
[0]PETSC ERROR: Try option -start_in_debugger or -on_error_attach_debugger
[0]PETSC ERROR: or see https://petsc.org/release/faq/#valgrind
[0]PETSC ERROR: or try http://valgrind.org on GNU/linux and Apple Mac OS X to find memory corruption errors
[0]PETSC ERROR: configure using --with-debugging=yes, recompile, link, and run
[0]PETSC ERROR: to get more information on the crash.
[0]PETSC ERROR: Run with -malloc_debug to check if memory corruption is causing the crash.
--------------------------------------------------------------------------
MPI_ABORT was invoked on rank 0 in communicator MPI_COMM_WORLD
with errorcode 59.
NOTE: invoking MPI_ABORT causes Open MPI to kill all MPI processes.
You may or may not see output from other processes, depending on
exactly when Open MPI kills them.
--------------------------------------------------------------------------
Trace
OffLatticeSimulation::Solve(...);
OffLatticeSimulation::ApplyBoundaries(...);
PlaneBoundaryCondition::ImposeBoundaryCondition(...);
if (dynamic_cast<AbstractOffLatticeCellPopulation<ELEMENT_DIM, SPACE_DIM>*>(this->mpCellPopulation) == nullptr){...}
TestPetscToolsPython
Needs wrapping for PETSc Vec
and Mat
.
Error
======================================================================
ERROR: test_some_vecs (__main__.TestPetscTools)
----------------------------------------------------------------------
TypeError: Unregistered type : _p_Vec
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/home/vboxuser/PyChaste/test/python/./core/TestPetscToolsPython.py", line 44, in test_some_vecs
vec = chaste.core.PetscTools.CreateVec(local)
TypeError: Unable to convert function return value to a Python type! The signature was
(data: List[float]) -> _p_Vec
Closing this issue as the remaining failing tests are unrelated to c_vectors
. The remaining problems will be addressed by #15
It appears that the errors in constructors which contained c_vector
s was not due to the c_vector
typecasters, which seem to work fine everywhere else. Incidentally, a pointer to an AbstractOffLatticeCellPopulation
was required in all the constructors where the c_vector
typecasters appeared to be failing. The actual problem was with casting a VertexBasedCellPopulation
to an AbstractOffLatticeCellPopulation
. This was the same problem raised by TestVertexBasedCellSimulationsPythonTutorial
and now seems to have been solved. It is not yet clear what changed, but the errors stopped after switching from C++14 to C++17 and fetching latest upstream updates from Chaste develop (fast-forwarded from e1fa6a6 to 3ed690e).
Describe the bug See PyChaste/issues/4
To Reproduce Install PyChaste from conda:
In Python:
Error