pycall 0.1.0.alpha → 0.1.0.alpha.20170224
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.travis.yml +15 -1
- data/Gemfile +5 -0
- data/Guardfile +1 -0
- data/README.md +4 -2
- data/bin/guard +17 -0
- data/bin/rspec +17 -0
- data/config/Guardfile +30 -0
- data/examples/classifier_comparison.rb +130 -0
- data/examples/hist.rb +27 -0
- data/examples/plot_forest_importances_faces.rb +41 -0
- data/examples/sum_benchmarking.rb +48 -0
- data/lib/pycall.rb +14 -4
- data/lib/pycall/conversion.rb +120 -0
- data/lib/pycall/dict.rb +90 -0
- data/lib/pycall/eval.rb +39 -0
- data/lib/pycall/import.rb +74 -0
- data/lib/pycall/init.rb +16 -0
- data/lib/pycall/libpython.rb +331 -0
- data/lib/pycall/list.rb +65 -0
- data/lib/pycall/pyerror.rb +25 -0
- data/lib/pycall/pyobject.rb +185 -0
- data/lib/pycall/pyobject_wrapper.rb +48 -0
- data/lib/pycall/python/investigator.py +6 -0
- data/lib/pycall/set.rb +17 -0
- data/lib/pycall/slice.rb +27 -0
- data/lib/pycall/tuple.rb +54 -0
- data/lib/pycall/types.rb +15 -0
- data/lib/pycall/utils.rb +33 -0
- data/lib/pycall/version.rb +2 -2
- data/pycall.gemspec +3 -1
- metadata +41 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA1:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: 6d6ddea53f723b45bf9207c52746afaa083fba57
         | 
| 4 | 
            +
              data.tar.gz: 1739c9f4e3a5d72503427540708f435694dea5aa
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: '08a91ee3157d872d18725d57ace058d991feb1855250307d9b9cd17feab5081a96ed03ab05941ecec073c22c3a63b2ed8c8c777c83735459e9e7b8e10e7220d7'
         | 
| 7 | 
            +
              data.tar.gz: 2359c6d09f3bc687466e41c0e0de2287a89bd4b11587e3384d4a41dd0eb737cf75af387d149efbb8322baa69136d4483f3901728e6b2b36545a9dbc5e6fe8d35
         | 
    
        data/.travis.yml
    CHANGED
    
    | @@ -1,5 +1,19 @@ | |
| 1 1 | 
             
            sudo: false
         | 
| 2 2 | 
             
            language: ruby
         | 
| 3 | 
            +
             | 
| 3 4 | 
             
            rvm:
         | 
| 5 | 
            +
              - 2.1.10
         | 
| 6 | 
            +
              - 2.2.5
         | 
| 4 7 | 
             
              - 2.3.1
         | 
| 5 | 
            -
             | 
| 8 | 
            +
              - ruby-head
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            addons:
         | 
| 11 | 
            +
              apt:
         | 
| 12 | 
            +
                packages:
         | 
| 13 | 
            +
                  - python3
         | 
| 14 | 
            +
                  - python3-dev
         | 
| 15 | 
            +
                  - python3-all
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            before_install:
         | 
| 18 | 
            +
              - gem update --system
         | 
| 19 | 
            +
              - gem update bundler
         | 
    
        data/Gemfile
    CHANGED
    
    
    
        data/Guardfile
    ADDED
    
    | @@ -0,0 +1 @@ | |
| 1 | 
            +
            ./config/Guardfile
         | 
    
        data/README.md
    CHANGED
    
    | @@ -1,4 +1,6 @@ | |
| 1 | 
            -
            #  | 
