use random oracle for randomized satin columns and redo split stitches

pull/1918/head
George Steel 2022-12-26 20:13:48 -05:00
rodzic b63f19b2d0
commit e28ea888a9
10 zmienionych plików z 262 dodań i 150 usunięć

Wyświetl plik

@ -3,8 +3,8 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import random
from copy import deepcopy
import itertools
from itertools import chain
import numpy as np
@ -16,7 +16,8 @@ from shapely.ops import nearest_points
from ..i18n import _
from ..stitch_plan import StitchGroup
from ..svg import line_strings_to_csp, point_lists_to_csp
from ..utils import Point, cache, cut, cut_multiple
from ..utils import Point, cache, cut, cut_multiple, prng
from ..stitches import running_stitch
from .element import EmbroideryElement, param, PIXELS_PER_MM
from .validation import ValidationError, ValidationWarning
@ -85,48 +86,68 @@ class SatinColumn(EmbroideryElement):
_('Maximum stitch length'),
tooltip=_('Maximum stitch length for split stitches.'),
type='float', unit="mm")
def max_stitch_length(self):
def max_stitch_length_px(self):
return self.get_float_param("max_stitch_length_mm") or None
@property
@param('random_split_factor',
_('Random Split Factor'),
tooltip=_('randomize position for split stitches.'),
type='int', unit="%", sort_index=70)
def random_split_factor(self):
return min(max(self.get_int_param("random_split_factor", 0), 0), 100)
@property
@param('random_zigzag_spacing',
_('Zig-zag spacing randomness(peak-to-peak)'),
tooltip=_('percentage of randomness of Peak-to-peak distance between zig-zags.'),
type='int', unit="%", sort_index=64)
def random_zigzag_spacing(self):
# peak-to-peak distance between zigzags
return max(self.get_int_param("random_zigzag_spacing", 0), 0)
@property
@param('random_width_decrease_percent',
_('Random percentage of satin width decrease'),
tooltip=_('shorten stitch across rails at most this percent.'
tooltip=_('shorten stitch across rails at most this percent. '
'Two values separated by a space may be used for an aysmmetric effect.'),
type='int', unit="% (each side)", sort_index=60)
default=0, type='float', unit="% (each side)", sort_index=91)
@cache
def random_width_decrease_percent(self):
return self.get_split_float_param("random_width_decrease_percent", (0, 0))
@property
@param('random_width_increase_percent',
_('Random percentage of satin width increase'),
tooltip=_('lengthen stitch across rails at most this percent.'
tooltip=_('lengthen stitch across rails at most this percent. '
'Two values separated by a space may be used for an aysmmetric effect.'),
type='int', unit="% (each side)", sort_index=60)
default=0, type='float', unit="% (each side)", sort_index=90)
@cache
def random_width_increase_percent(self):
return self.get_split_float_param("random_width_increase_percent", (0, 0))
@property
@param('random_zigzag_spacing',
_('Random zig-zag spacing percentage increase'),
tooltip=_('Percentage of stitch length added randomply Peak-to-peak distance between zig-zags.'),
default=0, type='float', unit="%", sort_index=92)
def random_zigzag_spacing(self):
# peak-to-peak distance between zigzags
return max(self.get_float_param("random_zigzag_spacing", 0), 0)
@property
@param('random_split_phase',
_('Random phase for split stitches'),
tooltip=_('Controls whether split stitches are centered or with a random phase (which may increase stitch count).'),
default=False, type='boolean', sort_index=95)
def random_split_phase(self):
return self.get_boolean_param('random_split_phase')
@property
@param('min_random_split_length_mm',
_('Minimum length for random-phase split.'),
tooltip=_('Defaults to maximum stitch length. Smaller values allow for a transition between single-stitch and split-stitch.'),
default='', type='float', unit='mm', sort_index=96)
def min_random_split_length_px(self):
if self.max_stitch_length_px is None:
return None
return min(self.max_stitch_length_px, self.get_float_param('min_random_split_length_mm', self.max_stitch_length_px))
@property
@param('random_split_length_percent',
_('Random jitter split stitch'),
tooltip=_('randomizes split stitch length if random phase is emabled, stitch position if disabled.'),
type='float', unit="%", sort_index=97)
def random_split_length(self):
return min(max(self.get_float_param("random_split_length_percent", 0), 0), 100) / 100
@property
@param('short_stitch_inset',
_('Short stitch inset'),
tooltip=_('Stitches in areas with high density will be shortened by this amount.'),
tooltip=_('Stitches in areas with high density will be inset by this amount.'),
type='float', unit="%",
default=15)
def short_stitch_inset(self):
@ -135,7 +156,7 @@ class SatinColumn(EmbroideryElement):
@property
@param('short_stitch_distance_mm',
_('Short stitch distance'),
tooltip=_('Do short stitches if the distance between stitches is smaller than this.'),
tooltip=_('Inset stitches if the distance between stitches is smaller than this.'),
type='float', unit="mm",
default=0.25)
def short_stitch_distance(self):
@ -328,10 +349,6 @@ class SatinColumn(EmbroideryElement):
def zigzag_underlay_max_stitch_length(self):
return self.get_float_param("zigzag_underlay_max_stitch_length_mm") or None
@property
def use_seed(self):
return self.get_int_param("use_seed", 0)
@property
@cache
def shape(self):
@ -601,7 +618,7 @@ class SatinColumn(EmbroideryElement):
"""
# like in do_satin()
points = list(chain.from_iterable(zip(*self.plot_points_on_rails(self.zigzag_spacing))))
points = list(chain.from_iterable(self.plot_points_on_rails(self.zigzag_spacing)))
if isinstance(split_point, float):
index_of_closest_stitch = int(round(len(points) * split_point))
@ -689,7 +706,7 @@ class SatinColumn(EmbroideryElement):
@cache
def center_line(self):
# similar technique to do_center_walk()
center_walk, _ = self.plot_points_on_rails(self.zigzag_spacing, (0, 0), (-0.5, -0.5))
center_walk = [p[0] for p in self.plot_points_on_rails(self.zigzag_spacing, (0, 0), (-0.5, -0.5))]
return shgeo.LineString(center_walk)
def offset_points(self, pos1, pos2, offset_px, offset_proportional):
@ -752,19 +769,22 @@ class SatinColumn(EmbroideryElement):
distance_remaining -= segment_length
pos = segment_end
def plot_points_on_rails(self, spacing, offset_px=(0, 0), offset_proportional=(0, 0)):
def plot_points_on_rails(self, spacing, offset_px=(0, 0), offset_proportional=(0, 0), use_random=False) -> list[tuple[Point, Point]]:
# Take a section from each rail in turn, and plot out an equal number
# of points on both rails. Return the points plotted. The points will
# be contracted or expanded by offset using self.offset_points().
def add_pair(pos0, pos1):
pos0, pos1 = self.offset_points(pos0, pos1, offset_px, offset_proportional)
points[0].append(pos0)
points[1].append(pos1)
# pre-cache ramdomised parameters to avoid property calls in loop
if use_random:
seed = prng.joinArgs(self.random_seed, "satin-points")
offset_proportional_min = np.array(offset_proportional) - self.random_width_decrease_percent/100
offset_range = (self.random_width_increase_percent + self.random_width_decrease_percent) / 100
spacing_range = spacing * self.random_zigzag_spacing / 100
points = [[], []]
pairs = []
to_travel = 0
cycle = 0
for section0, section1 in self.flattened_sections:
# Take one section at a time, delineated by the rungs. For each
@ -827,26 +847,32 @@ class SatinColumn(EmbroideryElement):
old_center = new_center
if to_travel <= 0:
if use_random:
roll = prng.uniformFloats(seed, cycle)
offset_prop = offset_proportional_min + roll[0:2] * offset_range
to_travel = spacing + roll[2] * spacing_range
else:
offset_prop = offset_proportional
to_travel = spacing
mismatch0 = random.uniform(-self.random_width_decrease_percent[0], self.random_width_increase_percent[0]) / 100
mismatch1 = random.uniform(-self.random_width_decrease_percent[1], self.random_width_increase_percent[1]) / 100
add_pair(pos0 + (pos0 - pos1) * mismatch0, pos1 + (pos1 - pos0) * mismatch1)
to_travel = spacing * (random.uniform(1, 1 + self.random_zigzag_spacing/100))
a, b = self.offset_points(pos0, pos1, offset_px, offset_prop)
pairs.append((a, b))
cycle += 1
if to_travel > 0:
add_pair(pos0, pos1)
pairs.append((pos0, pos1))
return points
return pairs
def do_contour_underlay(self):
# "contour walk" underlay: do stitches up one side and down the
# other.
forward, back = self.plot_points_on_rails(
pairs = self.plot_points_on_rails(
self.contour_underlay_stitch_length,
-self.contour_underlay_inset_px, -self.contour_underlay_inset_percent/100)
stitches = (forward + list(reversed(back)))
stitches = [p[0] for p in pairs] + [p[1] for p in reversed(pairs)]
if self._center_walk_is_odd():
stitches = (list(reversed(back)) + forward)
stitches = list(reversed(stitches))
return StitchGroup(
color=self.color,
@ -860,16 +886,16 @@ class SatinColumn(EmbroideryElement):
inset_prop = -np.array([self.center_walk_underlay_position, 100-self.center_walk_underlay_position]) / 100
# Do it like contour underlay, but inset all the way to the center.
forward, back = self.plot_points_on_rails(
pairs = self.plot_points_on_rails(
self.center_walk_underlay_stitch_length,
(0, 0), inset_prop)
stitches = []
for i in range(self.center_walk_underlay_repeats):
if i % 2 == 0:
stitches += forward
stitches += [p[0] for p in pairs]
else:
stitches += list(reversed(back))
stitches += [p[1] for p in reversed(pairs)]
return StitchGroup(
color=self.color,
@ -889,27 +915,26 @@ class SatinColumn(EmbroideryElement):
patch = StitchGroup(color=self.color)
sides = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0,
pairs = self.plot_points_on_rails(self.zigzag_underlay_spacing / 2.0,
-self.zigzag_underlay_inset_px,
-self.zigzag_underlay_inset_percent/100)
if self._center_walk_is_odd():
sides = [list(reversed(sides[0])), list(reversed(sides[1]))]
pairs = list(reversed(pairs))
# This organizes the points in each side in the order that they'll be
# visited.
sides = [sides[0][::2] + list(reversed(sides[0][1::2])),
sides[1][1::2] + list(reversed(sides[1][::2]))]
# take a points, from each side in turn, then go backed over the other points
points = [p[i % 2] for i, p in enumerate(pairs)] + list(reversed([p[i % 2] for i, p in enumerate(pairs, 1)]))
# This fancy bit of iterable magic just repeatedly takes a point
# from each side in turn.
max_len = self.zigzag_underlay_max_stitch_length
last_point = None
for point in chain.from_iterable(zip(*sides)):
if last_point and self.zigzag_underlay_max_stitch_length:
if last_point.distance(point) > self.zigzag_underlay_max_stitch_length:
points, count = self._get_split_points(last_point, point, self.zigzag_underlay_max_stitch_length)
for point in points:
patch.add_stitch(point)
for point in points:
if last_point and max_len:
if last_point.distance(point) > max_len:
split_points = running_stitch.split_segment_even_dist(last_point, point, max_len)
for p in split_points:
patch.add_stitch(p)
last_point = point
patch.add_stitch(point)
@ -928,28 +953,47 @@ class SatinColumn(EmbroideryElement):
patch = StitchGroup(color=self.color)
# pull compensation is automatically converted from mm to pixels by get_float_param
sides = self.plot_points_on_rails(
pairs = self.plot_points_on_rails(
self.zigzag_spacing,
self.pull_compensation_px,
self.pull_compensation_percent/100
self.pull_compensation_percent/100,
True,
)
if self.max_stitch_length:
return self.do_split_stitch(patch, sides)
max_stitch_length = self.max_stitch_length_px
length_sigma = self.random_split_length
random_phase = self.random_split_phase
min_split_length = self.min_random_split_length_px
seed = self.random_seed
# short stitches are not not included into the split stitch
# they would move the points in a maybe unwanted behaviour
if self.short_stitch_inset > 0:
self._do_short_stitches(sides)
short_pairs = self.inset_short_stitches_sawtooth(pairs)
# Like in zigzag_underlay(): take a point from each side in turn.
for point in chain.from_iterable(zip(*sides)):
patch.add_stitch(point)
last_point = None
last_short_point = None
last_count = None
for i, (a, b), (a_short, b_short) in zip(itertools.count(0), pairs, short_pairs):
if last_point is not None:
split_points, _ = self.get_split_points(
last_point, a, last_short_point, a_short, max_stitch_length, last_count,
length_sigma, random_phase, min_split_length, prng.joinArgs(seed, 'satin-split', 2*i))
patch.add_stitches(split_points, ("satin_column", "satin_split_stitch"))
patch.add_stitch(a_short)
patch.stitches[-1].add_tags(("satin_column", "satin_column_edge"))
split_points, last_count = self.get_split_points(
a, b, a_short, b_short, max_stitch_length, None,
length_sigma, random_phase, min_split_length, prng.joinArgs(seed, 'satin-split', 2*i+1))
patch.add_stitches(split_points, ("satin_column", "satin_split_stitch"))
patch.add_stitch(b_short)
patch.stitches[-1].add_tags(("satin_column", "satin_column_edge"))
last_point = b
last_short_point = b_short
if self._center_walk_is_odd():
patch.stitches = list(reversed(patch.stitches))
patch.add_tags(("satin_column", "satin_column_edge"))
return patch
def do_e_stitch(self):
@ -962,15 +1006,16 @@ class SatinColumn(EmbroideryElement):
patch = StitchGroup(color=self.color)
sides = self.plot_points_on_rails(
pairs = self.plot_points_on_rails(
self.zigzag_spacing,
self.pull_compensation_px,
self.pull_compensation_percent/100
self.pull_compensation_percent/100,
self.random_width_decrease_percent.any() and self.random_width_increase_percent.any() and self.random_zigzag_spacing,
)
# "left" and "right" here are kind of arbitrary designations meaning
# a point from the first and second rail respectively
for left, right in zip(*sides):
for left, right in pairs:
patch.add_stitch(left)
patch.add_stitch(right)
patch.add_stitch(left)
@ -981,54 +1026,49 @@ class SatinColumn(EmbroideryElement):
patch.add_tags(("satin_column", "e_stitch"))
return patch
def do_split_stitch(self, patch, sides):
# stitches exceeding the maximum stitch length will be divided into equal parts through additional stitches
for i, (left, right) in enumerate(zip(*sides)):
patch.add_stitch(left)
patch.stitches[-1].add_tags(("satin_column", "satin_column_edge"))
points, count = self._get_split_points(left, right, self.max_stitch_length)
for point in points:
patch.add_stitch(point)
patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch"))
patch.add_stitch(right)
patch.stitches[-1].add_tags(("satin_column", "satin_column_edge"))
# it is possible that the way back has a different length from the first
# but it looks ugly if the points differ too much
# so let's make sure they have at least the same amount of divisions
if not i+1 >= len(sides[0]):
points, count = self._get_split_points(right, sides[0][i+1], self.max_stitch_length, count)
for point in points:
patch.add_stitch(point)
patch.stitches[-1].add_tags(("satin_column", "satin_split_stitch"))
if self._center_walk_is_odd():
patch.stitches = list(reversed(patch.stitches))
return patch
def get_split_points(self, a, b, a_short, b_short, length, count=None, length_sigma=0.0, random_phase=False, min_split_length=None, seed=None):
if not length:
return ([], None)
if min_split_length is None:
min_split_length = length
distance = a.distance(b)
if distance <= min_split_length:
return ([], 1)
if random_phase:
points = running_stitch.split_segment_random_phase(a_short, b_short, length, length_sigma, seed)
return (points, None)
elif count is not None:
points = running_stitch.split_segment_even_n(a, b, count, length_sigma, seed)
return (points, count)
else:
points = running_stitch.split_segment_even_dist(a, b, length, length_sigma, seed)
return (points, len(points) + 1)
def _get_split_points(self, left, right, max_stitch_length, count=None):
points = []
distance = left.distance(right)
split_count = count or int(-(-distance // max_stitch_length))
random_move = 0
for i in range(split_count):
line = shgeo.LineString((left, right))
def inset_short_stitches_sawtooth(self, pairs):
min_dist = self.short_stitch_distance
inset = min(self.short_stitch_inset, 0.5)
max_stitch_length = None if self.random_split_phase else self.max_stitch_length_px
if not min_dist or not inset:
return pairs
if self.random_split_factor and i != split_count-1:
random_move = random.uniform(-self.random_split_factor / 100, self.random_split_factor / 100)
split_point = line.interpolate((i + 1 + random_move) / split_count, normalized=True)
points.append(Point(split_point.x, split_point.y))
return [points, split_count]
def _do_short_stitches(self, sides):
for i, (left, right) in enumerate(zip(*sides)):
shortened = []
for i, (a, b) in enumerate(pairs):
if i % 2 == 0:
shortened.append((a, b))
continue
if left.distance(sides[0][i-1]) < self.short_stitch_distance:
split_point = self._get_inset_point(left, right, self.short_stitch_inset)
sides[0][i] = Point(split_point.x, split_point.y)
if right.distance(sides[1][i-1]) < self.short_stitch_distance:
split_point = self._get_inset_point(right, left, self.short_stitch_inset)
sides[1][i] = Point(split_point.x, split_point.y)
dist = a.distance(b)
inset_px = inset * dist
if max_stitch_length and not self.random_split_phase:
# make sure inset is less than split etitch length
inset_px = min(inset_px, max_stitch_length / 3)
offset_px = [0, 0]
if a.distance(pairs[i-1][0]) < min_dist:
offset_px[0] = -inset_px
if b.distance(pairs[i-1][0]) < min_dist:
offset_px[1] = -inset_px
shortened.append(self.offset_points(a, b, offset_px, (0, 0)))
return shortened
def _get_inset_point(self, point1, point2, distance_fraction):
return point1 * (1 - distance_fraction) + point2 * distance_fraction
@ -1040,16 +1080,6 @@ class SatinColumn(EmbroideryElement):
# beziers. The boundary points between beziers serve as "checkpoints",
# allowing the user to control how the zigzags flow around corners.
# If no seed is defined, compute one randomly using time to seed, otherwise, use stored seed
if self.use_seed == 0:
random.seed()
x = random.randint(1, 10000)
random.seed(x)
self.set_param("use_seed", x)
else:
random.seed(self.use_seed)
patch = StitchGroup(color=self.color)
if self.center_walk_underlay:

Wyświetl plik

@ -12,7 +12,7 @@ from inkex import Transform
from ..i18n import _
from ..marker import get_marker_elements
from ..stitch_plan import StitchGroup
from ..stitches import bean_stitch, running_stitch
from ..stitches.running_stitch import bean_stitch, running_stitch
from ..stitches.ripple_stitch import ripple_stitch
from ..svg import get_node_transform, parse_length_with_units
from ..utils import Point, cache

Wyświetl plik

@ -4,6 +4,7 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
from ..utils.geometry import Point
from shapely import geometry as shgeo
class Stitch(Point):
@ -17,13 +18,13 @@ class Stitch(Point):
# Allow creating a Stitch from another Stitch. Attributes passed as
# arguments will override any existing attributes.
base_stitch = x
self.x = base_stitch.x
self.y = base_stitch.y
elif isinstance(x, Point):
self.x: float = base_stitch.x
self.y: float = base_stitch.y
elif isinstance(x, (Point, shgeo.Point)):
# Allow creating a Stitch from a Point
point = x
self.x = point.x
self.y = point.y
self.x: float = point.x
self.y: float = point.y
else:
Point.__init__(self, x, y)

Wyświetl plik

@ -43,14 +43,14 @@ class StitchGroup:
# This method allows `len(patch)` and `if patch:
return len(self.stitches)
def add_stitches(self, stitches):
def add_stitches(self, stitches, tags=None):
for stitch in stitches:
self.add_stitch(stitch)
self.add_stitch(stitch, tags=tags)
def add_stitch(self, stitch):
def add_stitch(self, stitch, tags=None):
if not isinstance(stitch, Stitch):
# probably a Point
stitch = Stitch(stitch)
stitch = Stitch(stitch, tags=tags)
self.stitches.append(stitch)

Wyświetl plik

@ -6,7 +6,6 @@
from .auto_fill import auto_fill
from .fill import legacy_fill
from .guided_fill import guided_fill
from .running_stitch import *
# Can't put this here because we get a circular import :(
# from .auto_satin import auto_satin

Wyświetl plik

@ -74,12 +74,12 @@ def _get_satin_ripple_helper_lines(stroke):
length = stroke.grid_size or stroke.running_stitch_length
# use satin column points for satin like build ripple stitches
rail_points = SatinColumn(stroke.node).plot_points_on_rails(length)
rail_pairs = SatinColumn(stroke.node).plot_points_on_rails(length)
steps = _get_steps(stroke.get_line_count(), exponent=stroke.exponent, flip=stroke.flip_exponent)
helper_lines = []
for point0, point1 in zip(*rail_points):
for point0, point1 in rail_pairs:
helper_lines.append([])
helper_line = LineString((point0, point1))
for step in steps:

Wyświetl plik

@ -6,11 +6,48 @@
import math
from copy import copy
from shapely.geometry import LineString
import numpy as np
from shapely import geometry as shgeo
from ..utils import prng
""" Utility functions to produce running stitches. """
def split_segment_even_n(a, b, segments: int, jitter_sigma: float = 0.0, random_seed: str | None = None) -> list[shgeo.Point]:
if segments <= 1:
return []
line = shgeo.LineString((a, b))
splits = np.array(range(1, segments)) / segments
if random_seed is not None:
jitters = (prng.nUniformFloats(len(splits), random_seed) * 2) - 1
splits = splits + jitters * (jitter_sigma / segments)
# sort the splits in case a bad roll transposes any of them
return [line.interpolate(x, normalized=True) for x in sorted(splits)]
def split_segment_even_dist(a, b, max_length: float, jitter_sigma: float = 0.0, random_seed: str | None = None) -> list[shgeo.Point]:
distance = shgeo.Point(a).distance(shgeo.Point(b))
segments = math.ceil(distance / max_length)
return split_segment_even_n(a, b, segments, jitter_sigma, random_seed)
def split_segment_random_phase(a, b, length: float, length_sigma: float, random_seed: str) -> list[shgeo.Point]:
line = shgeo.LineString([a, b])
progress = length * prng.uniformFloats(random_seed, "phase")[0]
splits = [progress]
distance = line.length
if progress >= distance:
return []
for x in prng.iterUniformFloats(random_seed):
progress += length * (1 + length_sigma * (x - 0.5) * 2)
if progress >= distance:
break
splits.append(progress)
return [line.interpolate(x, normalized=False) for x in splits]
def running_stitch(points, stitch_length, tolerance):
"""Generate running stitch along a path.
@ -28,7 +65,7 @@ def running_stitch(points, stitch_length, tolerance):
# simplify will remove as many points as possible while ensuring that the
# resulting path stays within the specified tolerance of the original path.
path = LineString(points)
path = shgeo.LineString(points)
simplified = path.simplify(tolerance, preserve_topology=False)
# save the points that simplify picked and make sure we stitch them
@ -45,7 +82,7 @@ def running_stitch(points, stitch_length, tolerance):
# Now split each section up evenly into stitches, each with a length no
# greater than the specified stitch_length.
section_ls = LineString(section)
section_ls = shgeo.LineString(section)
section_length = section_ls.length
if section_length > stitch_length:
# a fractional stitch needs to be rounded up, which will make all

