halton 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 393f4cb4ab8e899f4c8db51c402096022d3adaf2094f201a450029ca0aec9bfa
4
+ data.tar.gz: dea785edce51a7a2249f209ff744748fc77ea426f34de9aa318d4acee5b3a814
5
+ SHA512:
6
+ metadata.gz: 9174a270ab128d1bb8c1b67b2859155155c8e7f3bd26f15b58b6d92d2b3ccd5c68d15cc788b4118cbf9526ba711e96cce844eb38f82274826756bfb6eaada0df
7
+ data.tar.gz: 62665649ae6e0c70da6bcbbe70a9cde2257a4df7006815cc1d3382025758dd3c809b09042ecb8e72a451916fcacff341fa31f198909398fd2e1df35475f80838
data/README.rdoc ADDED
@@ -0,0 +1,45 @@
1
+ = Halton
2
+
3
+ A Ruby library for the fast generation of Halton sequences, a deterministic low
4
+ discrepancy sequence that appears to be random. The uniform distribution and
5
+ repeatability makes the sequence ideal for choosing sample points or placing
6
+ objects in 2D or 3D space.
7
+
8
+ grid = 10.times.map {10.times.map {"."}}
9
+ Halton.each(2, 3).take(26).zip("A".."Z") do |(x, y), c|
10
+ grid[y * 10][x * 10] = c
11
+ end
12
+ grid.each {|row| puts row.join(" ")}
13
+
14
+ Outputs:
15
+
16
+ . . R . . I . . . .
17
+ . L . . . . U C . .
18
+ X . . F . . . . . O
19
+ . . . J . A . . . .
20
+ . D . . . . M S . .
21
+ P . . . V . . . G .
22
+ . . B . . Y . . . .
23
+ . T . . . . E . K .
24
+ H . . . N . . . . W
25
+ . . . Z . Q . . . .
26
+
27
+ The method of generation is adapted from "Fast, portable, and reliable
28
+ algorithm for the calculation of Halton numbers" by Miroslav Kolář and Seamus F.
29
+ O'Shea.
30
+
31
+ == Install
32
+
33
+ Install via Rubygems:
34
+
35
+ gem install halton
36
+
37
+ or add it to your Gemfile if you're using Bundler:
38
+
39
+ gem "halton"
40
+
41
+ and then run the bundle command to install.
42
+
43
+ This gem requires a Rust compiler and the +cargo+ build tool to build the gem's
44
+ native extension. See https://www.rust-lang.org/tools/install for how to
45
+ install Rust. +cargo+ is usually part of the Rust installation.
@@ -0,0 +1,13 @@
1
+ [package]
2
+ name = "halton"
3
+ version = "0.1.0"
4
+ authors = ["Mat Sadler <mat@sourcetagsandcodes.com>"]
5
+ edition = "2018"
6
+
7
+ [lib]
8
+ crate-type = ["cdylib"]
9
+
10
+ [dependencies]
11
+ halton = "0.2.1"
12
+ lazy_static = "1"
13
+ rutie = "0.8"
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ class RakeCargoHelper
4
+ attr_reader :gemname
5
+
6
+ def initialize(gemname)
7
+ @gemname = gemname
8
+ end
9
+
10
+ def self.command?(name)
11
+ exts = ENV["PATHEXT"] ? ENV["PATHEXT"].split(";") : [""]
12
+ ENV["PATH"].split(File::PATH_SEPARATOR).any? do |path|
13
+ exts.any? do |ext|
14
+ exe = File.join(path, "#{name}#{ext}")
15
+ File.executable?(exe) && !File.directory?(exe)
16
+ end
17
+ end
18
+ end
19
+
20
+ def self.rust_toolchain
21
+ str = `rustc --version --verbose`
22
+ info = str.lines.map {|l| l.chomp.split(/:\s+/, 2)}.drop(1).to_h
23
+ info["host"]
24
+ end
25
+
26
+ def self.cargo_target_dir
27
+ return @cargo_target_dir if defined? @cargo_target_dir
28
+
29
+ str = `cargo metadata --format-version 1 --offline --no-deps --quiet`
30
+ begin
31
+ require "json"
32
+ dir = JSON.parse(str)["target_directory"]
33
+ rescue LoadError # json is usually part of the stdlib, but just in case
34
+ /"target_directory"\s*:\s*"(?<dir>[^"]*)"/ =~ str
35
+ end
36
+ @cargo_target_dir = dir || "target"
37
+ end
38
+
39
+ def install_dir
40
+ File.expand_path(File.join("..", "..", "lib", gemname), __dir__)
41
+ end
42
+
43
+ def rust_name
44
+ prefix = "lib" unless Gem.win_platform?
45
+ suffix = RbConfig::CONFIG["target_os"] =~ /darwin/i ? ".dylib" : ".so"
46
+ "#{prefix}#{gemname}#{suffix}"
47
+ end
48
+
49
+ def ruby_name
50
+ "#{gemname}.#{RbConfig::CONFIG["DLEXT"]}"
51
+ end
52
+
53
+ end
54
+
55
+ task default: [:install, :clean]
56
+
57
+ desc "set dev mode for subsequent task, run like `rake dev install`"
58
+ task :dev do
59
+ @dev = true
60
+ end
61
+
62
+ desc "build gem native extension and copy to lib"
63
+ task install: [:cd, :build] do
64
+ helper = RakeCargoHelper.new("halton")
65
+ profile_dir = @dev ? "debug" : "release"
66
+ source = File.join(RakeCargoHelper.cargo_target_dir, profile_dir, helper.rust_name)
67
+ dest = File.join(helper.install_dir, helper.ruby_name)
68
+ mkdir_p(helper.install_dir)
69
+ cp(source, dest)
70
+ end
71
+
72
+ desc "build gem native extension"
73
+ task build: [:cargo, :cd] do
74
+ sh "cargo", "build", *("--release" unless @dev)
75
+ end
76
+
77
+ desc "clean up release build artifacts"
78
+ task clean: [:cargo, :cd] do
79
+ # sh "cargo clean --release"
80
+ end
81
+
82
+ desc "clean up build artifacts"
83
+ task clobber: [:cargo, :cd] do
84
+ sh "cargo clean"
85
+ end
86
+
87
+ desc "check for cargo"
88
+ task :cargo do
89
+ raise <<-MSG unless RakeCargoHelper.command?("cargo")
90
+
91
+ This gem requires a Rust compiler and the `cargo' build tool to build the
92
+ gem's native extension. See https://www.rust-lang.org/tools/install for
93
+ how to install Rust. `cargo' is usually part of the Rust installation.
94
+ MSG
95
+
96
+ raise <<-MSG if Gem.win_platform? && RakeCargoHelper.rust_toolchain !~ /gnu/
97
+
98
+ Found Rust toolchain `#{RakeCargoHelper.rust_toolchain}' but the gem native
99
+ extension requires the gnu toolchain on Windows.
100
+ MSG
101
+ end
102
+
103
+ # ensure task is running in the right dir
104
+ task :cd do
105
+ cd(__dir__) unless __dir__ == pwd
106
+ end
@@ -0,0 +1,88 @@
1
+ use lazy_static::lazy_static;
2
+ use rutie::{
3
+ class, methods, module, wrappable_struct, AnyObject, Class, Float, Integer, Module, NilClass,
4
+ Object, VM,
5
+ };
6
+
7
+ macro_rules! unwrap_raise {
8
+ ($val:expr) => {
9
+ #[allow(clippy::redundant_closure)]
10
+ $val.map_err(|e| VM::raise_ex(e))
11
+ .expect("exception should have been raised")
12
+ };
13
+ }
14
+
15
+ macro_rules! raise {
16
+ ($class:ty, $($arg:tt)*) => {{
17
+ VM::raise(Class::from_existing(stringify!($class)), &format!($($arg)*));
18
+ unreachable!()
19
+ }};
20
+ }
21
+
22
+ fn any_object_or_nil<T>(obj: Option<T>) -> AnyObject
23
+ where
24
+ T: Into<AnyObject>,
25
+ {
26
+ obj.map(Into::into)
27
+ .unwrap_or_else(|| NilClass::new().into())
28
+ }
29
+
30
+ module!(Halton);
31
+
32
+ methods!(
33
+ Halton,
34
+ rtself,
35
+ fn halton_number(base: Integer, index: Integer) -> Float {
36
+ Float::new(halton::number(
37
+ unwrap_raise!(base).to_u64() as u8,
38
+ unwrap_raise!(index).to_u64() as usize,
39
+ ))
40
+ }
41
+ );
42
+
43
+ wrappable_struct!(halton::Sequence, SequenceWrapper, SEQUENCE_WRAPPER);
44
+
45
+ class!(Sequence);
46
+
47
+ methods!(
48
+ Sequence,
49
+ rtself,
50
+ fn halton_sequence_new(base: Integer) -> AnyObject {
51
+ let seq = halton::Sequence::new(unwrap_raise!(base).to_u64() as u8);
52
+ Module::from_existing("Halton")
53
+ .get_nested_class("Sequence")
54
+ .wrap_data(seq, &*SEQUENCE_WRAPPER)
55
+ },
56
+ fn halton_sequence_next() -> Float {
57
+ let seq = rtself.get_data_mut(&*SEQUENCE_WRAPPER);
58
+ match seq.next() {
59
+ Some(f) => Float::new(f),
60
+ None => raise!(StopIteration, "iteration reached an end"),
61
+ }
62
+ },
63
+ fn halton_sequence_skip(n: Integer) -> NilClass {
64
+ let seq = rtself.get_data_mut(&*SEQUENCE_WRAPPER);
65
+ seq.nth(unwrap_raise!(n).to_u64() as usize);
66
+ NilClass::new()
67
+ },
68
+ fn halton_sequence_remaining() -> AnyObject {
69
+ let seq = rtself.get_data(&*SEQUENCE_WRAPPER);
70
+ any_object_or_nil(seq.size_hint().1.map(|i| Integer::new(i as i64)))
71
+ }
72
+ );
73
+
74
+ #[allow(non_snake_case)]
75
+ #[no_mangle]
76
+ pub extern "C" fn Init_halton() {
77
+ Module::new("Halton").define(|module| {
78
+ module.def_self("number", halton_number);
79
+ module
80
+ .define_nested_class("Sequence", None)
81
+ .define(|class| {
82
+ class.def_self("new", halton_sequence_new);
83
+ class.def("next", halton_sequence_next);
84
+ class.def("skip", halton_sequence_skip);
85
+ class.def("remaining", halton_sequence_remaining);
86
+ });
87
+ });
88
+ }
data/lib/halton.rb ADDED
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "halton/halton"
4
+
5
+ # The Halton module provides methods for generating Halton sequences, a
6
+ # deterministic low discrepancy sequence that appears to be random. The uniform
7
+ # distribution and repeatability makes the sequence ideal for choosing sample
8
+ # points or placing objects in 2D or 3D space.
9
+ #
10
+ # grid = 10.times.map {10.times.map {"."}}
11
+ # Halton.each(2, 3).take(26).zip("A".."Z") do |(x, y), c|
12
+ # grid[y * 10][x * 10] = c
13
+ # end
14
+ # grid.each {|row| puts row.join(" ")}
15
+ #
16
+ # Outputs:
17
+ #
18
+ # . . R . . I . . . .
19
+ # . L . . . . U C . .
20
+ # X . . F . . . . . O
21
+ # . . . J . A . . . .
22
+ # . D . . . . M S . .
23
+ # P . . . V . . . G .
24
+ # . . B . . Y . . . .
25
+ # . T . . . . E . K .
26
+ # H . . . N . . . . W
27
+ # . . . Z . Q . . . .
28
+ #
29
+ module Halton
30
+
31
+ ##
32
+ # :singleton-method: number
33
+ # :call-seq: Halton.number(base, index) -> int
34
+ #
35
+ # Returns the number at +index+ of the Halton sequence for +base+. The number
36
+ # returned will be > 0 and < 1, assuming +index+ > 1.
37
+ #
38
+ # While #each will be faster for most cases, this function may be
39
+ # useful for calulating a single number from a Halton sequence, or creating
40
+ # a 'leaped' sequence.
41
+ #
42
+ # 'leaped' Halton sequence:
43
+ #
44
+ # step = 409;
45
+ # i = 1;
46
+ # while i < 10 * step
47
+ # puts Halton.number(17, i)
48
+ # i += step
49
+ # end
50
+ #
51
+ # Beware that indexing #each is effectively 0-based, whereas the
52
+ # `index` argument for [`number`] is 1-based.
53
+ #
54
+ # Halton.each(2).take(10)[2] #=> 0.75
55
+ # Halton.number(2, 3) #=> 0.75
56
+
57
+ # :call-seq:
58
+ # Halton.each(base) {|n| ... }
59
+ # Halton.each(base_x, base_y) {|x, y| ... }
60
+ # Halton.each(base_x, base_y, base_z) {|x, y, z| ... }
61
+ # Halton.each(*bases) {|*n| ... }
62
+ # Halton.each(*bases) -> Enumerator
63
+ #
64
+ # Implements the fast generation of Halton sequences.
65
+ # The method of generation is adapted from "Fast, portable, and reliable
66
+ # algorithm for the calculation of Halton numbers" by Miroslav Kolář and
67
+ # Seamus F. O'Shea.
68
+ #
69
+ # The numbers yielded will be in the range > 0 and < 1.
70
+ #
71
+ def self.each(base, *bases)
72
+ return to_enum(__method__, base, *bases) unless block_given?
73
+
74
+ if bases.empty?
75
+ seq = Sequence.new(base)
76
+ loop {yield seq.next}
77
+ return nil
78
+ end
79
+
80
+ if bases.length == 1
81
+ x = Sequence.new(base)
82
+ y = Sequence.new(bases.first)
83
+ loop {yield x.next, y.next}
84
+ return nil
85
+ end
86
+
87
+ if bases.length == 2
88
+ x = Sequence.new(base)
89
+ y = Sequence.new(bases.first)
90
+ z = Sequence.new(bases.last)
91
+ loop {yield x.next, y.next, z.next}
92
+ return nil
93
+ end
94
+
95
+ seqs = bases.unshift(base).map {|b| Sequence.new(b)}
96
+ loop {yield(*seqs.map(&:next))}
97
+ nil
98
+ end
99
+
100
+ # Halton::Sequence implements the fast generation of Halton sequences.
101
+ # The method of generation is adapted from "Fast, portable, and reliable
102
+ # algorithm for the calculation of Halton numbers" by Miroslav Kolář and
103
+ # Seamus F. O'Shea.
104
+ #
105
+ # This class is implemented as a stateful iterator, a pattern not common in
106
+ # Ruby. The Halton::each method provides a more friendly interface to this
107
+ # class.
108
+ #
109
+ class Sequence
110
+
111
+ ##
112
+ # :singleton-method: new
113
+ # :call-seq: Sequence.new(base) -> sequence
114
+ #
115
+ # Create a new Halton::Sequence.
116
+
117
+ ##
118
+ # :method: next
119
+ # :call-seq: sequence.next -> int
120
+ #
121
+ # Get the next number in the sequence. The numbers will be in the range > 0
122
+ # and < 1.
123
+ #
124
+ # Will raise StopIteration when the sequence has ended.
125
+
126
+ ##
127
+ # :method: skip
128
+ # :call-seq: sequence.skip(n) -> nil
129
+ #
130
+ # Can be used to efficiently skip large sections of the sequence. For small
131
+ # values of +n+ simply advances the sequence +n+ times.
132
+ #
133
+
134
+ ##
135
+ # :method: remaining
136
+ # :call-seq: sequence.remaining -> int or nil
137
+ #
138
+ # Returns the count of the remaining numbers in the sequence. May return
139
+ # +nil+ if the count is larger than the host platform's native unsigned
140
+ # integer type.
141
+
142
+ end
143
+
144
+ end
metadata ADDED
@@ -0,0 +1,73 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: halton
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Mat Sadler
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2021-05-03 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rake
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ description: A module implementing the fast generation of Halton sequences. The method
28
+ of generation is adapted from "Fast, portable, and reliable algorithm for the calculation
29
+ of Halton number" by Miroslav Kolář and Seamus F. O'Shea.
30
+ email:
31
+ - mat@sourcetagsandcodes.com
32
+ executables: []
33
+ extensions:
34
+ - ext/halton/Rakefile
35
+ extra_rdoc_files:
36
+ - README.rdoc
37
+ files:
38
+ - README.rdoc
39
+ - ext/halton/Cargo.toml
40
+ - ext/halton/Rakefile
41
+ - ext/halton/src/lib.rs
42
+ - lib/halton.rb
43
+ homepage: https://github.com/matsadler/halton-rb
44
+ licenses:
45
+ - MIT
46
+ metadata: {}
47
+ post_install_message:
48
+ rdoc_options:
49
+ - "--main"
50
+ - README.rdoc
51
+ - "--charset"
52
+ - utf-8
53
+ - "--exclude"
54
+ - ext/**
55
+ require_paths:
56
+ - lib
57
+ required_ruby_version: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ required_rubygems_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ requirements:
68
+ - Rust >= 1.51.0
69
+ rubygems_version: 3.2.3
70
+ signing_key:
71
+ specification_version: 4
72
+ summary: A module for generating Halton sequences
73
+ test_files: []