halton 0.2.1

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 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: []