Wyświetl plik

@ -126,9 +126,10 @@ inkstitch_attribs = [
'stroke_first',
'random_width_decrease_percent',
'random_width_increase_percent',
'random_split_factor',
'random_zigzag_spacing',
'use_seed',
'random_split_phase',
'random_split_length_percent',
'min_random_split_length_mm',
# stitch_plan
'invisible_layers',
# All elements

Wyświetl plik

@ -148,7 +148,7 @@ def cut_path(points, length):
class Point:
def __init__(self, x, y):
def __init__(self, x: float, y: float):
self.x = x
self.y = y

44
lib/utils/prng.py 100644
Wyświetl plik

@ -0,0 +1,44 @@
from hashlib import blake2s
from math import ceil
from itertools import count, chain
import numpy as np
def joinArgs(*args):
# Stringifies parameters into a slash-separated string for use in hash keys.
# Idempotent and associative.
return "/".join([str(x) for x in args])
MAX_UNIFORM_INT = 2 ** 32 - 1
def uniformInts(*args):
# Single pseudo-random drawing determined by the joined parameters.
# Returns 4 uniformly random uint64.
s = joinArgs(*args)
h = blake2s(s.encode()).hexdigest()
nums = []
for i in range(0, 64, 8):
nums.append(int(h[i:i+8], 16))
return np.array(nums)
def uniformFloats(*args):
# returns an array of 8 floats in the range [0,1]
return uniformInts(*args) / MAX_UNIFORM_INT
def nUniformFloats(n: int, *args):
# returns a fixed number (which may exceed 8) of floats in the range [0,1]
seed = joinArgs(*args)
nBlocks = ceil(n/8)
blocks = [uniformFloats(seed, x) for x in range(nBlocks)]
return np.concatenate(blocks)[0:n]
def iterUniformFloats(*args):
# returns an infinite sequence of floats in the range [0,1]
seed = joinArgs(*args)
blocks = map(lambda x: list(uniformFloats(seed, x)), count(0))
return chain.from_iterable(blocks)