| 1 | 
            +
            # PyCall
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            [](https://travis-ci.org/mrkn/pycall)
         | 
| 2 4 |  | 
| 3 5 | 
             
            Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/pycall`. To experiment with that code, run `bin/console` for an interactive prompt.
         | 
| 4 6 |  | 
| @@ -32,7 +34,7 @@ To install this gem onto your local machine, run `bundle exec rake install`. To | |
| 32 34 |  | 
| 33 35 | 
             
            ## Contributing
         | 
| 34 36 |  | 
| 35 | 
            -
            Bug reports and pull requests are welcome on GitHub at https://github.com/ | 
| 37 | 
            +
            Bug reports and pull requests are welcome on GitHub at https://github.com/mrkn/pycall.
         | 
| 36 38 |  | 
| 37 39 |  | 
| 38 40 | 
             
            ## License
         | 
    
        data/bin/guard
    ADDED
    
    | @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # This file was generated by Bundler.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # The application 'guard' is installed as part of a gem, and
         | 
| 7 | 
            +
            # this file is here to facilitate running it.
         | 
| 8 | 
            +
            #
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            require "pathname"
         | 
| 11 | 
            +
            ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
         | 
| 12 | 
            +
              Pathname.new(__FILE__).realpath)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require "rubygems"
         | 
| 15 | 
            +
            require "bundler/setup"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            load Gem.bin_path("guard", "guard")
         | 
    
        data/bin/rspec
    ADDED
    
    | @@ -0,0 +1,17 @@ | |
| 1 | 
            +
            #!/usr/bin/env ruby
         | 
| 2 | 
            +
            # frozen_string_literal: true
         | 
| 3 | 
            +
            #
         | 
| 4 | 
            +
            # This file was generated by Bundler.
         | 
| 5 | 
            +
            #
         | 
| 6 | 
            +
            # The application 'rspec' is installed as part of a gem, and
         | 
| 7 | 
            +
            # this file is here to facilitate running it.
         | 
| 8 | 
            +
            #
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            require "pathname"
         | 
| 11 | 
            +
            ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
         | 
| 12 | 
            +
              Pathname.new(__FILE__).realpath)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            require "rubygems"
         | 
| 15 | 
            +
            require "bundler/setup"
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            load Gem.bin_path("rspec-core", "rspec")
         | 
    
        data/config/Guardfile
    ADDED
    
    | @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            # More info at https://github.com/guard/guard#readme
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            if RUBY_PLATFORM =~ /darwin/
         | 
| 4 | 
            +
              notification :terminal_notifier, app_name: 'pycall.gem ::', activate: 'com.googlecode.iTerm2'
         | 
| 5 | 
            +
            end
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            directories %w(config lib spec).select { |d|
         | 
| 8 | 
            +
              Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")
         | 
| 9 | 
            +
            }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            watch('config/Guardfile') do
         | 
| 12 | 
            +
              UI.info "Exiting guard because config changed"
         | 
| 13 | 
            +
              exit 0
         | 
| 14 | 
            +
            end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            guard :rspec, cmd: 'bin/rspec' do
         | 
| 17 | 
            +
              require "guard/rspec/dsl"
         | 
| 18 | 
            +
              dsl = Guard::RSpec::Dsl.new(self)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
              # RSpec files
         | 
| 21 | 
            +
              rspec = dsl.rspec
         | 
| 22 | 
            +
              watch(rspec.spec_helper) { rspec.spec_dir }
         | 
| 23 | 
            +
              watch(rspec.spec_support) { rspec.spec_dir }
         | 
| 24 | 
            +
              watch(rspec.spec_files)
         | 
| 25 | 
            +
             | 
| 26 | 
            +
              # Ruby files
         | 
| 27 | 
            +
              ruby = dsl.ruby
         | 
| 28 | 
            +
              dsl.watch_spec_files_for(ruby.lib_files)
         | 
| 29 | 
            +
              watch('lib/pycall/libpython.rb') { 'spec' }
         | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,130 @@ | |
| 1 | 
            +
            require 'pycall/import'
         | 
| 2 | 
            +
            include PyCall::Import
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            pyimport 'numpy', as: :np
         | 
| 5 | 
            +
            pyimport 'matplotlib.pyplot', as: :plt
         | 
| 6 | 
            +
            pyimport 'matplotlib.colors', as: :mplc
         | 
| 7 | 
            +
            pyfrom 'sklearn.cross_validation', import: :train_test_split
         | 
| 8 | 
            +
            pyfrom 'sklearn.preprocessing', import: :StandardScaler
         | 
| 9 | 
            +
            pyfrom 'sklearn.datasets', import: %i(make_moons make_circles make_classification)
         | 
| 10 | 
            +
            pyfrom 'sklearn.neighbors', import: :KNeighborsClassifier
         | 
| 11 | 
            +
            pyfrom 'sklearn.svm', import: :SVC
         | 
| 12 | 
            +
            pyfrom 'sklearn.tree', import: :DecisionTreeClassifier
         | 
| 13 | 
            +
            pyfrom 'sklearn.ensemble', import: %i(RandomForestClassifier AdaBoostClassifier)
         | 
| 14 | 
            +
            pyfrom 'sklearn.naive_bayes', import: :GaussianNB
         | 
| 15 | 
            +
            pyfrom 'sklearn.discriminant_analysis', import: %i(LinearDiscriminantAnalysis QuadraticDiscriminantAnalysis)
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            h = 0.02  # step size in the mesh
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            names = [
         | 
| 20 | 
            +
              'Nearest Neighbors',
         | 
| 21 | 
            +
              'Linear SVM',
         | 
| 22 | 
            +
              'RBF SVM',
         | 
| 23 | 
            +
              'Decision Tree',
         | 
| 24 | 
            +
              'Random Forest',
         | 
| 25 | 
            +
              'AdaBoost',
         | 
| 26 | 
            +
              'Naive Bayes',
         | 
| 27 | 
            +
              'Linear Discriminant Analysis',
         | 
| 28 | 
            +
              'Quadratic Discriminant Analysis'
         | 
| 29 | 
            +
            ]
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            classifiers = [
         | 
| 32 | 
            +
              KNeighborsClassifier.(3),
         | 
| 33 | 
            +
              SVC.(kernel: 'linear', C: 0.025),
         | 
| 34 | 
            +
              SVC.(gamma: 2, C: 1),
         | 
| 35 | 
            +
              DecisionTreeClassifier.(max_depth: 5),
         | 
| 36 | 
            +
              RandomForestClassifier.(max_depth: 5, n_estimators: 10, max_features: 1),
         | 
| 37 | 
            +
              AdaBoostClassifier.(),
         | 
| 38 | 
            +
              GaussianNB.(),
         | 
| 39 | 
            +
              LinearDiscriminantAnalysis.(),
         | 
| 40 | 
            +
              QuadraticDiscriminantAnalysis.()
         | 
| 41 | 
            +
            ]
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            x, y = make_classification.(
         | 
| 44 | 
            +
              n_features: 2,
         | 
| 45 | 
            +
              n_redundant: 0,
         | 
| 46 | 
            +
              n_informative: 2,
         | 
| 47 | 
            +
              random_state: 1,
         | 
| 48 | 
            +
              n_clusters_per_class: 1
         | 
| 49 | 
            +
            )
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            np.random.seed.(42)
         | 
| 52 | 
            +
            x += 2 * np.random.random_sample.(x.shape)
         | 
| 53 | 
            +
            linearly_separable = PyCall.tuple(x, y)
         | 
| 54 | 
            +
             | 
| 55 | 
            +
            datasets = [
         | 
| 56 | 
            +
              make_moons.(noise: 0.3, random_state: 0),
         | 
| 57 | 
            +
              make_circles.(noise: 0.2, factor: 0.5, random_state: 1),
         | 
| 58 | 
            +
              linearly_separable
         | 
| 59 | 
            +
            ]
         | 
| 60 | 
            +
             | 
| 61 | 
            +
            fig = plt.figure.(figsize: PyCall.tuple(27, 9))
         | 
| 62 | 
            +
            i = 1
         | 
| 63 | 
            +
            all = PyCall.slice(nil)
         | 
| 64 | 
            +
            datasets.each do |ds|
         | 
| 65 | 
            +
              x, y = ds
         | 
| 66 | 
            +
              x = StandardScaler.().fit_transform.(x)
         | 
| 67 | 
            +
              x_train, x_test, y_train, y_test = train_test_split.(x, y, test_size: 0.4)
         | 
| 68 | 
            +
             | 
| 69 | 
            +
              x_min, x_max = np.min.(x[all, 0]) - 0.5, np.max.(x[all, 0]) + 0.5
         | 
| 70 | 
            +
              y_min, y_max = np.min.(x[all, 1]) - 0.5, np.max.(x[all, 1]) + 0.5
         | 
| 71 | 
            +
             | 
| 72 | 
            +
              xx, yy = np.meshgrid.(
         | 
| 73 | 
            +
                np.linspace.(x_min, x_max, ((x_max - x_min)/h).round),
         | 
| 74 | 
            +
                np.linspace.(y_min, y_max, ((y_max - y_min)/h).round),
         | 
| 75 | 
            +
              )
         | 
| 76 | 
            +
              mesh_points = np.dstack.(PyCall.tuple(xx.ravel.(), yy.ravel.()))[0, all, all]
         | 
| 77 | 
            +
             | 
| 78 | 
            +
              # just plot the dataset first
         | 
| 79 | 
            +
              cm = plt.cm.RdBu
         | 
| 80 | 
            +
              cm_bright = mplc.ListedColormap.(["#FF0000", "#0000FF"])
         | 
| 81 | 
            +
              ax = plt.subplot.(datasets.length, classifiers.length + 1, i)
         | 
| 82 | 
            +
              # plot the training points
         | 
| 83 | 
            +
              ax.scatter.(x_train[all, 0], x_train[all, 1], c: y_train, cmap: cm_bright)
         | 
| 84 | 
            +
              # and testing points
         | 
| 85 | 
            +
              ax.scatter.(x_test[all, 0], x_test[all, 1], c: y_test, cmap: cm_bright, alpha: 0.6)
         | 
| 86 | 
            +
             | 
| 87 | 
            +
              ax.set_xlim.(np.min.(xx), np.max.(xx))
         | 
| 88 | 
            +
              ax.set_ylim.(np.min.(yy), np.max.(yy))
         | 
| 89 | 
            +
              ax.set_xticks.(PyCall.tuple())
         | 
| 90 | 
            +
              ax.set_yticks.(PyCall.tuple())
         | 
| 91 | 
            +
              i += 1
         | 
| 92 | 
            +
             | 
| 93 | 
            +
              # iterate over classifiers
         | 
| 94 | 
            +
              names.zip(classifiers).each do |name, clf|
         | 
| 95 | 
            +
                ax = plt.subplot.(datasets.length, classifiers.length + 1, i)
         | 
| 96 | 
            +
                clf.fit.(x_train, y_train)
         | 
| 97 | 
            +
                scor = clf.score.(x_test, y_test)
         | 
| 98 | 
            +
             | 
| 99 | 
            +
                # Plot the decision boundary.  For that, we will assign a color to each
         | 
| 100 | 
            +
                # point in the mesh [x_min, x_max]x[y_min, y_max]
         | 
| 101 | 
            +
                begin
         | 
| 102 | 
            +
                  # not implemented for some
         | 
| 103 | 
            +
                  z = clf.decision_function.(mesh_points)
         | 
| 104 | 
            +
                rescue
         | 
| 105 | 
            +
                  z = clf.predict_proba.(mesh_points)[all, 1]
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 108 | 
            +
                # Put the result into a color plot
         | 
| 109 | 
            +
                z = z.reshape.(xx.shape)
         | 
| 110 | 
            +
                ax.contourf.(xx, yy, z, cmap: cm, alpha: 0.8)
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                # Plot also the training points
         | 
| 113 | 
            +
                ax.scatter.(x_train[all, 0], x_train[all, 1], c: y_train, cmap: cm_bright)
         | 
| 114 | 
            +
                # and testing points
         | 
| 115 | 
            +
                ax.scatter.(x_test[all, 0], x_test[all, 1], c: y_test, cmap: cm_bright, alpha: 0.6)
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                ax.set_xlim.(np.min.(xx), np.max.(xx))
         | 
| 118 | 
            +
                ax.set_ylim.(np.min.(yy), np.max.(yy))
         | 
| 119 | 
            +
                ax.set_xticks.(PyCall.tuple())
         | 
| 120 | 
            +
                ax.set_yticks.(PyCall.tuple())
         | 
| 121 | 
            +
                ax.set_title.(name)
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                ax.text.(np.max.(xx) - 0.3, np.min.(yy) + 0.3, "%.2f" % scor, size: 15, horizontalalignment: 'right')
         | 
| 124 | 
            +
             | 
| 125 | 
            +
                i += 1
         | 
| 126 | 
            +
              end
         | 
| 127 | 
            +
            end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
            fig.subplots_adjust.(left: 0.02, right: 0.98)
         | 
| 130 | 
            +
            plt.show.()
         | 
    
        data/examples/hist.rb
    ADDED
    
    | @@ -0,0 +1,27 @@ | |
| 1 | 
            +
            require 'pycall/import'
         | 
| 2 | 
            +
            include PyCall::Import
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            pyimport 'numpy', as: 'np'
         | 
| 5 | 
            +
            pyimport 'matplotlib.mlab', as: 'mlab'
         | 
| 6 | 
            +
            pyimport 'matplotlib.pyplot', as: 'plt'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            np.random.seed.(0)
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            mu = 100
         | 
| 11 | 
            +
            sigma = 15
         | 
| 12 | 
            +
            x = mu + sigma * np.random.randn.(437)
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            num_bins = 50
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            fig, ax = plt.subplots.()
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            n, bins, patches = ax.hist.(x, num_bins, normed: 1)
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            y = mlab.normpdf.(bins, mu, sigma)
         | 
| 21 | 
            +
            ax.plot.(bins, y, '--')
         | 
| 22 | 
            +
            ax.set_xlabel.('Smarts')
         | 
| 23 | 
            +
            ax.set_ylabel.('Probability density')
         | 
| 24 | 
            +
            ax.set_title.('Histogram of IQ: $\mu=100$, $\sigma=15$')
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            fig.tight_layout.()
         | 
| 27 | 
            +
            plt.show.()
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            require 'pycall/import'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            include PyCall::Import
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            pyimport 'numpy', as: 'np'
         | 
| 6 | 
            +
            pyimport 'matplotlib.pyplot', as: 'plt'
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            pyfrom 'sklearn.datasets', import: 'fetch_olivetti_faces'
         | 
| 9 | 
            +
            pyfrom 'sklearn.ensemble', import: 'ExtraTreesClassifier'
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            # Number of cores to use to perform parallel fitting of the forest model
         | 
| 12 | 
            +
            n_jobs = 1
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            # Load the faces datasets
         | 
| 15 | 
            +
            data = fetch_olivetti_faces.()
         | 
| 16 | 
            +
            x = data.images.reshape.(PyCall.tuple(PyCall.len(data.images), -1))
         | 
| 17 | 
            +
            y = data.target
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            mask = y < 5  # Limit to 5 classes
         | 
| 20 | 
            +
            x = x[mask]
         | 
| 21 | 
            +
            y = y[mask]
         | 
| 22 | 
            +
             | 
| 23 | 
            +
            # Build a forest and compute the pixel importances
         | 
| 24 | 
            +
            puts "Fitting ExtraTreesClassifier on faces data with #{n_jobs} cores..."
         | 
| 25 | 
            +
            t0 = Time.now
         | 
| 26 | 
            +
            forest = ExtraTreesClassifier.(
         | 
| 27 | 
            +
              n_estimators: 1_000,
         | 
| 28 | 
            +
              max_features: 128,
         | 
| 29 | 
            +
              n_jobs: n_jobs,
         | 
| 30 | 
            +
              random_state: 0
         | 
| 31 | 
            +
            )
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            forest = forest.fit.(x, y)
         | 
| 34 | 
            +
            puts "done in %0.3fs" % (Time.now - t0)
         | 
| 35 | 
            +
            importances = forest.feature_importances_
         | 
| 36 | 
            +
            importances = importances.reshape.(data.images[0].shape)
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            # Plot pixel importances
         | 
| 39 | 
            +
            plt.matshow.(importances, cmap: plt.cm.hot)
         | 
| 40 | 
            +
            plt.title.("Pixel importances with forests of trees")
         | 
| 41 | 
            +
            plt.show.()
         | 
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            require 'pycall/import'
         | 
| 2 | 
            +
            include PyCall::Import
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            require 'benchmark'
         | 
| 5 | 
            +
            pyimport :pandas, as: :pd
         | 
| 6 | 
            +
            pyimport :seaborn, as: :sns
         | 
| 7 | 
            +
            pyimport 'matplotlib.pyplot', as: :plt
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            array = Array.new(100_000) { rand }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            trials = 100
         | 
| 12 | 
            +
            results = { method: [], runtime: [] }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            # Array#sum
         | 
| 15 | 
            +
            trials.times do
         | 
| 16 | 
            +
              results[:method] << 'sum'
         | 
| 17 | 
            +
              results[:runtime] << Benchmark.realtime { array.sum }
         | 
| 18 | 
            +
            end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
            # Array#inject(:+)
         | 
| 21 | 
            +
            trials.times do
         | 
| 22 | 
            +
              results[:method] << 'inject'
         | 
| 23 | 
            +
              results[:runtime] << Benchmark.realtime { array.inject(:+) }
         | 
| 24 | 
            +
            end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            # while
         | 
| 27 | 
            +
            def while_sum(ary)
         | 
| 28 | 
            +
              sum, i, n = 0, 0, ary.length
         | 
| 29 | 
            +
              while i < n
         | 
| 30 | 
            +
                sum += ary[i]
         | 
| 31 | 
            +
                i += 1
         | 
| 32 | 
            +
              end
         | 
| 33 | 
            +
              sum
         | 
| 34 | 
            +
            end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
            trials.times do
         | 
| 37 | 
            +
              results[:method] << 'while'
         | 
| 38 | 
            +
              results[:runtime] << Benchmark.realtime { while_sum(array) }
         | 
| 39 | 
            +
            end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
            # visualization
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            df = pd.DataFrame.(PyCall::Dict.new(results))
         | 
| 44 | 
            +
            sns.barplot.(x: 'method', y: 'runtime', data: df)
         | 
| 45 | 
            +
            plt.title.("Array summation benchmark (#{trials} trials)")
         | 
| 46 | 
            +
            plt.xlabel.('Summation method')
         | 
| 47 | 
            +
            plt.ylabel.('Average runtime [sec]')
         | 
| 48 | 
            +
            plt.show.()
         | 
    
        data/lib/pycall.rb
    CHANGED
    
    | @@ -1,5 +1,15 @@ | |
| 1 1 | 
             
            require "pycall/version"
         | 
| 2 | 
            -
             | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 5 | 
            -
             | 
| 2 | 
            +
            require "pycall/libpython"
         | 
| 3 | 
            +
            require "pycall/pyobject"
         | 
| 4 | 
            +
            require "pycall/pyerror"
         | 
| 5 | 
            +
            require "pycall/eval"
         | 
| 6 | 
            +
            require "pycall/types"
         | 
| 7 | 
            +
            require "pycall/conversion"
         | 
| 8 | 
            +
            require "pycall/pyobject_wrapper"
         | 
| 9 | 
            +
            require "pycall/tuple"
         | 
| 10 | 
            +
            require "pycall/list"
         | 
| 11 | 
            +
            require "pycall/dict"
         | 
| 12 | 
            +
            require "pycall/set"
         | 
| 13 | 
            +
            require "pycall/slice"
         | 
| 14 | 
            +
            require "pycall/utils"
         | 
| 15 | 
            +
            require "pycall/init"
         | 
| @@ -0,0 +1,120 @@ | |
| 1 | 
            +
            module PyCall
         | 
| 2 | 
            +
              module Conversions
         | 
| 3 | 
            +
                def self.from_ruby(obj)
         | 
| 4 | 
            +
                  case obj
         | 
| 5 | 
            +
                  when PyObject
         | 
| 6 | 
            +
                    obj
         | 
| 7 | 
            +
                  when PyObjectWrapper
         | 
| 8 | 
            +
                    obj.__pyobj__
         | 
| 9 | 
            +
                  when TrueClass, FalseClass
         | 
| 10 | 
            +
                    LibPython.PyBool_FromLong(obj ? 1 : 0)
         | 
| 11 | 
            +
                  when Integer
         | 
| 12 | 
            +
                    LibPython.PyInt_FromSsize_t(obj)
         | 
| 13 | 
            +
                  when Float
         | 
| 14 | 
            +
                    LibPython.PyFloat_FromDouble(obj)
         | 
| 15 | 
            +
                  when String
         | 
| 16 | 
            +
                    case obj.encoding
         | 
| 17 | 
            +
                    when Encoding::US_ASCII, Encoding::BINARY
         | 
| 18 | 
            +
                      LibPython.PyString_FromStringAndSize(obj, obj.bytesize)
         | 
| 19 | 
            +
                    else
         | 
| 20 | 
            +
                      obj = obj.encode(Encoding::UTF_8)
         | 
| 21 | 
            +
                      LibPython.PyUnicode_DecodeUTF8(obj, obj.bytesize, nil)
         | 
| 22 | 
            +
                    end
         | 
| 23 | 
            +
                  when Symbol
         | 
| 24 | 
            +
                    from_ruby(obj.to_s)
         | 
| 25 | 
            +
                  when Array
         | 
| 26 | 
            +
                    PyCall::List.new(obj).__pyobj__
         | 
| 27 | 
            +
                  else
         | 
| 28 | 
            +
                    LibPython.Py_None
         | 
| 29 | 
            +
                  end
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def self.convert_to_boolean(py_obj)
         | 
| 33 | 
            +
                  0 != LibPython.PyInt_AsSsize_t(py_obj)
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def self.convert_to_integer(py_obj)
         | 
| 37 | 
            +
                  LibPython.PyInt_AsSsize_t(py_obj)
         | 
| 38 | 
            +
                end
         | 
| 39 | 
            +
             | 
| 40 | 
            +
                def self.convert_to_float(py_obj)
         | 
| 41 | 
            +
                  LibPython.PyFloat_AsDouble(py_obj)
         | 
| 42 | 
            +
                end
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                def self.convert_to_complex(py_obj)
         | 
| 45 | 
            +
                  real = LibPython.PyComplex_RealAsDouble(py_obj)
         | 
| 46 | 
            +
                  imag = LibPython.PyComplex_ImagAsDouble(py_obj)
         | 
| 47 | 
            +
                  Complex(real, imag)
         | 
| 48 | 
            +
                end
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                def self.convert_to_string(py_obj)
         | 
| 51 | 
            +
                  FFI::MemoryPointer.new(:string) do |str_ptr|
         | 
| 52 | 
            +
                    FFI::MemoryPointer.new(:int) do |len_ptr|
         | 
| 53 | 
            +
                      res = LibPython.PyString_AsStringAndSize(py_obj, str_ptr, len_ptr)
         | 
| 54 | 
            +
                      return nil if res == -1  # FIXME: error
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                      len = len_ptr.get(:int, 0)
         | 
| 57 | 
            +
                      return str_ptr.get_pointer(0).read_string(len)
         | 
| 58 | 
            +
                    end
         | 
| 59 | 
            +
                  end
         | 
| 60 | 
            +
                end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                def self.convert_to_array(py_obj, force_list: true, array_class: Array)
         | 
| 63 | 
            +
                  case
         | 
| 64 | 
            +
                  when force_list || py_obj.kind_of?(LibPython.PyList_Type)
         | 
| 65 | 
            +
                    len = LibPython.PySequence_Size(py_obj)
         | 
| 66 | 
            +
                    array_class.new(len) do |i|
         | 
| 67 | 
            +
                      LibPython.PySequence_GetItem(py_obj, i).to_ruby
         | 
| 68 | 
            +
                    end
         | 
| 69 | 
            +
                  end
         | 
| 70 | 
            +
                end
         | 
| 71 | 
            +
             | 
| 72 | 
            +
                def self.convert_to_tuple(py_obj)
         | 
| 73 | 
            +
                  PyCall::Tuple.new(py_obj)
         | 
| 74 | 
            +
                end
         | 
| 75 | 
            +
              end
         | 
| 76 | 
            +
             | 
| 77 | 
            +
              class PyObject
         | 
| 78 | 
            +
                def to_ruby
         | 
| 79 | 
            +
                  return nil if self.null? || self.py_none?
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                  case self
         | 
| 82 | 
            +
                  when LibPython.PyBool_Type
         | 
| 83 | 
            +
                    return Conversions.convert_to_boolean(self)
         | 
| 84 | 
            +
             | 
| 85 | 
            +
                  when LibPython.PyInt_Type
         | 
| 86 | 
            +
                    return Conversions.convert_to_integer(self)
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                  when LibPython.PyLong_Type
         | 
| 89 | 
            +
                    # TODO: should make Bignum
         | 
| 90 | 
            +
             | 
| 91 | 
            +
                  when LibPython.PyFloat_Type
         | 
| 92 | 
            +
                    return Conversions.convert_to_float(self)
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                  when LibPython.PyComplex_Type
         | 
| 95 | 
            +
                    return Conversions.convert_to_complex(self)
         | 
| 96 | 
            +
             | 
| 97 | 
            +
                  when LibPython.PyString_Type
         | 
| 98 | 
            +
                    return Conversions.convert_to_string(self)
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                  when LibPython.PyUnicode_Type
         | 
| 101 | 
            +
                    py_str_ptr = LibPython.PyUnicode_AsUTF8String(self)
         | 
| 102 | 
            +
                    return Conversions.convert_to_string(py_str_ptr).force_encoding(Encoding::UTF_8)
         | 
| 103 | 
            +
             | 
| 104 | 
            +
                  when LibPython.PyList_Type
         | 
| 105 | 
            +
                    return Conversions.convert_to_array(self)
         | 
| 106 | 
            +
             | 
| 107 | 
            +
                  when LibPython.PyTuple_Type
         | 
| 108 | 
            +
                    return Conversions.convert_to_tuple(self)
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                  when LibPython.PyDict_Type
         | 
| 111 | 
            +
                    return PyCall::Dict.new(self)
         | 
| 112 | 
            +
             | 
| 113 | 
            +
                  when LibPython.PySet_Type
         | 
| 114 | 
            +
                    return PyCall::Set.new(self)
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 117 | 
            +
                  self
         | 
| 118 | 
            +
                end
         | 
| 119 | 
            +
              end
         | 
| 120 | 
            +
            end
         |