c++gnuradiognuradio-companion

ImportError: undefined symbol: fftw_execute


I am trying to build a sink block with FFT and Kurtosis capability using gr_modtool.

The code itself can be compiled without error.

But when I run the flow graph in GRC, it produces following error message.

Question: How to solve this error?

I am passing the fftw3f path through, but I am getting an error.

Thank you in advance for any guidance you may be able to provide.

Generating: '/home/nomo/gr-Kurtosis/kurtosis.py'

Executing: /usr/bin/python3 -u /home/nomo/gr-Kurtosis/kurtosis.py

Traceback (most recent call last):
  File "/home/nomo/gr-Kurtosis/kurtosis.py", line 34, in <module>
    import Kurtosis
  File "/usr/local/lib/python3/dist-packages/Kurtosis/__init__.py", line 30, in <module>
    from .Kurtosis_swig import *
  File "/usr/local/lib/python3/dist-packages/Kurtosis/Kurtosis_swig.py", line 13, in <module>
    from . import _Kurtosis_swig
ImportError: /usr/local/lib/x86_64-linux-gnu/libgnuradio-Kurtosis.so.1.0.0git: undefined symbol: fftw_execute

Below is the implmentation source file:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <gnuradio/io_signature.h>
#include "Kurtosis_c_impl.h"

namespace gr {
  namespace Kurtosis {

    Kurtosis_c::sptr
    Kurtosis_c::make(int fftsize)
    {
      return gnuradio::get_initial_sptr
        (new Kurtosis_c_impl(fftsize));
    }


    /*
     * The private constructor
     */
    Kurtosis_c_impl::Kurtosis_c_impl(int fftsize)
      : gr::sync_block("Kurtosis_c",
              gr::io_signature::make(1, 1, sizeof(gr_complex)),
              gr::io_signature::make(0, 0, 0)),
              d_N(fftsize)
    {
      d_input = (fftw_complex*)fftw_malloc(sizeof(gr_complex) * d_N);
      K = (fftw_complex*)fftw_malloc(sizeof(gr_complex) * d_N);
      d_plan = fftw_plan_dft_1d(d_N, d_input, d_input, FFTW_BACKWARD, FFTW_ESTIMATE);
    }

    /*
     * Our virtual destructor.
     */
    Kurtosis_c_impl::~Kurtosis_c_impl()
    {
    }

    int
    Kurtosis_c_impl::work(int noutput_items,
        gr_vector_const_void_star &input_items,
        gr_vector_void_star &output_items)
    {
      const gr_complex *in = (const gr_complex*) input_items[0];

      for(int k = 0; k < noutput_items; k++)
      {
        for(int j = 0; j < M; j++)
        {
          for(int i = 0; i < d_N; i++)
          {
            d_input[i][0] = in[i].real();
            d_input[i][1] = in[i].imag();
          }
      
          fftw_execute(d_plan);
      
          M = 29296;
      
        
          for(int i = 0; i < d_N; i++)
          {   
            S1[i][0] = S1[i][0] + d_input[i][0];
            S1[i][1] = S1[i][1] + d_input[i][1];
        
            S2[i][0] = S2[i][0] + d_input[i][0] * d_input[i][0];
            S2[i][1] = S2[i][1] + d_input[i][1] * d_input[i][1];
        
            S3[i][0] = S3[i][0] + d_input[i][0] * d_input[i][0] * d_input[i][0];
            S3[i][1] = S3[i][1] + d_input[i][1] * d_input[i][1] * d_input[i][1];
        
            S4[i][0] = S4[i][0] + d_input[i][0] * d_input[i][0] * d_input[i][0] * d_input[i][0];
            S4[i][1] = S4[i][1] + d_input[i][1] * d_input[i][1] * d_input[i][1] * d_input[i][1];
        
            dc[i][0] = dc[i][0] + 1;
            dc[i][1] = dc[i][1] + 1;
          }
    
          for(int i = 0; i < d_N; i++)
          {
            Myu1[i][0] = S1[i][0] / dc[i][0];
            Myu1[i][1] = S1[i][1] / dc[i][1];
      
            Myu2[i][0] = S2[i][0] / dc[i][0];
            Myu2[i][1] = S2[i][1] / dc[i][1];
       
            Myu3[i][0] = S3[i][0] / dc[i][0];
            Myu3[i][1] = S3[i][1] / dc[i][1];
       
            Myu4[i][0] = S4[i][0] / dc[i][0];
            Myu4[i][1] = S4[i][1] / dc[i][1];
          }

    

          for(int i = 0; i < d_N; i++)
          {
            K[i][0] = (Myu4[i][0] - 4 * Myu3[i][0] * Myu1[i][0] + 6 * Myu2[i][0] * Myu1[i][0] 
                   * Myu1[i][0] - 3 * Myu1[i][0] * Myu1[i][0] * Myu1[i][0] * Myu1[i][0]) / 
                    ((Myu2[i][0] - Myu1[i][0] * Myu1[i][0]) * (Myu2[i][0] - Myu1[i][0] 
                      * Myu1[i][0]));
      
            K[i][1] = (Myu4[i][1] - 4 * Myu3[i][1] * Myu1[i][1] + 6 * Myu2[i][1] * Myu1[i][1] 
                   * Myu1[i][1] - 3 * Myu1[i][1] * Myu1[i][1] * Myu1[i][1] * Myu1[i][1]) / 
                    ((Myu2[i][1] - Myu1[i][1] * Myu1[i][1]) * (Myu2[i][1] - Myu1[i][1] 
                      * Myu1[i][1]));
          }
        }
      }
      // Tell runtime system how many output items we produced.
      return noutput_items;
    }

  } /* namespace Kurtosis1 */
} /* namespace gr */

Below is the top-level CMakeLists.txt file:

########################################################################
# Project setup
########################################################################
cmake_minimum_required(VERSION 3.8)
project(gr-Kurtosis CXX C)
enable_testing()

# Install to PyBOMBS target prefix if defined
if(DEFINED ENV{PYBOMBS_PREFIX})
    set(CMAKE_INSTALL_PREFIX $ENV{PYBOMBS_PREFIX})
    message(STATUS "PyBOMBS installed GNU Radio. Setting CMAKE_INSTALL_PREFIX to $ENV{PYBOMBS_PREFIX}")
endif()

# Select the release build type by default to get optimization flags
if(NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE "Release")
   message(STATUS "Build type not specified: defaulting to release.")
endif(NOT CMAKE_BUILD_TYPE)
set(CMAKE_BUILD_TYPE ${CMAKE_BUILD_TYPE} CACHE STRING "")

# Make sure our local CMake Modules path comes first
list(INSERT CMAKE_MODULE_PATH 0 ${CMAKE_SOURCE_DIR}/cmake/Modules)

# Set the version information here
set(VERSION_MAJOR 1)
set(VERSION_API   0)
set(VERSION_ABI   0)
set(VERSION_PATCH git)

cmake_policy(SET CMP0011 NEW)

# Enable generation of compile_commands.json for code completion engines
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

