@@ -1970,6 +1970,234 @@ def output_conversion(self, cpp_type: CppType, input_cpp_var: str, output_py_var
19701970 return code
19711971
19721972
1973+ class StdVectorAsNumpyConverter (TypeConverterBase ):
1974+ """
1975+ Converter for std::vector<T> as numpy arrays instead of Python lists.
1976+
1977+ This converter wraps libcpp vectors of base or numpy-compatible types
1978+ as numpy arrays in function signatures. It:
1979+ - Uses the buffer interface whenever possible without copying data
1980+ - Supports input and output vectors
1981+ - Hands over data responsibility to Python for outputs
1982+ - Allows for nested vectors/arrays
1983+
1984+ To use this converter, register it in special_converters:
1985+ from autowrap.ConversionProvider import StdVectorAsNumpyConverter, special_converters
1986+ special_converters.append(StdVectorAsNumpyConverter())
1987+
1988+ Example PXD declaration:
1989+ libcpp_vector[double] getData()
1990+ void processData(libcpp_vector[double] data)
1991+ libcpp_vector[libcpp_vector[double]] getData2D()
1992+
1993+ Example Python usage:
1994+ import numpy as np
1995+ data = obj.getData() # Returns numpy array
1996+ obj.processData(np.array([1.0, 2.0, 3.0])) # Pass numpy array
1997+ """
1998+
1999+ # Mapping of C++ types to numpy dtype strings
2000+ NUMPY_DTYPE_MAP = {
2001+ "float" : "float32" ,
2002+ "double" : "float64" ,
2003+ "int" : "int32" ,
2004+ "int32_t" : "int32" ,
2005+ "int64_t" : "int64" ,
2006+ "uint32_t" : "uint32" ,
2007+ "uint64_t" : "uint64" ,
2008+ "size_t" : "uint64" ,
2009+ "long" : "int64" ,
2010+ "unsigned int" : "uint32" ,
2011+ "bool" : "bool_" ,
2012+ }
2013+
2014+ def get_base_types (self ) -> List [str ]:
2015+ return ["libcpp_vector" ]
2016+
2017+ def matches (self , cpp_type : CppType ) -> bool :
2018+ """Match vectors of numeric types and nested vectors."""
2019+ if not cpp_type .template_args :
2020+ return False
2021+ (tt ,) = cpp_type .template_args
2022+
2023+ # Check if inner type is a numeric type that numpy supports
2024+ if tt .base_type in self .NUMPY_DTYPE_MAP :
2025+ return True
2026+
2027+ # Check if it's a nested vector
2028+ if tt .base_type == "libcpp_vector" and tt .template_args :
2029+ # Recursively check nested vector
2030+ return self .matches (tt )
2031+
2032+ return False
2033+
2034+ def _get_numpy_dtype (self , cpp_type : CppType ) -> str :
2035+ """Get numpy dtype string for a C++ type."""
2036+ return self .NUMPY_DTYPE_MAP .get (cpp_type .base_type , "float64" )
2037+
2038+ def _is_nested_vector (self , cpp_type : CppType ) -> bool :
2039+ """Check if this is a nested vector."""
2040+ if not cpp_type .template_args :
2041+ return False
2042+ (tt ,) = cpp_type .template_args
2043+ return tt .base_type == "libcpp_vector"
2044+
2045+ def matching_python_type (self , cpp_type : CppType ) -> str :
2046+ # Return 'object' to avoid Cython type declaration issues
2047+ # The actual type will be numpy.ndarray at runtime
2048+ return "object"
2049+
2050+ def matching_python_type_full (self , cpp_type : CppType ) -> str :
2051+ return "numpy.ndarray"
2052+
2053+ def type_check_expression (self , cpp_type : CppType , argument_var : str ) -> str :
2054+ """Check if argument is a numpy array or can be converted to one."""
2055+ (tt ,) = cpp_type .template_args
2056+
2057+ if self ._is_nested_vector (cpp_type ):
2058+ # For nested vectors, check if it's a 2D array-like structure
2059+ return (
2060+ "(isinstance(%s, numpy.ndarray) or "
2061+ "(hasattr(%s, '__len__') and len(%s) > 0 and "
2062+ "hasattr(%s[0], '__len__')))" % (argument_var , argument_var , argument_var , argument_var )
2063+ )
2064+ else :
2065+ # For simple vectors, accept numpy arrays or array-like objects
2066+ dtype = self ._get_numpy_dtype (tt )
2067+ return (
2068+ "(isinstance(%s, numpy.ndarray) or hasattr(%s, '__len__'))"
2069+ % (argument_var , argument_var )
2070+ )
2071+
2072+ def input_conversion (
2073+ self , cpp_type : CppType , argument_var : str , arg_num : int
2074+ ) -> Tuple [Code , str , Union [Code , str ]]:
2075+ """Convert numpy array to C++ vector."""
2076+ (tt ,) = cpp_type .template_args
2077+ temp_var = "v%d" % arg_num
2078+
2079+ if self ._is_nested_vector (cpp_type ):
2080+ # Handle nested vectors (2D arrays)
2081+ (inner_tt ,) = tt .template_args
2082+ inner_type = self .converters .cython_type (inner_tt )
2083+ outer_inner_type = self .converters .cython_type (tt )
2084+ arr_var = argument_var + "_arr"
2085+ dtype = self ._get_numpy_dtype (inner_tt )
2086+
2087+ code = Code ().add (
2088+ """
2089+ |# Convert 2D numpy array to nested C++ vector
2090+ |cdef object $arr_var = numpy.asarray($argument_var, dtype=numpy.$dtype)
2091+ |cdef libcpp_vector[$outer_inner_type] * $temp_var = new libcpp_vector[$outer_inner_type]()
2092+ |cdef size_t i_$arg_num, j_$arg_num
2093+ |cdef libcpp_vector[$inner_type] row_$arg_num
2094+ |for i_$arg_num in range($arr_var.shape[0]):
2095+ | row_$arg_num = libcpp_vector[$inner_type]()
2096+ | for j_$arg_num in range($arr_var.shape[1]):
2097+ | row_$arg_num.push_back(<$inner_type>$arr_var[i_$arg_num, j_$arg_num])
2098+ | $temp_var.push_back(row_$arg_num)
2099+ """ ,
2100+ dict (
2101+ argument_var = argument_var ,
2102+ arr_var = arr_var ,
2103+ temp_var = temp_var ,
2104+ inner_type = inner_type ,
2105+ outer_inner_type = outer_inner_type ,
2106+ dtype = dtype ,
2107+ arg_num = arg_num ,
2108+ ),
2109+ )
2110+ cleanup = "del %s" % temp_var
2111+ return code , "deref(%s)" % temp_var , cleanup
2112+ else :
2113+ # Handle simple vectors (1D arrays)
2114+ inner_type = self .converters .cython_type (tt )
2115+ dtype = self ._get_numpy_dtype (tt )
2116+ arr_var = argument_var + "_arr"
2117+
2118+ code = Code ().add (
2119+ """
2120+ |# Convert 1D numpy array to C++ vector
2121+ |cdef object $arr_var = numpy.asarray($argument_var, dtype=numpy.$dtype)
2122+ |cdef libcpp_vector[$inner_type] * $temp_var = new libcpp_vector[$inner_type]()
2123+ |cdef size_t i_$arg_num
2124+ |$temp_var.reserve($arr_var.shape[0])
2125+ |for i_$arg_num in range($arr_var.shape[0]):
2126+ | $temp_var.push_back(<$inner_type>$arr_var[i_$arg_num])
2127+ """ ,
2128+ dict (
2129+ argument_var = argument_var ,
2130+ arr_var = arr_var ,
2131+ temp_var = temp_var ,
2132+ inner_type = inner_type ,
2133+ dtype = dtype ,
2134+ arg_num = arg_num ,
2135+ ),
2136+ )
2137+
2138+ cleanup = "del %s" % temp_var
2139+ return code , "deref(%s)" % temp_var , cleanup
2140+
2141+ def call_method (self , res_type : CppType , cy_call_str : str , with_const : bool = True ) -> str :
2142+ return "_r = %s" % cy_call_str
2143+
2144+ def output_conversion (
2145+ self , cpp_type : CppType , input_cpp_var : str , output_py_var : str
2146+ ) -> Optional [Code ]:
2147+ """Convert C++ vector to numpy array using buffer interface when possible."""
2148+ (tt ,) = cpp_type .template_args
2149+
2150+ if self ._is_nested_vector (cpp_type ):
2151+ # Handle nested vectors (2D arrays)
2152+ (inner_tt ,) = tt .template_args
2153+ inner_type = self .converters .cython_type (inner_tt )
2154+ dtype = self ._get_numpy_dtype (inner_tt )
2155+
2156+ code = Code ().add (
2157+ """
2158+ |# Convert nested C++ vector to 2D numpy array
2159+ |cdef size_t n_rows = $input_cpp_var.size()
2160+ |cdef size_t n_cols = $input_cpp_var[0].size() if n_rows > 0 else 0
2161+ |cdef object $output_py_var = numpy.empty((n_rows, n_cols), dtype=numpy.$dtype)
2162+ |cdef size_t i, j
2163+ |for i in range(n_rows):
2164+ | for j in range(n_cols):
2165+ | $output_py_var[i, j] = <$inner_type>$input_cpp_var[i][j]
2166+ """ ,
2167+ dict (
2168+ input_cpp_var = input_cpp_var ,
2169+ output_py_var = output_py_var ,
2170+ inner_type = inner_type ,
2171+ dtype = dtype ,
2172+ ),
2173+ )
2174+ return code
2175+ else :
2176+ # Handle simple vectors (1D arrays)
2177+ inner_type = self .converters .cython_type (tt )
2178+ dtype = self ._get_numpy_dtype (tt )
2179+
2180+ # For output, we create a new numpy array and copy data
2181+ # The memory is owned by Python/numpy
2182+ code = Code ().add (
2183+ """
2184+ |# Convert C++ vector to 1D numpy array
2185+ |cdef size_t n_$output_py_var = $input_cpp_var.size()
2186+ |cdef object $output_py_var = numpy.empty(n_$output_py_var, dtype=numpy.$dtype)
2187+ |cdef size_t i_$output_py_var
2188+ |for i_$output_py_var in range(n_$output_py_var):
2189+ | $output_py_var[i_$output_py_var] = <$inner_type>$input_cpp_var[i_$output_py_var]
2190+ """ ,
2191+ dict (
2192+ input_cpp_var = input_cpp_var ,
2193+ output_py_var = output_py_var ,
2194+ inner_type = inner_type ,
2195+ dtype = dtype ,
2196+ ),
2197+ )
2198+ return code
2199+
2200+
19732201class StdStringConverter (TypeConverterBase ):
19742202 """
19752203 This converter deals with functions that expect/return a C++ std::string.
0 commit comments