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