halton 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.rdoc +45 -0
- data/ext/halton/Cargo.toml +13 -0
- data/ext/halton/Rakefile +106 -0
- data/ext/halton/src/lib.rs +88 -0
- data/lib/halton.rb +144 -0
- metadata +73 -0
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.
|
data/ext/halton/Rakefile
ADDED
@@ -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: []
|