1 |
#' @include utils.R |
|
2 |
#' @include collatz_function.R |
|
3 |
NULL |
|
4 | ||
5 |
#' Hailstone Sequencer |
|
6 |
#' |
|
7 |
#' Calculates the values of a hailstone sequence, from an initial value. |
|
8 |
#' |
|
9 |
#' Returns a list of successive values obtained by iterating a Collatz-esque |
|
10 |
#' function, until either 1 is reached, or the total amount of iterations |
|
11 |
#' exceeds max_total_stopping_time, unless total_stopping_time is FALSE, which |
|
12 |
#' will terminate the hailstone at the "stopping time" value, i.e. the first |
|
13 |
#' value less than the initial value. While the sequence has the capability to |
|
14 |
#' determine that it has encountered a cycle, the cycle from "1" wont be |
|
15 |
#' attempted or reported as part of a cycle, regardless of default or custom |
|
16 |
#' parameterisation, as "1" is considered a "total stop". |
|
17 |
#' |
|
18 |
#' @param initial_value (numeric|bigz) |
|
19 |
#' The value to begin the hailstone sequence from. |
|
20 |
#' @param P (numeric|bigz): Modulus used to divide |
|
21 |
#' n, iff n is equivalent to (0 mod P). Default is 2. |
|
22 |
#' @param a (numeric|bigz) Factor by which to multiply n. Default is 3. |
|
23 |
#' @param b (numeric|bigz) Value to add |
|
24 |
#' to the scaled value of n. Default is 1. |
|
25 |
#' @param max_total_stopping_time (int) Maximum amount of times to iterate the |
|
26 |
#' function, if 1 is not reached. Default is 1000. |
|
27 |
#' @param total_stopping_time (bool) Whether or not to execute until the "total" |
|
28 |
#' stopping time (number of iterations to obtain 1) rather than the regular |
|
29 |
#' stopping time (number of iterations to reach a value less than the initial |
|
30 |
#' value). Default is TRUE. |
|
31 |
#' @param verbose (bool) If set to verbose, the hailstone sequence will include |
|
32 |
#' control string sequences to provide information about how the |
|
33 |
#' sequence terminated, whether by reaching a stopping time or entering |
|
34 |
#' a cycle. Default is TRUE. |
|
35 |
#' @returns A keyed list consisting of a $values list of numeric | bigz |
|
36 |
#' along with a $terminalCondition and $terminalStatus |
|
37 |
#' @examples |
|
38 |
#' # Compute a hailstone sequence, which defaults to the total stopping time; |
|
39 |
#' hailstone_sequence(5) |
|
40 |
#' # Or only compute down to the regular stopping time; |
|
41 |
#' hailstone_sequence(5, total_stopping_time=FALSE) |
|
42 |
#' # Remove verbose messaging; |
|
43 |
#' hailstone_sequence(5, verbose=FALSE) |
|
44 |
#' # It will also stop on finding a cycle; |
|
45 |
#' hailstone_sequence(-56) |
|
46 |
#' # And can be parameterised; |
|
47 |
#' hailstone_sequence(3, -1, 3, 1) |
|
48 |
#' # The hailstone sequence can run on `bigz`; |
|
49 |
#' hailstone_sequence(27+as.bigz("576460752303423488")) |
|
50 |
#' @export |
|
51 |
hailstone_sequence <- function(initial_value, P=2, a=3, b=1, |
|
52 |
max_total_stopping_time=1000, total_stopping_time=TRUE, verbose=TRUE){ |
|
53 |
# Call out the collatz_function before any magic returns to trap bad values. |
|
54 | 64x |
throwaway_test <- collatz_function(initial_value,P=P,a=a,b=b) |
55 |
# 0 is always an immediate stop. |
|
56 | 58x |
if (initial_value == 0){ |
57 | 2x |
if (verbose) { |
58 | 2x |
return(list(values=list(0), terminalCondition=Collatz$SequenceState$ZERO_STOP, terminalStatus=0)) |
59 |
} else { |
|
60 | ! |
return(list(0)) |
61 |
} |
|
62 |
} |
|
63 |
# 1 is always an immediate stop, with 0 stopping time. |
|
64 | 56x |
if (initial_value == 1){ |
65 | 2x |
if (verbose) { |
66 | 2x |
return(list(values=list(1), terminalCondition=Collatz$SequenceState$TOTAL_STOPPING_TIME, terminalStatus=0)) |
67 |
} else { |
|
68 | ! |
return(list(1)) |
69 |
} |
|
70 |
} |
|
71 | 54x |
terminate <- stopping_time_terminus(initial_value, total_stopping_time) |
72 |
# Start the hailstone sequence. |
|
73 | 54x |
max_max_total_stopping_time <- max(max_total_stopping_time, 1) |
74 | 54x |
hailstone <- list(values=vector(mode="list", length=max_max_total_stopping_time), terminalCondition=NA, terminalStatus=NA) |
75 | 54x |
hailstone$values[[1]] <- initial_value |
76 | 54x |
for (k in 1:max_max_total_stopping_time){ |
77 | 1053x |
next_val <- collatz_function(hailstone$values[[k]],P=P,a=a,b=b) |
78 |
# Check if the next_val hailstone is either the stopping time, total |
|
79 |
# stopping time, the same as the initial value, or stuck at zero. |
|
80 | 1053x |
if (terminate(next_val)) { |
81 | 14x |
hailstone$values[[k+1]] <- next_val |
82 | 14x |
hailstone$values <- hailstone$values[1:(k+1)] |
83 | 14x |
if (verbose) { |
84 | 14x |
if (next_val == 1) { |
85 | 5x |
hailstone$terminalCondition <- Collatz$SequenceState$TOTAL_STOPPING_TIME |
86 |
} else { |
|
87 | 9x |
hailstone$terminalCondition <- Collatz$SequenceState$STOPPING_TIME |
88 |
} |
|
89 | 14x |
hailstone$terminalStatus <- k |
90 | 14x |
return(hailstone) |
91 |
} else { |
|
92 | ! |
return(hailstone$values) |
93 |
} |
|
94 |
} |
|
95 |
# Here is normally where cyclic <- function(x){x %in% hailstone$values} |
|
96 |
# would be used to determine presence of a new value in previous values |
|
97 |
# but R's in-built tests for set membership all behave differently to |
|
98 |
# other languages when the input itself is a vector, which bigz raw is! |
|
99 |
# e.g. see how meaningless this is: `gmp::numerator(5) %in% list(5)` |
|
100 |
# So we need to always do to the inverse loop traversal and compare, |
|
101 |
# as the compare on list elements against bigz | bigq _does_ work! |
|
102 |
# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> |
|
103 | 1039x |
cycle_init <- -1 |
104 | 1039x |
for (j in 0:(k-1)) { |
105 | 32753x |
if (hailstone$values[[k-j]] == next_val) { |
106 | 36x |
cycle_init <- j+1 |
107 | 36x |
break |
108 |
} |
|
109 |
} |
|
110 | 1039x |
if (cycle_init > 0) { |
111 | 36x |
hailstone$values[[k+1]] <- next_val |
112 | 36x |
hailstone$values <- hailstone$values[1:(k+1)] |
113 | 36x |
if (verbose) { |
114 | 36x |
hailstone$terminalCondition <- Collatz$SequenceState$CYCLE_LENGTH |
115 | 36x |
hailstone$terminalStatus <- cycle_init |
116 | 36x |
return(hailstone) |
117 |
} else { |
|
118 | ! |
return(hailstone$values) |
119 |
} |
|
120 |
} |
|
121 |
# <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|
122 | 1003x |
if (next_val == 0) { |
123 | 2x |
hailstone$values[[k+1]] <- 0 |
124 | 2x |
hailstone$values <- hailstone$values[1:(k+1)] |
125 | 2x |
if (verbose) { |
126 | 2x |
hailstone$terminalCondition <- Collatz$SequenceState$ZERO_STOP |
127 | 2x |
hailstone$terminalStatus <- -k |
128 | 2x |
return(hailstone) |
129 |
} else { |
|
130 | ! |
return(hailstone$values) |
131 |
} |
|
132 |
} |
|
133 | 1001x |
hailstone$values[[k+1]] <- next_val |
134 |
} |
|
135 | 2x |
if (verbose) { |
136 | 2x |
hailstone$terminalCondition <- Collatz$SequenceState$MAX_STOP_OUT_OF_BOUNDS |
137 | 2x |
hailstone$terminalStatus <- max_max_total_stopping_time |
138 | 2x |
return(hailstone) |
139 |
} else { |
|
140 | ! |
return(hailstone$values) |
141 |
} |
|
142 |
} |
1 |
#' @include utils.R |
|
2 |
#' @include reverse_function.R |
|
3 |
NULL |
|
4 | ||
5 |
#' Tree Graph |
|
6 |
#' |
|
7 |
#' Determine the Tree Graph to some depth by iteratively reversing values. |
|
8 |
#' |
|
9 |
#' Returns nested dictionaries that model the directed tree graph up to a |
|
10 |
#' maximum nesting of max_orbit_distance, with the initial_value as the root. |
|
11 |
#' @param initial_value (int) The root value of the directed tree graph. |
|
12 |
#' @param max_orbit_distance (int) Maximum amount of times to iterate the |
|
13 |
#' reverse function. There is no natural termination to populating the tree |
|
14 |
#' graph, equivalent to the termination of hailstone sequences or stopping time |
|
15 |
#' attempts, so this is not an optional argument like max_stopping_time or |
|
16 |
#' max_total_stopping_time, as it is the intended target of orbits to obtain, |
|
17 |
#' rather than a limit to avoid uncapped computation. |
|
18 |
#' @param P (numeric|bigz): Modulus used to divide |
|
19 |
#' n, iff n is equivalent to (0 mod P). Default is 2. |
|
20 |
#' @param a (numeric|bigz) Factor by which to multiply n. Default is 3. |
|
21 |
#' @param b (numeric|bigz) Value to add |
|
22 |
#' to the scaled value of n. Default is 1. |
|
23 |
#' @param cycle_prevention (set[int]) Used to prevent cycles from precipitating |
|
24 |
#' by keeping track of all values added across previous nest depths. Only to be |
|
25 |
#' used internally by the function recursing. Does not expect input. |
|
26 |
#' @returns A set of nested dictionaries. |
|
27 |
#' @examples |
|
28 |
#' #Compute a tree graph, which takes both a value to initialise the tree from, |
|
29 |
#' # and an "orbit distance" for how many layers deep in the tree to compute; |
|
30 |
#' tree_graph(16, 3) |
|
31 |
#' # It will also stop on finding a cycle; |
|
32 |
#' tree_graph(4, 3) |
|
33 |
#' # And can be parameterised; |
|
34 |
#' tree_graph(1, 1, -3, -2, -5) |
|
35 |
#' # If b is a multiple of a, but not of Pa, then 0 can have a reverse; |
|
36 |
#' tree_graph(0, 1, 17, 2, -6) |
|
37 |
#' # The tree graph can run on `bigz`; |
|
38 |
#' tree_graph((27+as.bigz("576460752303423488")), 3) |
|
39 |
#' @export |
|
40 |
tree_graph <- function(initial_value, max_orbit_distance, P=2, a=3, b=1, cycle_prevention=list()){ |
|
41 |
# Call out the reverse_function before any magic returns to trap bad values. |
|
42 | 70x |
throwaway_test <- reverse_function(initial_value,P=P,a=a,b=b) |
43 |
# In R, if a numeric is used as the key in a K:V list, it will populate the |
|
44 |
# entire list up to that point as ascending numerics pointing to NULLs. |
|
45 |
# To get around this, we can "as.character(numeric_val)" as it does not |
|
46 |
# do the same backfilling for string keys. Although this yields another |
|
47 |
# problem, that the syntax `list((as.character(some_num))=anything)` |
|
48 |
# complains of `Error: unexpected '=' in "list((as.character(some_num))="` |
|
49 |
# So rather than a syntactically concise one liner, we need a few lines.. |
|
50 | 67x |
tgraph <- list() |
51 | 67x |
tgraph[[as.character(initial_value)]] <- NA |
52 | 67x |
if (max(0, max_orbit_distance) == 0) { |
53 | 28x |
return(tgraph) |
54 |
} else { |
|
55 | 39x |
tgraph[[as.character(initial_value)]] <- list() |
56 |
} |
|
57 | 39x |
cycle_prevention <- append(cycle_prevention, list(initial_value)) |
58 | 39x |
for (branch_value in reverse_function(initial_value, P=P, a=a, b=b)) { |
59 | 58x |
no_cycle <- TRUE |
60 | 58x |
for (previous_value in cycle_prevention) { |
61 | 101x |
if (branch_value == previous_value) { |
62 | 10x |
tgraph[[as.character(initial_value)]][[Collatz$SequenceState$CYCLE_INIT]] <- branch_value |
63 | 10x |
no_cycle <- FALSE |
64 | 10x |
break |
65 |
} |
|
66 |
} |
|
67 | 58x |
if (no_cycle) { |
68 | 48x |
tgraph[[as.character(initial_value)]][[as.character(branch_value)]] <- tree_graph(branch_value, |
69 | 48x |
max_orbit_distance-1, P=P, a=a, b=b, cycle_prevention=cycle_prevention)[[as.character(branch_value)]] |
70 |
} |
|
71 |
} |
|
72 | 39x |
return(tgraph) |
73 |
} |
1 |
#' @include utils.R |
|
2 |
NULL |
|
3 | ||
4 |
#' The "inverse"/"reverse" Collatz function |
|
5 |
#' |
|
6 |
#' Calculates the values that would return the input under the Collatz function. |
|
7 |
#' |
|
8 |
#' Returns the output of a single application of a Collatz-esque reverse |
|
9 |
#' function. If only one value is returned, it is the value that would be |
|
10 |
#' divided by P. If two values are returned, the first is the value that |
|
11 |
#' would be divided by P, and the second value is that which would undergo |
|
12 |
#' the multiply and add step, regardless of which is larger. |
|
13 |
#' @param n (numeric|bigz) The value on which |
|
14 |
#' to perform the reverse Collatz function |
|
15 |
#' @param P (numeric|bigz) Modulus used to divide |
|
16 |
#' n, iff n is equivalent to (0 mod P) Default is 2. |
|
17 |
#' @param a (numeric|bigz) Factor by which to multiply n. Default is 3. |
|
18 |
#' @param b (numeric|bigz) Value to add |
|
19 |
#' to the scaled value of n. Default is 1. |
|
20 |
#' @returns A list of either numeric or bigz type |
|
21 |
#' @examples |
|
22 |
#' # Calculates the values that would return the input under the Collatz |
|
23 |
#' # function. Without `gmp` or parameterisation, we can try something |
|
24 |
#' # simple like |
|
25 |
#' reverse_function(1) |
|
26 |
#' reverse_function(2) |
|
27 |
#' reverse_function(4) |
|
28 |
#' # If we want change the default parameterisation we can; |
|
29 |
#' reverse_function(3, -3, -2, -5) |
|
30 |
#' # Or if we only want to change one of them |
|
31 |
#' reverse_function(16, a=5) |
|
32 |
#' # All the above work fine, but the function doesn't offer protection against |
|
33 |
#' # overflowing integers by default. To venture into the world of arbitrary |
|
34 |
#' # integer inputs we can use an `as.bigz` from `gmp`. Compare the two; |
|
35 |
#' reverse_function(99999999999999999999) |
|
36 |
#' reverse_function(as.bigz("99999999999999999999")) |
|
37 |
#' @export |
|
38 |
reverse_function <- function(n, P=2, a=3, b=1){ |
|
39 | 128x |
assert_sane_parameterication(P,a,b) |
40 |
# Every input can be reversed as the result of "n/P" division, which yields |
|
41 |
# "Pn"... {f(n) = an + b}~={(f(n) - b)/a = n} ~ if n was such that the |
|
42 |
# muliplication step was taken instead of the division by the modulus, then |
|
43 |
# (f(n) - b)/a) must be an integer that is not in (0 mod P). Because we're |
|
44 |
# not placing restrictions on the parameters yet, although there is a better |
|
45 |
# way of shortcutting this for the default variables, we need to always |
|
46 |
# attempt (f(n) - b)/a) |
|
47 | 122x |
pre_values <- list(P*n) |
48 | 122x |
n_minus_b <- (n-b) |
49 | 122x |
if (n_minus_b%%a == 0 && n_minus_b%%(P*a) != 0){ |
50 |
# bigq does not have a defined use for %%, so must be int or bigz |
|
51 |
# bigz/bigz returns a bigq even if that bigq has denominator 1 |
|
52 |
# so we do a divq, "%/%", instead of div, to just get the bigz. |
|
53 | 60x |
pre_values <- append(pre_values, list(n_minus_b%/%a)) |
54 |
} |
|
55 | 122x |
pre_values |
56 |
} |
1 |
#' @include utils.R |
|
2 |
NULL |
|
3 | ||
4 |
#' The Collatz function |
|
5 |
#' |
|
6 |
#' Returns the output of a single application of a Collatz-esque function. |
|
7 |
#' |
|
8 |
#' This function will compute and return the result of applying one iteration |
|
9 |
#' of a parameterised Collatz-esque function. Although it will operate with |
|
10 |
#' integer inputs, for overflow safety, provide a gmp Big Integer ('bigz'). |
|
11 |
#' |
|
12 |
#' @param n (numeric|bigz) The value on which |
|
13 |
#' to perform the Collatz-esque function |
|
14 |
#' @param P (numeric|bigz): Modulus used to divide |
|
15 |
#' n, iff n is equivalent to (0 mod P). Default is 2. |
|
16 |
#' @param a (numeric|bigz) Factor by which to multiply n. Default is 3. |
|
17 |
#' @param b (numeric|bigz) Value to add |
|
18 |
#' to the scaled value of n. Default is 1. |
|
19 |
#' @returns a numeric, either in-built or a bigz from the gmp library. |
|
20 |
#' @examples |
|
21 |
#' # Returns the output of a single application of a Collatz-esque function. |
|
22 |
#' # Without `gmp` or parameterisation, we can try something simple like |
|
23 |
#' collatz_function(5) |
|
24 |
#' collatz_function(16) |
|
25 |
#' # If we want change the default parameterisation we can; |
|
26 |
#' collatz_function(4, 5, 2, 3) |
|
27 |
#' # Or if we only want to change one of them |
|
28 |
#' collatz_function(3, a=-2) |
|
29 |
#' # All the above work fine, but the function doesn't offer protection against |
|
30 |
#' # overflowing integers by default. To venture into the world of arbitrary |
|
31 |
#' # integer inputs we can use an `as.bigz` from `gmp`. Compare the two; |
|
32 |
#' collatz_function(99999999999999999999) |
|
33 |
#' collatz_function(as.bigz("99999999999999999999")) |
|
34 |
#' @export |
|
35 |
collatz_function <- function(n, P=2, a=3, b=1){ |
|
36 | 1134x |
assert_sane_parameterication(P,a,b) |
37 |
# bigq does not have a defined use for %%, so must be int or bigz |
|
38 |
# bigz/bigz returns a bigq even if that bigq has denominator 1 |
|
39 |
# so we do a divq, "%/%", instead of div, to just get the bigz. |
|
40 | 431x |
if (n%%P == 0) (n%/%P) else ((a*n)+b) |
41 |
} |
1 |
#' Collatz |
|
2 |
#' |
|
3 |
#' Functions related to the Collatz/Syracuse/3N+1 problem. |
|
4 |
#' |
|
5 |
#' Provides the basic functionality to interact with the Collatz conjecture. |
|
6 |
#' The parameterisation uses the same (P,a,b) notation as Conway's generalisations. |
|
7 |
#' Besides the function and reverse function, there is also functionality to retrieve |
|
8 |
#' the hailstone sequence, the "stopping time"/"total stopping time", or tree-graph. |
|
9 |
#' The only restriction placed on parameters is that both P and a can't be 0. |
|
10 |
#' |
|
11 |
#' @docType package |
|
12 |
#' @name collatz |
|
13 |
#' @import gmp |
|
14 |
NULL |
|
15 | ||
16 |
library(gmp) |
|
17 | ||
18 |
# An environment for constants related to the Collatz package, primarily for testing purposes. |
|
19 |
Collatz <- new.env() |
|
20 | ||
21 |
# The four known cycles for the standard parameterisation, as ints. |
|
22 |
Collatz$KNOWN.CYCLES <- list(list(1, 4, 2), list(-1, -2), list(-5, -14, -7, -20, -10), |
|
23 |
list(-17, -50, -25, -74, -37, -110, -55, -164, -82, -41, -122, -61, -182, -91, -272, -136, -68, -34)) |
|
24 |
lockBinding("KNOWN.CYCLES", Collatz) |
|
25 | ||
26 |
# The current value up to which the standard parameterisation has been verified. |
|
27 |
Collatz$VERIFIED.MAXIMUM <- as.bigz("295147905179352825856") |
|
28 |
lockBinding("VERIFIED.MAXIMUM", Collatz) |
|
29 | ||
30 |
# The current value down to which the standard parameterisation has been verified. |
|
31 |
# TODO: Check the actual lowest bound. |
|
32 |
Collatz$VERIFIED.MINIMUM <- -272 |
|
33 |
lockBinding("VERIFIED.MINIMUM", Collatz) |
|
34 | ||
35 |
# Error message constants |
|
36 |
Collatz$SaneParameterErrMsg <- list(P="'P' should not be 0 ~ violates modulo being non-zero.", A="'a' should not be 0 ~ violates the reversability.") |
|
37 |
lockBinding("SaneParameterErrMsg", Collatz) |
|
38 | ||
39 |
# SequenceState for Cycle Control: Descriptive flags to indicate when some |
|
40 |
# event occurs in the hailstone sequences or tree graph reversal, when set to |
|
41 |
# verbose, or stopping time check. Create as an S3 class. |
|
42 |
Collatz$SequenceState <- list( |
|
43 |
STOPPING_TIME="STOPPING_TIME", |
|
44 |
TOTAL_STOPPING_TIME="TOTAL_STOPPING_TIME", |
|
45 |
CYCLE_INIT="CYCLE_INIT", |
|
46 |
CYCLE_LENGTH="CYCLE_LENGTH", |
|
47 |
MAX_STOP_OUT_OF_BOUNDS="MAX_STOP_OUT_OF_BOUNDS", |
|
48 |
ZERO_STOP="ZERO_STOP") |
|
49 |
lockBinding("SequenceState", Collatz) |
|
50 | ||
51 |
#' Sane Parameter Check |
|
52 |
#' |
|
53 |
#' Handles the sanity check for the parameterisation (P,a,b) |
|
54 |
#' |
|
55 |
#' Required by both the function and reverse function, to assert that they |
|
56 |
#' have sane parameters, otherwise will force stop the execution. |
|
57 |
#' |
|
58 |
#' @param P Modulus used to devide n, iff n is equivalent to (0 mod P). |
|
59 |
#' @param a Factor by which to multiply n. |
|
60 |
#' @param b Value to add to the scaled value of n. |
|
61 |
assert_sane_parameterication <- function(P, a, b) { |
|
62 |
# Sanity check (P,a,b) ~ P absolutely can't be 0. a "could" be zero |
|
63 |
# theoretically, although would violate the reversability (if ~a is 0 then a |
|
64 |
# value of "b" as the input to the reverse function would have a pre-emptive |
|
65 |
# value of every number not divisible by P). The function doesn't _have_ to |
|
66 |
# be reversable, but we are only interested in dealing with the class of |
|
67 |
# functions that exhibit behaviour consistant with the collatz function. If |
|
68 |
# _every_ input not divisable by P went straight to "b", it would simply |
|
69 |
# cause a cycle consisting of "b" and every b/P^z that is an integer. While |
|
70 |
# P in [-1, 1] could also be a reasonable check, as it makes every value |
|
71 |
# either a 1 or 2 length cycle, it's not strictly an illegal operation. |
|
72 |
# "b" being zero would cause behaviour not consistant with the collatz |
|
73 |
# function, but would not violate the reversability, so no check either. |
|
74 |
# " != 0" is redundant for python assertions. |
|
75 | 10x |
if (P == 0) stop(Collatz$SaneParameterErrMsg$P) |
76 | 5x |
if (a == 0) stop(Collatz$SaneParameterErrMsg$A) |
77 |
} |
|
78 | ||
79 |
#' Stopping Time Terminus |
|
80 |
#' |
|
81 |
#' Provides the appropriate lambda to use to check if iterations on an initial |
|
82 |
#' value have reached either the stopping time, or total stopping time. |
|
83 |
#' |
|
84 |
#' @param n The initial value to confirm against a stopping time check. |
|
85 |
#' @param total_stop If false, the lambda will confirm that iterations of n |
|
86 |
#' have reached the oriented stopping time to reach a value closer to 0. |
|
87 |
#' If true, the lambda will simply check equality to 1. |
|
88 |
#' @returns An anonymous function to check for the stopping time. |
|
89 |
stopping_time_terminus <- function(n, total_stop) { |
|
90 | 54x |
if (total_stop) { |
91 | 561x |
return(function(x) { return(x==1) }) |
92 | 54x |
} else if (n >= 0) { |
93 | 492x |
return(function(x) { return ((x < n) && (x > 0)) }) |
94 |
} else { |
|
95 | ! |
return(function(x) { return ((x > n) && (x < 0)) }) |
96 |
} |
|
97 |
} |
1 |
#' @include utils.R |
|
2 |
#' @include hailstone_sequence.R |
|
3 |
NULL |
|
4 | ||
5 |
#' Stopping Time |
|
6 |
#' |
|
7 |
#' Determine the stopping time, or "total" stopping time, for an initial value. |
|
8 |
#' |
|
9 |
#' Returns the stopping time, the amount of iterations required to reach a |
|
10 |
#' value less than the initial value, or NaN if max_stopping_time is exceeded. |
|
11 |
#' Alternatively, if total_stopping_time is TRUE, then it will instead count |
|
12 |
#' the amount of iterations to reach 1. If the sequence does not stop, but |
|
13 |
#' instead ends in a cycle, the result will be (Inf). If (P,a,b) are such |
|
14 |
#' that it is possible to get stuck on zero, the result will be the negative of |
|
15 |
#' what would otherwise be the "total stopping time" to reach 1, where 0 is |
|
16 |
#' considered a "total stop" that should not occur as it does form a cycle of |
|
17 |
#' length 1. |
|
18 |
#' @param initial_value (int): The value for which to find the stopping time. |
|
19 |
#' @param P (numeric|bigz): Modulus used to divide |
|
20 |
#' n, iff n is equivalent to (0 mod P). Default is 2. |
|
21 |
#' @param a (numeric|bigz) Factor by which to multiply n. Default is 3. |
|
22 |
#' @param b (numeric|bigz) Value to add |
|
23 |
#' to the scaled value of n. Default is 1. |
|
24 |
#' @param max_stopping_time (int) Maximum amount of times to iterate the |
|
25 |
#' function, if the stopping time is not reached. IF the max_stopping_time |
|
26 |
#' is reached, the function will return NaN. Default is 1000. |
|
27 |
#' @param total_stopping_time (bool) Whether or not to execute until the "total" |
|
28 |
#' stopping time (number of iterations to obtain 1) rather than the regular |
|
29 |
#' stopping time (number of iterations to reach a value less than the initial |
|
30 |
#' value). Default is FALSE. |
|
31 |
#' @returns An integer numeral if stopped, Inf if a cycle, NaN if OOB, else NA. |
|
32 |
#' @examples |
|
33 |
#' # Calculates the "stopping time", or optionally the "total" stopping time. |
|
34 |
#' # Without `gmp` or parameterisation, we can try something simple like |
|
35 |
#' stopping_time(27) |
|
36 |
#' stopping_time(27, total_stopping_time=TRUE) |
|
37 |
#' # If we want change the default parameterisation we can; |
|
38 |
#' stopping_time(3, 5, 2, 1) |
|
39 |
#' # Or if we only want to change one of them |
|
40 |
#' stopping_time(17, a=5) |
|
41 |
#' # All the above work fine, but the function doesn't offer protection against |
|
42 |
#' # overflowing integers by default. To venture into the world of arbitrary |
|
43 |
#' # integer inputs we can use an `as.bigz` from `gmp`. Compare the two; |
|
44 |
#' stopping_time(99999999999999999999) |
|
45 |
#' stopping_time(as.bigz("99999999999999999999")) |
|
46 |
#' # As an extra note, the original motivation for creating a range of Collatz |
|
47 |
#' # themed packages came from some earlier scripts for calculating the stopping |
|
48 |
#' # distances under certain parameterisations. An inconsequential result of |
|
49 |
#' # which was observing that all of the following, for however high `k` goes, |
|
50 |
#' # should equal `96`! |
|
51 |
#' stopping_time(27) |
|
52 |
#' stopping_time(27+as.bigz("576460752303423488")) |
|
53 |
#' stopping_time(27+(2*as.bigz("576460752303423488"))) |
|
54 |
#' stopping_time(27+(3*as.bigz("576460752303423488"))) |
|
55 |
#' stopping_time(27+(4*as.bigz("576460752303423488"))) |
|
56 |
#' @export |
|
57 |
stopping_time <- function(initial_value, P=2, a=3, b=1, |
|
58 |
max_stopping_time=1000, total_stopping_time=FALSE){ |
|
59 |
# The information is contained in the verbose form of a hailstone sequence. |
|
60 |
# Although the "max_~_time" for hailstones is name for "total stopping" time |
|
61 |
# and the "max_~_time" for this "stopping time" function is _not_ "total", |
|
62 |
# they are handled the same way, as the default for "total_stopping_time" |
|
63 |
# for hailstones is true, but for this, is false. Thus the naming difference |
|
64 | 46x |
sequence <- hailstone_sequence(initial_value, P=P, a=a, b=b, verbose=TRUE, |
65 | 46x |
max_total_stopping_time=max_stopping_time, |
66 | 46x |
total_stopping_time=total_stopping_time) |
67 |
# For total/regular/zero stopping time, the value is already the same as |
|
68 |
# that present, for cycles we report infinity instead of the cycle length, |
|
69 |
# and for max stop out of bounds, we report None instead of the max stop cap |
|
70 |
# An extra note here is that while other designs use the enum values as the |
|
71 |
# comparitors in the switch, R is very unhappy with non-literal switch keys. |
|
72 | 43x |
return(switch(sequence$terminalCondition, |
73 | 43x |
"TOTAL_STOPPING_TIME" = sequence$terminalStatus, |
74 | 43x |
"STOPPING_TIME" = sequence$terminalStatus, |
75 | 43x |
"CYCLE_LENGTH" = Inf, |
76 | 43x |
"ZERO_STOP" = sequence$terminalStatus, |
77 | 43x |
"MAX_STOP_OUT_OF_BOUNDS" = NaN, |
78 | 43x |
NA |
79 |
)) |
|
80 |
} |