diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..a2e8532 --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,52 @@ +name: Ruby + +on: [push, pull_request] + +jobs: + build: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + # Due to https://github.com/actions/runner/issues/849, we have to use quotes for '3.0' + ruby: [2.3, 2.4, 2.6, 2.7, '3.0', 3.1, 3.2, 3.3, 3.4, '4.0'] + exclude: + - os: windows-latest + ruby: 2.3 + runs-on: ${{ matrix.os }} + + steps: + - uses: actions/checkout@v6 + - name: before_setup + shell: bash + run: | + case ${{ matrix.os }} in + ubuntu-latest) + sudo apt-get update && sudo apt-get install -yq libfftw3-dev + ;; + windows-latest) + vcpkg install fftw3 --triplet=x64-mingw-static + # find $(cygpath -u "$VCPKG_INSTALLATION_ROOT")/packages -name "*fftw3*" + echo "BUNDLE_BUILD__NUMO___FFTW=\"--with-fftw-dir=$(cygpath -m ${VCPKG_INSTALLATION_ROOT})/packages/fftw3_x64-mingw-static\"" >> $GITHUB_ENV + ;; + esac + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + id: ruby-inst + env: + BUNDLE_BUILD__NUMO___NARRAY: --with-cflags=-std=c17 + with: + ruby-version: ${{ matrix.ruby }} + bundler-cache: true + - name: Run the default task + run: | + bundle exec rake compile -- ${{ env.BUNDLE_BUILD__NUMO___FFTW }} + bundle exec rake spec + - name: Upload artifact for error reproduction when failure + if: ${{ failure() }} + uses: actions/upload-artifact@v7 + with: + name: gem-build-logs.${{ matrix.os }}_${{ matrix.ruby }} + path: | + ${{ github.workspace }}/vendor/bundle/ruby/**/gems/ + ${{ github.workspace }}/vendor/bundle/ruby/**/mkmf.log \ No newline at end of file diff --git a/.rspec b/.rspec new file mode 100644 index 0000000..34c5164 --- /dev/null +++ b/.rspec @@ -0,0 +1,3 @@ +--format documentation +--color +--require spec_helper diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..2e7431b --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +# Specify your gem's dependencies in gps_pvt.gemspec +gemspec diff --git a/Rakefile b/Rakefile index 8fd75b9..edee9bb 100644 --- a/Rakefile +++ b/Rakefile @@ -1,13 +1,21 @@ require "bundler/gem_tasks" -task :doc do - dir = "ext/numo/fftw" - src = %w[fftw.c] - path = src.map{|s| File.join(dir,s)} - sh "cd #{dir}; ruby extconf.rb; make #{src.join(' ')}" - sh "rm -rf yard .yardoc; yard doc -o yard -m markdown -r README.md #{path.join(' ')}" +require "rspec/core/rake_task" +RSpec::Core::RakeTask.new(:spec) + +require "rake/extensiontask" +Rake::ExtensionTask.new("numo/fftw") do |ext| + ext.lib_dir = "lib/numo" end -task :cleandoc do - rm_rf %w[yard .yardoc] +require "yard" +YARD::Rake::YardocTask.new do |t| + t.before = proc{ + Rake::Task["compile:numo/fftw"].invoke + } + t.files = Dir['**/numo/fftw/**/fftw.c', __dir__] + t.options = ['--embed-mixins'] + #t.stats_options = ['--list-undoc'] end + +task :default => [:compile, :spec] diff --git a/ext/numo/fftw/depend b/ext/numo/fftw/depend index decf83a..6e08e58 100644 --- a/ext/numo/fftw/depend +++ b/ext/numo/fftw/depend @@ -2,13 +2,4 @@ CLEANOBJS = *.o */*.o *.bak *~ ERB = erb -T- fftw.c: fftw.erb.c - $(ERB) fftw.erb.c > fftw.c - -doc : fftw.c - yard doc -o yard -m markdown $< - -clean: cleansrc cleandoc -cleansrc: - -$(Q)$(RM) fftw.c -cleandoc: - -$(Q)$(RM_RF) yard .yardoc + $(ERB) $^ > $@ \ No newline at end of file diff --git a/ext/numo/fftw/fftw.erb.c b/ext/numo/fftw/fftw.erb.c index 209f9fe..89a10ba 100644 --- a/ext/numo/fftw/fftw.erb.c +++ b/ext/numo/fftw/fftw.erb.c @@ -150,10 +150,10 @@ numo_fftw_<%=func%>(VALUE mod, VALUE vna, VALUE vsign) void Init_fftw() { - VALUE mNumo,mFFTW; + VALUE /*mNumo,*/ mFFTW; // CAUTION: mNumo is already defined in narray.h - rb_require("numo/narray"); - mNumo = rb_define_module("Numo"); + //rb_require("numo/narray"); // move to fftw.rb because numo/narray.so should be loaded before Init_fftw() + //mNumo = rb_define_module("Numo"); mFFTW = rb_define_module_under(mNumo,"FFTW"); <% $funcs.each do |f| %> diff --git a/lib/numo/fftw.rb b/lib/numo/fftw.rb new file mode 100644 index 0000000..6d3e55d --- /dev/null +++ b/lib/numo/fftw.rb @@ -0,0 +1,2 @@ +require "numo/narray" +require "numo/fftw.#{RbConfig::CONFIG['DLEXT']}" diff --git a/numo-fftw.gemspec b/numo-fftw.gemspec index efa3586..4dbb4ef 100644 --- a/numo-fftw.gemspec +++ b/numo-fftw.gemspec @@ -27,7 +27,10 @@ Gem::Specification.new do |spec| spec.require_paths = ["lib"] spec.extensions = ["ext/numo/fftw/extconf.rb"] - spec.add_development_dependency "bundler", "~> 1.3" - spec.add_development_dependency "rake", "~> 0" + spec.add_development_dependency "bundler", ">= 1.3" + spec.add_development_dependency "rake" + spec.add_development_dependency "rake-compiler" + spec.add_development_dependency "rspec" + spec.add_development_dependency "yard" spec.add_runtime_dependency "numo-narray", ">= 0.9.0.8" end diff --git a/spec/complexFFT_spec.rb b/spec/complexFFT_spec.rb new file mode 100644 index 0000000..3edab94 --- /dev/null +++ b/spec/complexFFT_spec.rb @@ -0,0 +1,76 @@ +# frozen_string_literal: true +# port from numru-fftw3/test/complexFFT.rb + +require 'rspec' + +require "numo/fftw" + +module Numo::FFTW + FORWARD = -1 + BACKWARD = 1 + singleton_methods.each{|func| + slice = (/(\d)d/ =~ func.to_s) ? $1.to_i : 0 + {:fw => FORWARD, :bk => BACKWARD}.each{|dir, sign| + define_singleton_method("#{func}_#{dir}".to_sym){|na| + res = send(func, na, sign) + (dir == :bk) ? (res / res.shape[-slice..-1].inject(:*)) : res + } + } + } +end + +RSpec::describe Numo::FFTW do + let(:fftw){described_class} + let(:eps){1e-10} + let(:seps){1e-6} + + it 'can perform forward and backward FFT' do # test_fft_fw_bk + na = Numo::DFloat::new(8,4).fill(1) # will be corced to complex + na[1,1]=5 + fc = fftw.dft_fw(na) + nb = fftw.dft_bk(fc).real + expect((na-nb).abs.max).to be < eps + + fc = fftw.dft_1d_fw(na) + nb = fftw.dft_1d_bk(fc).real + expect((na-nb).abs.max).to be < eps + end + + it 'can perform real forward FFT' do # test_real_all_dims + na = Numo::DFloat::new(8,4).fill(1) # will be corced to complex + na[1,1]=5 + fc = fftw.dft(na, fftw::FORWARD)/na.length + nb = fftw.dft(fc, fftw::BACKWARD).real + expect((na-nb).abs.max).to be < eps + end + + it 'can perform complex forward FFT' do # test_complex_all_dims + na = Numo::DComplex::new(8,4).fill(1) * Complex::I + na[1,1]=5 + fc = fftw.dft(na, fftw::FORWARD)/na.length + nb = fftw.dft(fc, fftw::BACKWARD) + expect((na-nb).abs.max).to be < eps + end + + it 'can perform single precision forward FFT' do # test_single_float + # single float (treated as single if lib fftw3f exits). + # see http://www.fftw.org/fftw3_doc/Precision.html for more info + na = Numo::SFloat::new(8,4).indgen + fc = fftw.dft(na, fftw::FORWARD)/na.length + nb = fftw.dft(fc, fftw::BACKWARD).real + expect((na-nb).abs.max).to be < eps + end +end + +__END__ +# TODO: +class FFTW3Test < Test::Unit::TestCase + def test_dim_selection + na = NArray.float(8,4).indgen! + fc = FFTW3.fft(na, FFTW3::FORWARD, 0) + fc = FFTW3.fft(fc, FFTW3::FORWARD, 1) + fc2 = FFTW3.fft(na, FFTW3::FORWARD) + assert( (fc-fc2).abs.max < @eps ) + end +end + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..b960d8c --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,13 @@ +# frozen_string_literal: true + +RSpec.configure do |config| + # Enable flags like --only-failures and --next-failure + config.example_status_persistence_file_path = ".rspec_status" + + # Disable RSpec exposing methods globally on `Module` and `main` + config.disable_monkey_patching! + + config.expect_with :rspec do |c| + c.syntax = :expect + end +end