########################################################################
# Compiler specific setup
########################################################################
if((CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR
    CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    AND NOT WIN32)
    #http://gcc.gnu.org/wiki/Visibility
    add_definitions(-fvisibility=hidden)
endif()

IF(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
    SET(CMAKE_CXX_STANDARD 11)
ELSEIF(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    SET(CMAKE_CXX_STANDARD 11)
ELSEIF(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC")
    SET(CMAKE_CXX_STANDARD 11)
ELSE()
    message(WARNING "C++ standard could not be set because compiler is not GNU, Clang or MSVC.")
ENDIF()

IF(CMAKE_C_COMPILER_ID STREQUAL "GNU")
    SET(CMAKE_C_STANDARD 11)
ELSEIF(CMAKE_C_COMPILER_ID MATCHES "Clang")
    SET(CMAKE_C_STANDARD 11)
ELSEIF(CMAKE_C_COMPILER_ID STREQUAL "MSVC")
    SET(CMAKE_C_STANDARD 11)
ELSE()
    message(WARNING "C standard could not be set because compiler is not GNU, Clang or MSVC.")
ENDIF()

########################################################################
# Install directories
########################################################################
find_package(Gnuradio "3.8" REQUIRED)
include(GrVersion)

include(GrPlatform) #define LIB_SUFFIX

find_package(FFTW3f)

if(NOT CMAKE_MODULES_DIR)
  set(CMAKE_MODULES_DIR lib${LIB_SUFFIX}/cmake)
endif(NOT CMAKE_MODULES_DIR)

set(GR_INCLUDE_DIR      include/Kurtosis)
set(GR_CMAKE_DIR        ${CMAKE_MODULES_DIR}/Kurtosis)
set(GR_PKG_DATA_DIR     ${GR_DATA_DIR}/${CMAKE_PROJECT_NAME})
set(GR_PKG_DOC_DIR      ${GR_DOC_DIR}/${CMAKE_PROJECT_NAME})
set(GR_PKG_CONF_DIR     ${GR_CONF_DIR}/${CMAKE_PROJECT_NAME}/conf.d)
set(GR_PKG_LIBEXEC_DIR  ${GR_LIBEXEC_DIR}/${CMAKE_PROJECT_NAME})
set(GR_REQUIRED_COMPONENTS RUNTIME FFTW3f)

########################################################################
# On Apple only, set install name and use rpath correctly, if not already set
########################################################################
if(APPLE)
    if(NOT CMAKE_INSTALL_NAME_DIR)
        set(CMAKE_INSTALL_NAME_DIR
            ${CMAKE_INSTALL_PREFIX}/${GR_LIBRARY_DIR} CACHE
            PATH "Library Install Name Destination Directory" FORCE)
    endif(NOT CMAKE_INSTALL_NAME_DIR)
    if(NOT CMAKE_INSTALL_RPATH)
        set(CMAKE_INSTALL_RPATH
            ${CMAKE_INSTALL_PREFIX}/${GR_LIBRARY_DIR} CACHE
            PATH "Library Install RPath" FORCE)
    endif(NOT CMAKE_INSTALL_RPATH)
    if(NOT CMAKE_BUILD_WITH_INSTALL_RPATH)
        set(CMAKE_BUILD_WITH_INSTALL_RPATH ON CACHE
            BOOL "Do Build Using Library Install RPath" FORCE)
    endif(NOT CMAKE_BUILD_WITH_INSTALL_RPATH)
endif(APPLE)

########################################################################
# Find gnuradio build dependencies
########################################################################
find_package(Doxygen)

########################################################################
# Setup doxygen option
########################################################################
if(DOXYGEN_FOUND)
    option(ENABLE_DOXYGEN "Build docs using Doxygen" ON)
else(DOXYGEN_FOUND)
    option(ENABLE_DOXYGEN "Build docs using Doxygen" OFF)
endif(DOXYGEN_FOUND)

########################################################################
# Create uninstall target
########################################################################
configure_file(
    ${CMAKE_SOURCE_DIR}/cmake/cmake_uninstall.cmake.in
    ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake
@ONLY)

add_custom_target(uninstall
    ${CMAKE_COMMAND} -P ${CMAKE_CURRENT_BINARY_DIR}/cmake_uninstall.cmake
    )


########################################################################
# Add subdirectories
########################################################################
add_subdirectory(include/Kurtosis)
add_subdirectory(lib)
add_subdirectory(apps)
add_subdirectory(docs)
add_subdirectory(swig)
add_subdirectory(python)
add_subdirectory(grc)

########################################################################
# Install cmake search helper for this library
########################################################################

install(FILES cmake/Modules/KurtosisConfig.cmake
    DESTINATION ${CMAKE_MODULES_DIR}/Kurtosis
)

Below is the lib/CMakeLists.txt file:

########################################################################
# Setup library
########################################################################
include(GrPlatform) #define LIB_SUFFIX

list(APPEND Kurtosis_sources
    Kurtosis_c_impl.cc
)

set(Kurtosis_sources "${Kurtosis_sources}" PARENT_SCOPE)
if(NOT Kurtosis_sources)
    MESSAGE(STATUS "No C++ sources... skipping lib/")
    return()
endif(NOT Kurtosis_sources)

add_library(gnuradio-Kurtosis SHARED ${Kurtosis_sources})
target_link_libraries(gnuradio-Kurtosis PUBLIC ${FFTW3F_LIBRARIES}
                      gnuradio::gnuradio-runtime fftw3f::fftw3f)
target_include_directories(gnuradio-Kurtosis
    PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/../include>
    PUBLIC $<INSTALL_INTERFACE:include>
  )
link_directories(${FFTW3F_LIBRARY_DIRS})
set_target_properties(gnuradio-Kurtosis PROPERTIES DEFINE_SYMBOL "gnuradio_Kurtosis_EXPORTS")

if(APPLE)
    set_target_properties(gnuradio-Kurtosis PROPERTIES
        INSTALL_NAME_DIR "${CMAKE_INSTALL_PREFIX}/lib"
    )
endif(APPLE)

########################################################################
# Install built library files
########################################################################
include(GrMiscUtils)
GR_LIBRARY_FOO(gnuradio-Kurtosis FFTW3f)


########################################################################
# Print summary
########################################################################
message(STATUS "Using install prefix: ${CMAKE_INSTALL_PREFIX}")
message(STATUS "Building for version: ${VERSION} / ${LIBVER}")

########################################################################
# Build and register unit test
########################################################################
include(GrTest)

include_directories(${CPPUNIT_INCLUDE_DIRS})

# If your unit tests require special include paths, add them here
#include_directories()
# List all files that contain Boost.UTF unit tests here
list(APPEND test_Kurtosis_sources
)


# Anything we need to link to for the unit tests go here
list(APPEND GR_TEST_TARGET_DEPS gnuradio-Kurtosis)

if(NOT test_Kurtosis_sources)
    MESSAGE(STATUS "No C++ unit tests... skipping")
    return()
endif(NOT test_Kurtosis_sources)

foreach(qa_file ${test_Kurtosis_sources})
    GR_ADD_CPP_TEST("Kurtosis_${qa_file}"
        ${CMAKE_CURRENT_SOURCE_DIR}/${qa_file}
    )
endforeach(qa_file)

Solution

  • link_directories(${FFTW3F_LIBRARY_DIRS})
    

    is not a great idea; you need to link to the library, not just tell the linker that the directory containing the libraries might also be interesting.

    Instead, you use the modern CMake interface for linking libraries: that already tells CMake to link and how to find the libraries at link-time. Great! You can just remove this link_directories line.

    The problem here is that you link against FFTWf (note the f: single precision, which is usually more than enough for SDR applications!), but use some double precision routines. Use fftwf_execute, and fftwf_complex!


    This is just a unnecessarily complicated way of copying of memory:

          for(int i = 0; i < d_N; i++)
          {
            d_input[i][0] = in[i].real();
            d_input[i][1] = in[i].imag();
          }
      
    

    The gr_complex type is just real, imaginary parts right after each other, exactly identical to fftwf_complex. Plan your DFT to allow you to select your input memory, instead of having to copy data into the fixed position.

    Even more comfortably: remove all your own FFTW(f) handling, and just use gr::fft::fft_complex_rev, which does all this for you:

    In your Kurtosis_c_impl.h:

    #include <gnuradio/fft/fft.h>
    …
    class Kurtosis_c {
    …
       fft::fft_complex_rev d_fft;
    …
    };
    

    in your Kurtosis_c_impl.cc:

    …
    // constructor gets shorter :)
    Kurtosis_c_impl::Kurtosis_c_impl(int fftsize) //should be unsigned int, by the way
          : gr::sync_block("Kurtosis_c",
                  gr::io_signature::make(1, 1, sizeof(gr_complex)),
                  gr::io_signature::make(0, 0, 0)),
                  d_N(fftsize),
                  d_fft(fftsize) {}
    …
    // work function:
        int
        Kurtosis_c_impl::work(int noutput_items,
            gr_vector_const_void_star &input_items,
            gr_vector_void_star &output_items)
        {
          const gr_complex *in = (const gr_complex*) input_items[0];
          for(unsigned int idx = 0; idx < d_N; ++idx) {
            d_fft.get_inbuf()[idx] = in[idx];
          }
          d_fft.execute();
          
    
    

    regarding:

          for(int i = 0; i < d_N; i++)
          {   
            S1[i][0] = S1[i][0] + d_input[i][0];
            S1[i][1] = S1[i][1] + d_input[i][1];
        
    

    You do realize that with complex numbers in C++, you can just do complex arithmetics? If your S1 is an array/vector/other container of gr_complex (which is just an alias for std::complex<float>, which is, given the inclusion order, just the same as fftwf_complex), then this boils down to

    for(unsigned int i = 0; i < d_N; ++i)
    {
      S1[i] += d_input[i];
    }
    

    Writing readable code is kind of important! In this case it's OK, especially because you're afterwards doing higher powers of the real and imaginary parts of d_input, anyways.

    However,

            dc[i][0] = dc[i][0] + 1;
            dc[i][1] = dc[i][1] + 1;
    

    Come one. That's just wasteful. Instead of increasing a number by +1, N times, just increase it by N, once.

          for(int i = 0; i < d_N; i++)
          {
            K[i][0] = (Myu4[i][0] - 4 * Myu3[i][0] * Myu1[i][0] + 6 * Myu2[i][0] * Myu1[i][0] 
                   * Myu1[i][0] - 3 * Myu1[i][0] * Myu1[i][0] * Myu1[i][0] * Myu1[i][0]) / 
                    ((Myu2[i][0] - Myu1[i][0] * Myu1[i][0]) * (Myu2[i][0] - Myu1[i][0] 
                      * Myu1[i][0]));
      
            K[i][1] = (Myu4[i][1] - 4 * Myu3[i][1] * Myu1[i][1] + 6 * Myu2[i][1] * Myu1[i][1] 
                   * Myu1[i][1] - 3 * Myu1[i][1] * Myu1[i][1] * Myu1[i][1] * Myu1[i][1]) / 
                    ((Myu2[i][1] - Myu1[i][1] * Myu1[i][1]) * (Myu2[i][1] - Myu1[i][1] 
                      * Myu1[i][1]));
          }
    

    Honestly, this is unreadable, and you and your supervisor will curse you next week, when either of you try to read this.

          for(int i = 0; i < d_N; i++)
          {
            const auto& M1 = Myu1[i];
            const auto& M2 = Myu2[i];
            const auto& M3 = Myu3[i];
            const auto& M4 = Myu4[i];
            K[i].real() = (M4.real() - 4 * M3.real() * M1.real()  //comment on what this is
                   + 6 * M2.real() * M1.real() * M1.real()  // comment
                   - 3 * M1.real() * M1.real() * M1.real() * M1.real() /  // comment
                    ((M2.real() - M1.real() * M1.real()) * (M2.real() - M1.real() * M1.real())); //comment
      
            K[i].imag() = /* This seems to be the exact same thing as above.
                              Clear indication that you should write a small inline
                              function that takes four float arguments and 
                              returns a flow to do this */
          }
    

    Note that this literally yields the same machine code as your way of writing it, as these .real() and .imag() functions just compile to the same memory access. And references like M1M4 are just logical sugar and also have no overhead.


    Most importantly,

      // Tell runtime system how many output items we produced.
      return noutput_items;
    

    You didn't read that comment! You're not returning the number of items you've produced (which is the same as you produced). You act as if you used all the input, even if you just used d_N! You're not even checking whether you have at least d_N input items. That is necessary. GNU Radio will give you a varying amount of input items, not always the same number!