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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9dfb955ab7f7606b2e9b757dc3c5f7b8aaa3b90b
4
- data.tar.gz: f2c110fd87edf0ed41eaaf150c8cef2bb45c5709
3
+ metadata.gz: 6d6ddea53f723b45bf9207c52746afaa083fba57
4
+ data.tar.gz: 1739c9f4e3a5d72503427540708f435694dea5aa
5
5
  SHA512:
6
- metadata.gz: 6203262df5efc0a06c35da7e17e40170ed87b537b4c718c786b981524547f05a4c3ee9cd238927e8028125344afcabf319fbe16ada076cad8d7b004c60926c35
7
- data.tar.gz: 592f154d6598fbf7528bd0302f857430f51727536e776b2b035fbef7a55ac32602e931fe6235bdf58f3a44b62c7842b7cfbb7319fa9da59813ed9cc500a8db4b
6
+ metadata.gz: '08a91ee3157d872d18725d57ace058d991feb1855250307d9b9cd17feab5081a96ed03ab05941ecec073c22c3a63b2ed8c8c777c83735459e9e7b8e10e7220d7'
7
+ data.tar.gz: 2359c6d09f3bc687466e41c0e0de2287a89bd4b11587e3384d4a41dd0eb737cf75af387d149efbb8322baa69136d4483f3901728e6b2b36545a9dbc5e6fe8d35
@@ -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
- before_install: gem install bundler -v 1.13.2
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
@@ -2,3 +2,8 @@ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in pycall.gemspec
4
4
  gemspec
5
+
6
+ group :development do
7
+ gem 'guard-rspec', require: false
8
+ gem 'terminal-notifier-guard', require: false
9
+ end
@@ -0,0 +1 @@
1
+ ./config/Guardfile
data/README.md CHANGED
@@ -1,4 +1,6 @@
1
- # Pycall
1
+ # PyCall
2
+
3
+ [![Build Status](https://travis-ci.org/mrkn/pycall.svg?branch=master)](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/[USERNAME]/pycall.
37
+ Bug reports and pull requests are welcome on GitHub at https://github.com/mrkn/pycall.
36
38
 
37
39
 
38
40
  ## License
@@ -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")
@@ -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")
@@ -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.()
@@ -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.()
@@ -1,5 +1,15 @@
1
1
  require "pycall/version"
2
-
3
- module Pycall
4
- # Your code goes here...
5
- end
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