diff --git a/build/helper/metadata_add_all.py b/build/helper/metadata_add_all.py index a9e2f0a0d..0ff740cd3 100644 --- a/build/helper/metadata_add_all.py +++ b/build/helper/metadata_add_all.py @@ -132,6 +132,12 @@ def _add_complex_type(parameter): parameter['complex_type'] = None +def _add_array_dimension(parameter): + '''Adds a array_dimension parameter to the metadata for multi dimensional arrays''' + if 'array_dimension' not in parameter: + parameter['array_dimension'] = 1 + + def _add_numpy_info(parameter, parameters, config): '''Adds the following numpy-related information: @@ -457,6 +463,7 @@ def add_all_function_metadata(functions, config): _add_ctypes_variable_name(p) _add_ctypes_type(p, config) _add_complex_type(p) + _add_array_dimension(p) _add_numpy_info(p, functions[f]['parameters'], config) _add_default_value_name(p) _add_default_value_name_for_docs(p, config['module_name']) diff --git a/build/templates/_library_interpreter.py.mako b/build/templates/_library_interpreter.py.mako index 24e5e12a4..f3eda2e79 100644 --- a/build/templates/_library_interpreter.py.mako +++ b/build/templates/_library_interpreter.py.mako @@ -53,8 +53,12 @@ def _get_ctypes_pointer_for_buffer(value=None, library_type=None, size=None): % if are_complex_parameters_used: if library_type in (_complextype.NIComplexI16, _complextype.NIComplexNumberF32, _complextype.NIComplexNumber): complex_dtype = numpy.dtype(library_type) - structured_array = value.view(complex_dtype) - return structured_array.ctypes.data_as(ctypes.POINTER(library_type)) + if value.ndim > 1: + # we create a flattened view of the multi-dimensional numpy array + restructured_array_view = value.ravel().view(complex_dtype) + else: + restructured_array_view = value.view(complex_dtype) + return restructured_array_view.ctypes.data_as(ctypes.POINTER(library_type)) else: return numpy.ctypeslib.as_ctypes(value) % else: diff --git a/build/templates/session.py/numpy_write_method.py.mako b/build/templates/session.py/numpy_write_method.py.mako index 8da38befe..36fa97aad 100644 --- a/build/templates/session.py/numpy_write_method.py.mako +++ b/build/templates/session.py/numpy_write_method.py.mako @@ -30,6 +30,8 @@ raise TypeError('${parameter['python_name']} must be in C-order') if ${parameter['python_name']}.dtype is not numpy.dtype('${parameter['numpy_type']}'): raise TypeError('${parameter['python_name']} must be numpy.ndarray of dtype=${parameter['numpy_type']}, is ' + str(${parameter['python_name']}.dtype)) + if ${parameter['python_name']}.ndim != ${parameter['array_dimension']}: + raise TypeError('${parameter['python_name']} must be numpy.ndarray of dimension=${parameter['array_dimension']}, is ' + str(${parameter['python_name']}.ndim)) % endfor % for p in helper.filter_parameters(parameters, helper.ParameterUsageOptions.INTERPRETER_METHOD_CALL): % if 'python_api_converter_name' in p: diff --git a/build/unit_tests/test_metadata_add_all.py b/build/unit_tests/test_metadata_add_all.py index f32828c64..b927cc226 100644 --- a/build/unit_tests/test_metadata_add_all.py +++ b/build/unit_tests/test_metadata_add_all.py @@ -260,6 +260,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'vi', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'name_ctype', @@ -294,6 +295,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'name', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'pin_data_buffer_size_ctype', @@ -331,6 +333,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': False, 'python_name_or_default_for_init': 'pin_data_buffer_size', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'python_code_input_ctype', @@ -368,6 +371,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'python_code_input', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'None if actual_num_pin_data_ctype is None else (ctypes.pointer(actual_num_pin_data_ctype))', @@ -405,6 +409,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': False, 'python_name_or_default_for_init': 'actual_num_pin_data', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'expected_pin_states_ctype', @@ -444,6 +449,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'expected_pin_states', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'custom_type_input_ctype', @@ -481,6 +487,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'custom_type_input', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'None if custom_type_output_ctype is None else (ctypes.pointer(custom_type_output_ctype))', @@ -518,6 +525,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'custom_type_output', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'custom_type_without_struct_prefix_input_ctype', @@ -555,6 +563,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'custom_type_without_struct_prefix_input', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'None if custom_type_without_struct_prefix_output_ctype is None else (ctypes.pointer(custom_type_without_struct_prefix_output_ctype))', @@ -592,6 +601,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'custom_type_without_struct_prefix_output', 'complex_type': None, + 'array_dimension': 1, }, ], 'python_name': 'make_a_foo', @@ -640,6 +650,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'vi', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'status_ctype', @@ -677,6 +688,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'status', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'data_buffer_size_ctype', @@ -714,6 +726,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': False, 'python_name_or_default_for_init': 'data_buffer_size', 'complex_type': None, + 'array_dimension': 1, }, { 'ctypes_method_call_snippet': 'data_ctype', @@ -752,6 +765,7 @@ def _compare_dicts(actual, expected): 'use_in_python_api': True, 'python_name_or_default_for_init': 'data', 'complex_type': None, + 'array_dimension': 1, }, ], 'documentation': { diff --git a/generated/nifake/nifake/_grpc_stub_interpreter.py b/generated/nifake/nifake/_grpc_stub_interpreter.py index 829c8657e..b9e7617da 100644 --- a/generated/nifake/nifake/_grpc_stub_interpreter.py +++ b/generated/nifake/nifake/_grpc_stub_interpreter.py @@ -144,6 +144,9 @@ def fetch_waveform(self, number_of_samples): # noqa: N802 def fetch_waveform_into(self, number_of_samples): # noqa: N802 raise NotImplementedError('numpy-specific methods are not supported over gRPC') + def function_with_3d_numpy_array_of_numpy_complex128_input_parameter(self, multidimensional_array): # noqa: N802 + raise NotImplementedError('numpy-specific methods are not supported over gRPC') + def function_with_intflag_parameter(self, flag): # noqa: N802 self._invoke( self._client.FunctionWithIntflagParameter, diff --git a/generated/nifake/nifake/_library.py b/generated/nifake/nifake/_library.py index 0033ec0a5..95a91e2f7 100644 --- a/generated/nifake/nifake/_library.py +++ b/generated/nifake/nifake/_library.py @@ -36,6 +36,7 @@ def __init__(self, ctypes_library): self.niFake_EnumInputFunctionWithDefaults_cfunc = None self.niFake_ExportAttributeConfigurationBuffer_cfunc = None self.niFake_FetchWaveform_cfunc = None + self.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter_cfunc = None self.niFake_FunctionWithIntflagParameter_cfunc = None self.niFake_FunctionWithRepeatedCapabilityType_cfunc = None self.niFake_GetABoolean_cfunc = None @@ -188,6 +189,14 @@ def niFake_FetchWaveform(self, vi, number_of_samples, waveform_data, actual_numb self.niFake_FetchWaveform_cfunc.restype = ViStatus # noqa: F405 return self.niFake_FetchWaveform_cfunc(vi, number_of_samples, waveform_data, actual_number_of_samples) + def niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter(self, vi, multidimensional_array): # noqa: N802 + with self._func_lock: + if self.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter_cfunc is None: + self.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter_cfunc = self._get_library_function('niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter') + self.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter_cfunc.argtypes = [ViSession, ctypes.POINTER(NIComplexNumber)] # noqa: F405 + self.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter_cfunc.restype = ViStatus # noqa: F405 + return self.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter_cfunc(vi, multidimensional_array) + def niFake_FunctionWithIntflagParameter(self, vi, flag): # noqa: N802 with self._func_lock: if self.niFake_FunctionWithIntflagParameter_cfunc is None: diff --git a/generated/nifake/nifake/_library_interpreter.py b/generated/nifake/nifake/_library_interpreter.py index 380a13954..da7e3881a 100644 --- a/generated/nifake/nifake/_library_interpreter.py +++ b/generated/nifake/nifake/_library_interpreter.py @@ -32,8 +32,12 @@ def _get_ctypes_pointer_for_buffer(value=None, library_type=None, size=None): import numpy if library_type in (_complextype.NIComplexI16, _complextype.NIComplexNumberF32, _complextype.NIComplexNumber): complex_dtype = numpy.dtype(library_type) - structured_array = value.view(complex_dtype) - return structured_array.ctypes.data_as(ctypes.POINTER(library_type)) + if value.ndim > 1: + # we create a flattened view of the multi-dimensional numpy array + restructured_array_view = value.ravel().view(complex_dtype) + else: + restructured_array_view = value.view(complex_dtype) + return restructured_array_view.ctypes.data_as(ctypes.POINTER(library_type)) else: return numpy.ctypeslib.as_ctypes(value) elif isinstance(value, bytes): @@ -218,6 +222,13 @@ def fetch_waveform_into(self, waveform_data): # noqa: N802 errors.handle_error(self, error_code, ignore_warnings=False, is_error_handling=False) return + def function_with_3d_numpy_array_of_numpy_complex128_input_parameter(self, multidimensional_array): # noqa: N802 + vi_ctype = _visatype.ViSession(self._vi) # case S110 + multidimensional_array_ctype = _get_ctypes_pointer_for_buffer(value=multidimensional_array, library_type=_complextype.NIComplexNumber) # case B510 + error_code = self._library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter(vi_ctype, multidimensional_array_ctype) + errors.handle_error(self, error_code, ignore_warnings=False, is_error_handling=False) + return + def function_with_intflag_parameter(self, flag): # noqa: N802 vi_ctype = _visatype.ViSession(self._vi) # case S110 flag_ctype = _visatype.ViUInt64(flag.value) # case S130 diff --git a/generated/nifake/nifake/session.py b/generated/nifake/nifake/session.py index 5e88e6ce5..eb4627054 100644 --- a/generated/nifake/nifake/session.py +++ b/generated/nifake/nifake/session.py @@ -945,6 +945,27 @@ def fetch_waveform_into(self, waveform_data): raise TypeError('waveform_data must be numpy.ndarray of dtype=float64, is ' + str(waveform_data.dtype)) self._interpreter.fetch_waveform_into(waveform_data) + def function_with_3d_numpy_array_of_numpy_complex128_input_parameter(self, multidimensional_array): + r'''function_with_3d_numpy_array_of_numpy_complex128_input_parameter + + Method that takes a 3D numpy array of numpy complex128 as an input parameter. + + Args: + multidimensional_array (numpy.array(dtype=numpy.complex128)): Specifies the 3D array of numpy complex numbers to write. + + ''' + import numpy + + if type(multidimensional_array) is not numpy.ndarray: + raise TypeError('multidimensional_array must be {0}, is {1}'.format(numpy.ndarray, type(multidimensional_array))) + if numpy.isfortran(multidimensional_array) is True: + raise TypeError('multidimensional_array must be in C-order') + if multidimensional_array.dtype is not numpy.dtype('complex128'): + raise TypeError('multidimensional_array must be numpy.ndarray of dtype=complex128, is ' + str(multidimensional_array.dtype)) + if multidimensional_array.ndim != 3: + raise TypeError('multidimensional_array must be numpy.ndarray of dimension=3, is ' + str(multidimensional_array.ndim)) + self._interpreter.function_with_3d_numpy_array_of_numpy_complex128_input_parameter(multidimensional_array) + @ivi_synchronized def function_with_intflag_parameter(self, flag): r'''function_with_intflag_parameter @@ -1682,13 +1703,15 @@ def write_waveform_numpy(self, waveform): raise TypeError('waveform must be in C-order') if waveform.dtype is not numpy.dtype('float64'): raise TypeError('waveform must be numpy.ndarray of dtype=float64, is ' + str(waveform.dtype)) + if waveform.ndim != 1: + raise TypeError('waveform must be numpy.ndarray of dimension=1, is ' + str(waveform.ndim)) self._interpreter.write_waveform_numpy(waveform) @ivi_synchronized def write_waveform_numpy_complex128(self, waveform_data_array): r'''write_waveform_numpy_complex128 - A method that writes a waveform of numpy complex128 numbers + A method that writes a waveform of numpy complex128 numbers. Args: waveform_data_array (numpy.array(dtype=numpy.complex128)): Specifies the array of data to load into the waveform. Array should be numberOfSamples big. @@ -1702,6 +1725,8 @@ def write_waveform_numpy_complex128(self, waveform_data_array): raise TypeError('waveform_data_array must be in C-order') if waveform_data_array.dtype is not numpy.dtype('complex128'): raise TypeError('waveform_data_array must be numpy.ndarray of dtype=complex128, is ' + str(waveform_data_array.dtype)) + if waveform_data_array.ndim != 1: + raise TypeError('waveform_data_array must be numpy.ndarray of dimension=1, is ' + str(waveform_data_array.ndim)) self._interpreter.write_waveform_numpy_complex128(waveform_data_array) @ivi_synchronized @@ -1722,6 +1747,8 @@ def write_waveform_numpy_complex64(self, waveform_data_array): raise TypeError('waveform_data_array must be in C-order') if waveform_data_array.dtype is not numpy.dtype('complex64'): raise TypeError('waveform_data_array must be numpy.ndarray of dtype=complex64, is ' + str(waveform_data_array.dtype)) + if waveform_data_array.ndim != 1: + raise TypeError('waveform_data_array must be numpy.ndarray of dimension=1, is ' + str(waveform_data_array.ndim)) self._interpreter.write_waveform_numpy_complex64(waveform_data_array) @ivi_synchronized @@ -1742,6 +1769,8 @@ def write_waveform_numpy_complex_interleaved_i16(self, waveform_data_array): raise TypeError('waveform_data_array must be in C-order') if waveform_data_array.dtype is not numpy.dtype('int16'): raise TypeError('waveform_data_array must be numpy.ndarray of dtype=int16, is ' + str(waveform_data_array.dtype)) + if waveform_data_array.ndim != 1: + raise TypeError('waveform_data_array must be numpy.ndarray of dimension=1, is ' + str(waveform_data_array.ndim)) self._interpreter.write_waveform_numpy_complex_interleaved_i16(waveform_data_array) def _close(self): diff --git a/generated/nifake/nifake/unit_tests/_mock_helper.py b/generated/nifake/nifake/unit_tests/_mock_helper.py index 4bdda748f..8c76539e0 100644 --- a/generated/nifake/nifake/unit_tests/_mock_helper.py +++ b/generated/nifake/nifake/unit_tests/_mock_helper.py @@ -42,6 +42,8 @@ def __init__(self): self._defaults['FetchWaveform']['return'] = 0 self._defaults['FetchWaveform']['waveformData'] = None self._defaults['FetchWaveform']['actualNumberOfSamples'] = None + self._defaults['FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter'] = {} + self._defaults['FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter']['return'] = 0 self._defaults['FunctionWithIntflagParameter'] = {} self._defaults['FunctionWithIntflagParameter']['return'] = 0 self._defaults['FunctionWithRepeatedCapabilityType'] = {} @@ -339,6 +341,11 @@ def niFake_FetchWaveform(self, vi, number_of_samples, waveform_data, actual_numb actual_number_of_samples.contents.value = self._defaults['FetchWaveform']['actualNumberOfSamples'] return self._defaults['FetchWaveform']['return'] + def niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter(self, vi, multidimensional_array): # noqa: N802 + if self._defaults['FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter']['return'] != 0: + return self._defaults['FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter']['return'] + return self._defaults['FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter']['return'] + def niFake_FunctionWithIntflagParameter(self, vi, flag): # noqa: N802 if self._defaults['FunctionWithIntflagParameter']['return'] != 0: return self._defaults['FunctionWithIntflagParameter']['return'] @@ -1043,6 +1050,8 @@ def set_side_effects_and_return_values(self, mock_library): mock_library.niFake_ExportAttributeConfigurationBuffer.return_value = 0 mock_library.niFake_FetchWaveform.side_effect = MockFunctionCallError("niFake_FetchWaveform") mock_library.niFake_FetchWaveform.return_value = 0 + mock_library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter.side_effect = MockFunctionCallError("niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter") + mock_library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter.return_value = 0 mock_library.niFake_FunctionWithIntflagParameter.side_effect = MockFunctionCallError("niFake_FunctionWithIntflagParameter") mock_library.niFake_FunctionWithIntflagParameter.return_value = 0 mock_library.niFake_FunctionWithRepeatedCapabilityType.side_effect = MockFunctionCallError("niFake_FunctionWithRepeatedCapabilityType") diff --git a/generated/nifake/nifake/unit_tests/test_library_interpreter.py b/generated/nifake/nifake/unit_tests/test_library_interpreter.py index bf9542186..45cfc7dc2 100644 --- a/generated/nifake/nifake/unit_tests/test_library_interpreter.py +++ b/generated/nifake/nifake/unit_tests/test_library_interpreter.py @@ -899,6 +899,50 @@ def test_write_waveform_numpy_complex_interleaved_i16_valid_input(self): _matchers.NIComplexI16PointerMatcher(waveform_data_pointer, number_of_samples) ) + def test_write_3d_numpy_array_of_numpy_complex128(self): + from nifake._complextype import NIComplexNumber + + array_3d = numpy.full((2, 3, 4), 1.0 + 2.0j, dtype=numpy.complex128) + number_of_samples = array_3d.size + flattened_array = array_3d.flatten() + complex_array = (NIComplexNumber * len(flattened_array))() + for i, value in enumerate(flattened_array): + complex_array[i] = NIComplexNumber(value.real, value.imag) + flattened_array_ptr = ctypes.cast(complex_array, ctypes.POINTER(NIComplexNumber)) + self.patched_library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter.side_effect = self.side_effects_helper.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter + interpreter = self.get_initialized_library_interpreter() + interpreter.function_with_3d_numpy_array_of_numpy_complex128_input_parameter(array_3d) + self.patched_library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter.assert_called_once_with( + _matchers.ViSessionMatcher(SESSION_NUM_FOR_TEST), + _matchers.NIComplexNumberPointerMatcher(flattened_array_ptr, number_of_samples) + ) + + def test_no_memorycopy_with_multi_dimensional_numpy_complex128_array(self): + array_3d = numpy.full((2, 3, 4), 1.0 + 2.0j, dtype=numpy.complex128) + self.patched_library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter.side_effect = self.side_effects_helper.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter + interpreter = self.get_initialized_library_interpreter() + interpreter.function_with_3d_numpy_array_of_numpy_complex128_input_parameter(array_3d) + args, kwargs = self.patched_library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter.call_args + actual_pointer = args[1] + input_address = array_3d.__array_interface__['data'][0] + address_passed_to_library = ctypes.addressof(actual_pointer.contents) + assert input_address == address_passed_to_library, f"Addresses do NOT match: input_address={input_address}, address_passed_to_library={address_passed_to_library}" + + def test_no_memorycopy_with_numpy_complex64_array(self): + array_1d = numpy.full(1000, 0.707 + 0.707j, dtype=numpy.complex64) + self.patched_library.niFake_WriteWaveformNumpyComplex64.side_effect = ( + self.side_effects_helper.niFake_WriteWaveformNumpyComplex64 + ) + interpreter = self.get_initialized_library_interpreter() + interpreter.write_waveform_numpy_complex64(array_1d) + args, kwargs = self.patched_library.niFake_WriteWaveformNumpyComplex64.call_args + actual_pointer = args[2] + input_address = array_1d.__array_interface__['data'][0] + address_passed_to_library = ctypes.addressof(actual_pointer.contents) + assert input_address == address_passed_to_library, ( + f"Addresses do NOT match: input_address={input_address}, address_passed_to_library={address_passed_to_library}" + ) + def test_matcher_prints(self): assert _matchers.ViSessionMatcher(SESSION_NUM_FOR_TEST).__repr__() == "ViSessionMatcher(" + str(nifake._visatype.ViSession) + ", 42)" assert _matchers.ViAttrMatcher(SESSION_NUM_FOR_TEST).__repr__() == "ViAttrMatcher(" + str(nifake._visatype.ViAttr) + ", 42)" diff --git a/generated/nifgen/nifgen/session.py b/generated/nifgen/nifgen/session.py index 4c381e760..0edd4dec4 100644 --- a/generated/nifgen/nifgen/session.py +++ b/generated/nifgen/nifgen/session.py @@ -1703,6 +1703,8 @@ def _create_waveform_f64_numpy(self, waveform_data_array): raise TypeError('waveform_data_array must be in C-order') if waveform_data_array.dtype is not numpy.dtype('float64'): raise TypeError('waveform_data_array must be numpy.ndarray of dtype=float64, is ' + str(waveform_data_array.dtype)) + if waveform_data_array.ndim != 1: + raise TypeError('waveform_data_array must be numpy.ndarray of dimension=1, is ' + str(waveform_data_array.ndim)) waveform_handle = self._interpreter.create_waveform_f64_numpy(self._repeated_capability, waveform_data_array) return waveform_handle @@ -1876,6 +1878,8 @@ def _create_waveform_i16_numpy(self, waveform_data_array): raise TypeError('waveform_data_array must be in C-order') if waveform_data_array.dtype is not numpy.dtype('int16'): raise TypeError('waveform_data_array must be numpy.ndarray of dtype=int16, is ' + str(waveform_data_array.dtype)) + if waveform_data_array.ndim != 1: + raise TypeError('waveform_data_array must be numpy.ndarray of dimension=1, is ' + str(waveform_data_array.ndim)) waveform_handle = self._interpreter.create_waveform_i16_numpy(self._repeated_capability, waveform_data_array) return waveform_handle @@ -2687,6 +2691,8 @@ def _write_binary16_waveform_numpy(self, waveform_handle, data): raise TypeError('data must be in C-order') if data.dtype is not numpy.dtype('int16'): raise TypeError('data must be numpy.ndarray of dtype=int16, is ' + str(data.dtype)) + if data.ndim != 1: + raise TypeError('data must be numpy.ndarray of dimension=1, is ' + str(data.ndim)) self._interpreter.write_binary16_waveform_numpy(self._repeated_capability, waveform_handle, data) @ivi_synchronized @@ -2781,6 +2787,8 @@ def _write_named_waveform_f64_numpy(self, waveform_name, data): raise TypeError('data must be in C-order') if data.dtype is not numpy.dtype('float64'): raise TypeError('data must be numpy.ndarray of dtype=float64, is ' + str(data.dtype)) + if data.ndim != 1: + raise TypeError('data must be numpy.ndarray of dimension=1, is ' + str(data.ndim)) self._interpreter.write_named_waveform_f64_numpy(self._repeated_capability, waveform_name, data) @ivi_synchronized @@ -2824,6 +2832,8 @@ def _write_named_waveform_i16_numpy(self, waveform_name, data): raise TypeError('data must be in C-order') if data.dtype is not numpy.dtype('int16'): raise TypeError('data must be numpy.ndarray of dtype=int16, is ' + str(data.dtype)) + if data.ndim != 1: + raise TypeError('data must be numpy.ndarray of dimension=1, is ' + str(data.ndim)) self._interpreter.write_named_waveform_i16_numpy(self._repeated_capability, waveform_name, data) @ivi_synchronized @@ -2947,6 +2957,8 @@ def _write_waveform_numpy(self, waveform_handle, data): raise TypeError('data must be in C-order') if data.dtype is not numpy.dtype('float64'): raise TypeError('data must be numpy.ndarray of dtype=float64, is ' + str(data.dtype)) + if data.ndim != 1: + raise TypeError('data must be numpy.ndarray of dimension=1, is ' + str(data.ndim)) self._interpreter.write_waveform_numpy(self._repeated_capability, waveform_handle, data) @ivi_synchronized diff --git a/generated/nirfsg/nirfsg/_library_interpreter.py b/generated/nirfsg/nirfsg/_library_interpreter.py index bee7a62fe..b8e6388c5 100644 --- a/generated/nirfsg/nirfsg/_library_interpreter.py +++ b/generated/nirfsg/nirfsg/_library_interpreter.py @@ -21,8 +21,12 @@ def _get_ctypes_pointer_for_buffer(value=None, library_type=None, size=None): import numpy if library_type in (_complextype.NIComplexI16, _complextype.NIComplexNumberF32, _complextype.NIComplexNumber): complex_dtype = numpy.dtype(library_type) - structured_array = value.view(complex_dtype) - return structured_array.ctypes.data_as(ctypes.POINTER(library_type)) + if value.ndim > 1: + # we create a flattened view of the multi-dimensional numpy array + restructured_array_view = value.ravel().view(complex_dtype) + else: + restructured_array_view = value.view(complex_dtype) + return restructured_array_view.ctypes.data_as(ctypes.POINTER(library_type)) else: return numpy.ctypeslib.as_ctypes(value) elif isinstance(value, bytes): diff --git a/generated/nirfsg/nirfsg/session.py b/generated/nirfsg/nirfsg/session.py index a420436c5..b4c02b02b 100644 --- a/generated/nirfsg/nirfsg/session.py +++ b/generated/nirfsg/nirfsg/session.py @@ -8217,6 +8217,8 @@ def _write_arb_waveform_complex_f32(self, waveform_name, waveform_data_array, mo raise TypeError('waveform_data_array must be in C-order') if waveform_data_array.dtype is not numpy.dtype('complex64'): raise TypeError('waveform_data_array must be numpy.ndarray of dtype=complex64, is ' + str(waveform_data_array.dtype)) + if waveform_data_array.ndim != 1: + raise TypeError('waveform_data_array must be numpy.ndarray of dimension=1, is ' + str(waveform_data_array.ndim)) self._interpreter.write_arb_waveform_complex_f32(waveform_name, waveform_data_array, more_data_pending) def _write_arb_waveform_complex_f64(self, waveform_name, waveform_data_array, more_data_pending): @@ -8255,6 +8257,8 @@ def _write_arb_waveform_complex_f64(self, waveform_name, waveform_data_array, mo raise TypeError('waveform_data_array must be in C-order') if waveform_data_array.dtype is not numpy.dtype('complex128'): raise TypeError('waveform_data_array must be numpy.ndarray of dtype=complex128, is ' + str(waveform_data_array.dtype)) + if waveform_data_array.ndim != 1: + raise TypeError('waveform_data_array must be numpy.ndarray of dimension=1, is ' + str(waveform_data_array.ndim)) self._interpreter.write_arb_waveform_complex_f64(waveform_name, waveform_data_array, more_data_pending) def _write_arb_waveform_complex_i16(self, waveform_name, waveform_data_array): @@ -8288,6 +8292,8 @@ def _write_arb_waveform_complex_i16(self, waveform_name, waveform_data_array): raise TypeError('waveform_data_array must be in C-order') if waveform_data_array.dtype is not numpy.dtype('int16'): raise TypeError('waveform_data_array must be numpy.ndarray of dtype=int16, is ' + str(waveform_data_array.dtype)) + if waveform_data_array.ndim != 1: + raise TypeError('waveform_data_array must be numpy.ndarray of dimension=1, is ' + str(waveform_data_array.ndim)) self._interpreter.write_arb_waveform_complex_i16(waveform_name, waveform_data_array) def write_arb_waveform(self, waveform_name, waveform_data_array, more_data_pending=False): diff --git a/src/nifake/metadata/functions.py b/src/nifake/metadata/functions.py index 4ebff5b4e..0b45a5fd6 100644 --- a/src/nifake/metadata/functions.py +++ b/src/nifake/metadata/functions.py @@ -2883,7 +2883,7 @@ 'WriteWaveformNumpyComplex128': { 'codegen_method': 'public', 'documentation': { - 'description': 'A function that writes a waveform of numpy complex128 numbers' + 'description': 'A function that writes a waveform of numpy complex128 numbers.' }, 'included_in_proto': False, 'is_error_handling': False, @@ -2990,6 +2990,50 @@ ], 'returns': 'ViStatus' }, + 'FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter': { + 'codegen_method': 'public', + 'documentation': { + 'description': 'Function that takes a 3D numpy array of numpy complex128 as an input parameter.' + }, + 'included_in_proto': False, + 'is_error_handling': False, + 'method_templates': [ + { + 'documentation_filename': 'numpy_method', + 'library_interpreter_filename': 'numpy_write_method', + 'method_python_name_suffix': '', + 'session_filename': 'numpy_write_method' + } + ], + 'parameters': [ + { + 'direction': 'in', + 'name': 'vi', + 'documentation': { + 'description': 'Identifies a particular instrument session.' + }, + 'type': 'ViSession', + 'use_array': False, + 'use_in_python_api': True + }, + { + 'complex_type': 'numpy', + 'direction': 'in', + 'documentation': { + 'description': 'Specifies the 3D array of numpy complex numbers to write. ' + }, + 'name': 'multidimensionalArray', + 'type': 'NIComplexNumber[]', + 'numpy': True, + 'use_numpy_array': True, + 'use_in_python_api': True, + 'array_dimension': 3 + }, + ], + 'python_name': 'function_with_3d_numpy_array_of_numpy_complex128_input_parameter', + 'returns': 'ViStatus', + 'use_session_lock': False + }, 'close': { 'codegen_method': 'private', 'documentation': { diff --git a/src/nifake/unit_tests/test_library_interpreter.py b/src/nifake/unit_tests/test_library_interpreter.py index bf9542186..45cfc7dc2 100644 --- a/src/nifake/unit_tests/test_library_interpreter.py +++ b/src/nifake/unit_tests/test_library_interpreter.py @@ -899,6 +899,50 @@ def test_write_waveform_numpy_complex_interleaved_i16_valid_input(self): _matchers.NIComplexI16PointerMatcher(waveform_data_pointer, number_of_samples) ) + def test_write_3d_numpy_array_of_numpy_complex128(self): + from nifake._complextype import NIComplexNumber + + array_3d = numpy.full((2, 3, 4), 1.0 + 2.0j, dtype=numpy.complex128) + number_of_samples = array_3d.size + flattened_array = array_3d.flatten() + complex_array = (NIComplexNumber * len(flattened_array))() + for i, value in enumerate(flattened_array): + complex_array[i] = NIComplexNumber(value.real, value.imag) + flattened_array_ptr = ctypes.cast(complex_array, ctypes.POINTER(NIComplexNumber)) + self.patched_library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter.side_effect = self.side_effects_helper.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter + interpreter = self.get_initialized_library_interpreter() + interpreter.function_with_3d_numpy_array_of_numpy_complex128_input_parameter(array_3d) + self.patched_library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter.assert_called_once_with( + _matchers.ViSessionMatcher(SESSION_NUM_FOR_TEST), + _matchers.NIComplexNumberPointerMatcher(flattened_array_ptr, number_of_samples) + ) + + def test_no_memorycopy_with_multi_dimensional_numpy_complex128_array(self): + array_3d = numpy.full((2, 3, 4), 1.0 + 2.0j, dtype=numpy.complex128) + self.patched_library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter.side_effect = self.side_effects_helper.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter + interpreter = self.get_initialized_library_interpreter() + interpreter.function_with_3d_numpy_array_of_numpy_complex128_input_parameter(array_3d) + args, kwargs = self.patched_library.niFake_FunctionWith3dNumpyArrayOfNumpyComplex128InputParameter.call_args + actual_pointer = args[1] + input_address = array_3d.__array_interface__['data'][0] + address_passed_to_library = ctypes.addressof(actual_pointer.contents) + assert input_address == address_passed_to_library, f"Addresses do NOT match: input_address={input_address}, address_passed_to_library={address_passed_to_library}" + + def test_no_memorycopy_with_numpy_complex64_array(self): + array_1d = numpy.full(1000, 0.707 + 0.707j, dtype=numpy.complex64) + self.patched_library.niFake_WriteWaveformNumpyComplex64.side_effect = ( + self.side_effects_helper.niFake_WriteWaveformNumpyComplex64 + ) + interpreter = self.get_initialized_library_interpreter() + interpreter.write_waveform_numpy_complex64(array_1d) + args, kwargs = self.patched_library.niFake_WriteWaveformNumpyComplex64.call_args + actual_pointer = args[2] + input_address = array_1d.__array_interface__['data'][0] + address_passed_to_library = ctypes.addressof(actual_pointer.contents) + assert input_address == address_passed_to_library, ( + f"Addresses do NOT match: input_address={input_address}, address_passed_to_library={address_passed_to_library}" + ) + def test_matcher_prints(self): assert _matchers.ViSessionMatcher(SESSION_NUM_FOR_TEST).__repr__() == "ViSessionMatcher(" + str(nifake._visatype.ViSession) + ", 42)" assert _matchers.ViAttrMatcher(SESSION_NUM_FOR_TEST).__repr__() == "ViAttrMatcher(" + str(nifake._visatype.ViAttr) + ", 42)"