Skip to content

Conversation

@ThreeMonth03
Copy link
Collaborator

@ThreeMonth03 ThreeMonth03 commented Jan 20, 2026

Problem

In issue #604 , when we assign values from np.complex128 or Complex to SimpleArrayComplex128, the cast of python instance fails.

import modmesh as mm
import numpy as np

nc = np.array([1.0 + 0.1j])
mc = mm.SimpleArrayComplex128([1])
mc[0] = nc[0]          # <- fails

print(mc[0].real, mc[0].imag)
Traceback (most recent call last):
  ...
  mc[0] = nc[0]
RuntimeError: Unable to cast Python instance of type <class 'numpy.complex128'> to C++ type '?' 
(#define PYBIND11_DETAILED_ERROR_MESSAGES or compile in debug mode for details)

Solution

The function __setitem__ in wrap_SimpleArray.cpp calls &property_helper::setitem_parser, and the fuunction calls &property_helper::setitem_parser checks if the value is number based on the following logic:

const bool is_number = py::isinstance<py::bool_>(py_value) || py::isinstance<py::int_>(py_value) || py::isinstance<py::float_>(py_value) || is_complex_v<T>;

This condition ignores whether py_value is np.complex128 or Complex, it just check whether py_value is modmesh.complex128.

@ThreeMonth03 ThreeMonth03 force-pushed the issue604 branch 2 times, most recently from 9d890ea to 7836ffb Compare January 20, 2026 07:39
Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yungyuc @tigercosmos Please review this pull request. Thanks.

Comment on lines 224 to 231
if constexpr (is_complex_v<T>)
{
if (py::isinstance(py_value, complex_class))
{
arr_out.at(key) = py_value.cast<std_complex_t<T>>();
return;
}
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When py_value is Complex or np.complex, casting to std::complex at first.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if/else code is unbalanced and hard to read:

if constexpr (is_complex_v<T>)
{
    if (py::isinstance(py_value, complex_class))
    {
        arr_out.at(key) = py_value.cast<std_complex_t<T>>();
        return;
    }
}
arr_out.at(key) = py_value.cast<T>();
return;

The following code is more concise and maintainable:

if (is_complex_v<T> && py::isinstance(py_value, complex_class))
{
    arr_out.at(key) = py_value.cast<std_complex_t<T>>();
}
else
{
    arr_out.at(key) = py_value.cast<T>();
}

constexpr is unnecessary because py::isinstance(py_value, complex_class) needs to happen during runtime rather than compile time.

Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, the program will return compile error if we don't use if constexpr.

template <typename T>
struct complex_element_type
{
};

template <typename T>
struct complex_element_type<Complex<T>>
{
    using type = T;
};

template <typename T>
using complex_element_type_t = typename complex_element_type<T>::type;

template <typename T>
using std_complex_t = std::complex<complex_element_type_t<T>>;

The following message is compile error.

/home/threemonth03/Downloads/modmesh/cpp/modmesh/buffer/pymod/wrap_SimpleArrayPlex.cpp:325:55:   required from here
/home/threemonth03/Downloads/modmesh/cpp/modmesh/math/Complex.hpp:287:7: error: no type named ‘type’ in ‘struct modmesh::complex_element_type<double>’
In file included from /home/threemonth03/Downloads/modmesh/cpp/modmesh/math/math.hpp:8,
                 from /home/threemonth03/Downloads/modmesh/cpp/modmesh/buffer/SimpleArray.hpp:32,
                 from /home/threemonth03/Downloads/modmesh/cpp/modmesh/buffer/buffer.hpp:38,
                 from /home/threemonth03/Downloads/modmesh/cpp/modmesh/toggle/toggle.hpp:32,
                 from /home/threemonth03/Downloads/modmesh/cpp/modmesh/python/common.hpp:38,
                 from /home/threemonth03/Downloads/modmesh/cpp/modmesh/buffer/pymod/buffer_pymod.hpp:34,
                 from /home/threemonth03/Downloads/modmesh/cpp/modmesh/buffer/pymod/wrap_SimpleArray.cpp:29:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Please figure out why.

Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 Jan 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting. Please figure out why.

The error message is simple, it caused from the missing alias in generic template.

template <typename T>
struct complex_element_type
{
};

However, if we add the alias using type = T; in generic template, it would deduce to a wrong type, so I think I should redesign the template , such that it might look like using convert_to_std_t = something...;.

Comment on lines 272 to 288
template <typename T>
struct complex_element_type
{
};

template <typename T>
struct complex_element_type<Complex<T>>
{
using type = T;
};

template <typename T>
using complex_element_type_t = typename complex_element_type<T>::type;

template <typename T>
using std_complex_t = std::complex<complex_element_type_t<T>>;

Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 Jan 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given the type of complex<T>, return std::complex<T>

Comment on lines +109 to +113
.def("__complex__",
[](const wrapped_type & self)
{
return self.to_std_complex();
})
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add an interface to support casting.

Copy link
Member

@yungyuc yungyuc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good job. Points to address:

  • Balance the if/else to improve readability for the value assignment.
  • Write an informative summary in the commit log.

Comment on lines 224 to 231
if constexpr (is_complex_v<T>)
{
if (py::isinstance(py_value, complex_class))
{
arr_out.at(key) = py_value.cast<std_complex_t<T>>();
return;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The if/else code is unbalanced and hard to read:

if constexpr (is_complex_v<T>)
{
    if (py::isinstance(py_value, complex_class))
    {
        arr_out.at(key) = py_value.cast<std_complex_t<T>>();
        return;
    }
}
arr_out.at(key) = py_value.cast<T>();
return;

The following code is more concise and maintainable:

if (is_complex_v<T> && py::isinstance(py_value, complex_class))
{
    arr_out.at(key) = py_value.cast<std_complex_t<T>>();
}
else
{
    arr_out.at(key) = py_value.cast<T>();
}

constexpr is unnecessary because py::isinstance(py_value, complex_class) needs to happen during runtime rather than compile time.

@yungyuc yungyuc added the array Multi-dimensional array implementation label Jan 20, 2026
Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  • Balance the if/else to improve readability for the value assignment.
  • Write an informative summary in the commit log.

@yungyuc Please review this pull request again. Thanks.

Comment on lines +177 to +191
template <typename T>
struct convert_to_std
{
using type = T;
};

template <typename T>
struct convert_to_std<Complex<T>>
{
using type = std::complex<T>;
};

template <typename T>
using convert_to_std_t = typename convert_to_std<T>::type;

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template could identify not only modmesh::complex<T> but also standard type.

Comment on lines 220 to 242

if (is_complex_v<T> && py::isinstance(py_value, complex_class))
{
arr_out.at(key) = py_value.cast<convert_to_std_t<T>>();
return;
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove if constexpr.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please balance if/else as I mentioned in the previous review:

if (/* ... */)
{
    // ...
}
else
{
    // ...
}

The early return should be factored outside the branch.

Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice that if we replace the branch with the following code, it also could work.

arr_out.at(key) = py_value.cast<convert_to_std_t<T>>();

However, if the input is modmesh::complex<T>, it casts to std::complex<T> at first, then it casts to modmesh::complex<T>. I'm not sure which design is better.

Copy link
Member

@yungyuc yungyuc left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The commit log looks good, but you did not balance the branch.

Comment on lines 220 to 242

if (is_complex_v<T> && py::isinstance(py_value, complex_class))
{
arr_out.at(key) = py_value.cast<convert_to_std_t<T>>();
return;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please balance if/else as I mentioned in the previous review:

if (/* ... */)
{
    // ...
}
else
{
    // ...
}

The early return should be factored outside the branch.

@ThreeMonth03 ThreeMonth03 force-pushed the issue604 branch 3 times, most recently from a9ae9be to a3f3b10 Compare January 22, 2026 10:21
…the argument of `np.complex`

- Check whether `py::object py_value` is `np.complex`.
- If `py::object py_value` is `np.complex`, cast `py::object py_value` to `modmesh::complex<T>`.
Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yungyuc Please review this pr. Thanks.

Comment on lines 220 to 242

if (is_complex_v<T> && py::isinstance(py_value, complex_class))
{
arr_out.at(key) = py_value.cast<convert_to_std_t<T>>();
return;
}
Copy link
Collaborator Author

@ThreeMonth03 ThreeMonth03 Jan 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice that if we replace the branch with the following code, it also could work.

arr_out.at(key) = py_value.cast<convert_to_std_t<T>>();

However, if the input is modmesh::complex<T>, it casts to std::complex<T> at first, then it casts to modmesh::complex<T>. I'm not sure which design is better.

@ThreeMonth03 ThreeMonth03 requested a review from yungyuc January 24, 2026 10:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

array Multi-dimensional array implementation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants