Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add complex array support #468

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open

Conversation

j8xixo12
Copy link
Collaborator

@j8xixo12 j8xixo12 commented Feb 3, 2025

I have added complex array with SimpleArray, the complex array will be used for fourier transform.

@j8xixo12
Copy link
Collaborator Author

j8xixo12 commented Feb 3, 2025

This PR is still under development, I have problem about SimpleArray convert to numpy array.

cplx = mmComplexFloat64(1.0, 2.0)
sarr = mm.SimpleArrayComplexFloat64(10)
sarr.fill(cplx)
ndarr = np.array(sarr, copy=False, dtype=mm.ComplexFloat64.dtype())   <--- after this line, the ndarr element would treat as real = (1.0, 20) imag = 0.0 not real = 1.0, imag = 2.0.
sarr = mm.SimpleArrayComplexFloat64(array=ndarr)  <--- numpy array to SimpleArray conversion is working well.

@yungyuc @tigercosmos Do you have any idea?
I guess I must be doing something wrong with the buffer protocol.

@@ -78,7 +78,7 @@ endif()

include(Flake8)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because I have used designate initialiser, this feature is supported by c++20, therefore change it to c++ standard 20.

Copy link
Member

Choose a reason for hiding this comment

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

Is there a reason that you have to use designated initializer? Please let us know what it is.

If there is not, then just don't use designated initializer. I have no problem using it, but it alone is not a good reason to upgrade to C++20.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I just want to mark the initial value for each structure member during structure initialization, but I did not consider what side effects upgrading to C++20 may bring.

Comment on lines +165 to +172
if constexpr (is_complex_v<value_type>)
{
initial = value_type();
}
else
{
initial = 0;
}
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 the element type is complex, different initialization should be performed here.

Comment on lines +75 to +84
constexpr bool valid_conversion = (!is_complex_v<T> && !is_complex_v<D>) || (is_complex_v<T> && is_complex_v<D> && std::is_same_v<T, D>);

if constexpr (valid_conversion)
{
arr_out.at(offset_out) = static_cast<out_type>(*ptr_in);
}
else
{
throw std::runtime_error("Cannot convert between complex and non-complex types");
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Currently, only support float complex to float complex and double complex to double complex conversion.

Comment on lines +143 to +153
template <typename T>
bool operator<(const Complex<T> & lhs, const Complex<T> & rhs)
{
return lhs.norm() < rhs.norm();
}

template <typename T>
bool operator>(const Complex<T> & lhs, const Complex<T> & rhs)
{
return lhs.norm() > rhs.norm();
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

These comparison operator would be used in SimpleArray::min(), SimpleArray::max().

Copy link
Collaborator

Choose a reason for hiding this comment

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

I think this is worth being a code comment.

Copy link
Member

Choose a reason for hiding this comment

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

Agree

Copy link
Collaborator Author

@j8xixo12 j8xixo12 Feb 4, 2025

Choose a reason for hiding this comment

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

I will leave a code comment for it.

Comment on lines +89 to +91
.def("dtype",
[]()
{ return py::dtype::of<wrapped_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.

Add dtype getter is convenient for user to assign numpy dtype while initial numpy arrary.

@@ -65,7 +66,9 @@ static std::unordered_map<std::string, DataType, DataTypeHasher> string_data_typ
{"uint32", DataType::Uint32},
{"uint64", DataType::Uint64},
{"float32", DataType::Float32},
{"float64", DataType::Float64}};
{"float64", DataType::Float64},
{"ComplexFloat32", DataType::ComplexFloat32},
Copy link
Collaborator

@tigercosmos tigercosmos Feb 3, 2025

Choose a reason for hiding this comment

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

I would just call it "complex32".

Copy link
Member

Choose a reason for hiding this comment

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

I like the idea to drop Float from the complex type name, but we should use the correct bit count. For a complex number using two 32-bit float, it should be called complex64. A complex using two 64-bit float, it should be called complex128.

It follows numpy convention: https://numpy.org/doc/stable/user/basics.types.html#relationship-between-numpy-data-types-and-c-data-types

@@ -14,9 +14,9 @@ class ParsevalTest : public ::testing::Test
const size_t VN = 1024;

modmesh::SimpleArray<modmesh::Complex<T>> signal{
modmesh::small_vector<size_t>{VN}, modmesh::Complex<T>(0.0, 0.0)};
modmesh::small_vector<size_t>{VN}, modmesh::Complex<T>{.real_v = 0.0, .imag_v = 0.0}};
Copy link
Collaborator

Choose a reason for hiding this comment

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

I personally don't like designated initializers

Copy link
Member

Choose a reason for hiding this comment

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

The designated initializer does not seem to be necessary.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I will remove designated initializer.

@tigercosmos
Copy link
Collaborator

regarding ndarr = np.array(sarr, copy=False, dtype=mm.ComplexFloat64.dtype()) error, it sounds like a python binding issue.

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.

  • Stay with C++17 if possible.
  • Name the complex data type as complex64 and complex128, or Complex64 and Complex128, if capitalization is desired.

@@ -65,7 +66,9 @@ static std::unordered_map<std::string, DataType, DataTypeHasher> string_data_typ
{"uint32", DataType::Uint32},
{"uint64", DataType::Uint64},
{"float32", DataType::Float32},
{"float64", DataType::Float64}};
{"float64", DataType::Float64},
{"ComplexFloat32", DataType::ComplexFloat32},
Copy link
Member

Choose a reason for hiding this comment

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

I like the idea to drop Float from the complex type name, but we should use the correct bit count. For a complex number using two 32-bit float, it should be called complex64. A complex using two 64-bit float, it should be called complex128.

It follows numpy convention: https://numpy.org/doc/stable/user/basics.types.html#relationship-between-numpy-data-types-and-c-data-types

{
if (!pybind11::isinstance<Complex<float>>(value))
{
throw pybind11::type_error("Data type mismatch, expected complex float");
Copy link
Member

Choose a reason for hiding this comment

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

The error message should use the type name.

Comment on lines +143 to +153
template <typename T>
bool operator<(const Complex<T> & lhs, const Complex<T> & rhs)
{
return lhs.norm() < rhs.norm();
}

template <typename T>
bool operator>(const Complex<T> & lhs, const Complex<T> & rhs)
{
return lhs.norm() > rhs.norm();
}
Copy link
Member

Choose a reason for hiding this comment

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

Agree

@@ -14,9 +14,9 @@ class ParsevalTest : public ::testing::Test
const size_t VN = 1024;

modmesh::SimpleArray<modmesh::Complex<T>> signal{
modmesh::small_vector<size_t>{VN}, modmesh::Complex<T>(0.0, 0.0)};
modmesh::small_vector<size_t>{VN}, modmesh::Complex<T>{.real_v = 0.0, .imag_v = 0.0}};
Copy link
Member

Choose a reason for hiding this comment

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

The designated initializer does not seem to be necessary.

@@ -40,50 +41,68 @@ struct ComplexImpl
T real_v;
Copy link
Member

Choose a reason for hiding this comment

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

What is the rationale to append the member name with _v?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Because ComplexImpl has member function real() and imag(), so I added the _v to avoid confusion between function name and variable name.

@yungyuc yungyuc added the array Multi-dimensional array implementation label Feb 3, 2025
@tigercosmos
Copy link
Collaborator

Btw, can you check if sarr = mm.SimpleArray(..., dtype="complex32") works?

@j8xixo12
Copy link
Collaborator Author

j8xixo12 commented Feb 4, 2025

Btw, can you check if sarr = mm.SimpleArray(..., dtype="complex32") works?

No, it doesn't work.

>>> sarr = mm.SimpleArray(10, dtype="complex64")
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: Unsupported datatype

The reason is complex64 can not be found in DataType.

And sarr = mm.SimpleArray(10, dtype="ComplexFloat64") is work.

>>> sarr = mm.SimpleArray(10, dtype="ComplexFloat64")
>>> sarr[1]
<_modmesh.ComplexFloat64 object at 0x1392238b0>
>>> sarr[1].real
0.0
>>> sarr[1].imag
0.0

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.

3 participants