#### misc-goodies.R
#### ~~~~~~~~~~~~~~  SfS - R - goodies that are NOT in
####		"/u/sfs/R/SfS/R/u.goodies.R"
####		"/u/sfs/R/SfS/R/p.goodies.R"

###--- Original: From 'S' in /u/sfs/S/misc-goodies.S
###--- ========  But start doing *less* here !

###==================================================================
###  Functions <<<<<<<< Please use a few subsections  like "Plotting"...
###==================================================================

### ___Note___ we have some of these headers __MESS__
### But we leave it because of RCS {rather dismantle everything into 4-6 pieces

## A fast substitute for structure() *when* we don't need to special case for factor, .Dim, ...
struct <- function(x, ...) `attributes<-`(x, c(attributes(x), list(...)))

##' list_(a, b, cc)  creates a *named* list  using the actual arguments' names
list_ <- function(...)
   `names<-`(list(...), vapply(sys.call()[-1L], as.character, ""))

##-#### Vector, Matrix (or higher Array) stuff ########
##-###  -------------------------------------- ########

last <- function(x, length.out = 1, na.rm = FALSE)
{
    ## Purpose: last element(s) of a vector
    ## Author: Werner Stahel, Date:  Tue Jan 21 17:29:42 1992
    ## ----------------------------------------------------------------
    ## Arguments:
    ##   x:          vector
    ##   length.out: if positive, return the  length.out last elements of x,
    ##               if negative, the last  length.out  elements are dropped
    ## ----------------------------------------------------------------
    if (na.rm)
        x <- x[!is.na(x)]
    n <- length(x)
    x[sign(length.out)*(n-abs(length.out)+1):n]
}

empty.dimnames <- function(a) {
    ## 'Remove' all dimension names from an array for compact printing.
    n <- length(da <- dim(a))
    if(n) dimnames(a) <- lapply(1:n, function(i) rep.int("", da[i]))
    a
}


##-#### Plot / Devices  related stuff        ########
##-###  -----------------------------        ########
##-### >>>>> "p.goodies.S" or "ps.goodies.S" ########

errbar <- function(x, y, yplus, yminus, cap = 0.015,
		   ylim = range(y, yplus, yminus),
                   xlab = deparse(substitute(x)),
                   ylab = deparse(substitute(y)), ... )
{
  ## Purpose: Makes a plot with error bars
  ## Authors: Charles Geyer, Statistics, U. Chicago, geyer@galton.uchicago.edu
  ## 	  Martin Maechler, Date:  11 Apr 91  and  Mar 27 1992, 12:32
  ## ----------------------------------------------------------------
  ## Arguments: --- see  help(..) page --->  ?errbar
  ## ----------------------------------------=======

  plot( x, y, ylim=ylim, xlab=xlab, ylab=ylab, ... )
  xcoord <- par()$usr[1:2]
  segments( x, yminus, x, yplus )
  smidge <- cap * ( xcoord[2] - xcoord[1] ) / 2
  segments( x - smidge, yminus, x + smidge, yminus )
  segments( x - smidge, yplus, x + smidge, yplus )
}
## C.Monatsname , etc..  sind jetzt bei der zugehoerigen Funktion
##		u.Datumvonheute  in  /u/sfs/S/u.goodies.S

cum.Vert.funkt <- function(x, Quartile = TRUE, titel = TRUE, Datum = TRUE,
                           rang.axis = n <= 20, xlab = "", main = "", ...)
{
  ## Ziel: Kumulative Verteilung von x aufzeichnen, auf Wunsch auch Median
  ##       und Quartile
  op <- par(xaxs = "r", yaxs = "r", las = 1)# the default anyway
  on.exit(par(op))
  r <- plotStep(x, xlab = xlab, main = main, ...)
  #### FIXME : stepfun() / ecdf() instead
  n <- length(x)
  if(rang.axis)
      axis(4, at = (0:n)/n, labels = 0:n, pos = par("usr")[1])#, las = 1)
  if(titel) mtext("Kumulative Verteilungsfunktion", 3, line = 0.5)
  if(Quartile) for(i in 1:3) abline(h = i/4, lty = 2)
  if(Datum) p.datum()
  invisible(r)
}


## This was "plot.step()" but that's in conflict with S3 methods
plotStep <- function(ti, y,
		      cad.lag = TRUE,
		      verticals = !cad.lag,
		      left.points = cad.lag,
		      right.points = FALSE,
		      end.points = FALSE,

		      add = FALSE,

		      pch = par('pch'),
		      xlab = deparse(substitute(ti)),
		      ylab = deparse(substitute(y)),
		      main = NULL,
		      ...)

#####- FIXME ----------- use stepfun(), plot.stepfun() etc !!! ----------------

{
  ## Purpose: plot step-function  f(x)= sum{ y[i] * 1_[ t[i-1], t[i] ] (x) }
  ## -------------------------------------------------------------------------
  ## Arguments: for missing 'y', do empirical CDF; ==> ON-LINE Help "?plot.step"
  ## -------------------------------------------------------------------------
  ## Author: Martin Maechler, 1990, U.Washington, Seattle; improved -- Dec.1993
  ##
  ## EXAMPLE: ##-- Plot empirical cdf  Fn(x)  for a small n:
  ## 	      xx <- runif(20); plot.step(xx); plot.step( xx, cad.lag = F )
  ##	      plot.step( runif(20), add=T, cad.lag=F)
  xlab
  ylab
  if(missing(y)) {
    if(is.vector(ti) && is.numeric(ti)) {   # -- Do empirical CDF --
      nt <- length(ti)
      ti <- sort(ti)
      dt <- (ti[nt] - ti[1])/20
      ti <- c(ti[1] - dt, ti, ti[nt] + dt)
      n <- nt + 1
      y <- (0:nt)/nt
    } else {
      xy <- xy.coords(ti,NULL,NULL,NULL)
      ti <- c(xy$x[1], xy$x)
      y <- xy$y
      n <- length(y)
    }
  } else {
    n <- length(y)
    if(length(ti) != (n + 1))  stop("length(ti) MUST == length(y) + 1")
  }
  if(length(ti) != (n + 1) || length(y) != n)
    stop("NEVER CALLED! --length(ti) MUST == length(y) + 1")
  if(missing(main))  main <- deparse(sys.call())

  n1 <- n+1
  ##-- horizontal segments:
  if (add) segments(ti[-n1], y, ti[-1], y, ...)
  else {
    plot(ti, c(y[1],y), type = 'n', xlab = xlab, ylab = ylab, main = main, ...)
    segments(ti[-n1], y, ti[-1], y)
  }
  if(left.points)  points(ti[-n1],y, pch = pch)
  if(right.points) points(ti[-1], y, pch = pch)
  ##-- col=0 <==> "erase" :
  if(! end.points) points(ti[c(1,n1)], y[c(1,n)], pch = pch, col = 0)
  if(verticals) {
    if (add) segments(ti[2:n], y[-n], ti[2:n], y[-1], ...)
    else     segments(ti[2:n], y[-n], ti[2:n], y[-1])
  }
  invisible(list(t = ti, y = y))
}

histBxp <-
    function(x, nclass, breaks, probability = FALSE, include.lowest = TRUE,
             xlab = deparse(substitute(x)), ..., width = 0.2,
             boxcol = 3, medcol = 2, medlwd = 5, whisklty = 2, staplelty = 1)
{
  ## Purpose:   Plot a histogram and a boxplot
  ## -------------------------------------------------------------------------
  ## Arguments: ---> see help(hist.bxp) !
  ## -------------------------------------------------------------------------
  ## Authors: Christian Keller, Date: 10 Nov 95,  (Martin Maechler, Jan 96)
  ##						calls  p.hboxp(.) !

  ## determine the height of the plot
  h <-
    if(missing(breaks)){
      if(missing(nclass))
        hist(x, include.lowest = include.lowest, plot = FALSE)
      else
	hist(x, nclass = nclass,
             include.lowest = include.lowest, plot = FALSE)
    }
    else
        hist(x, breaks = breaks,
             include.lowest = include.lowest, plot = FALSE)
  ymax <- max(h$counts)
  ymin <-  - ymax * width # range:  (-w,1)*ymax  instead of  (0,1)*ymax

  ##------- drawing the histogram -------------
  hist(x, breaks = h$breaks, probability = probability,
       include.lowest = include.lowest, plot = TRUE, xlab = xlab,
       ylim = c(ymin, ymax), axes = FALSE, ...)
  axis(1)
  axis(2, at = pretty(c(0,ymax), n = 5), srt = 90) ## ph, 8.5.00: n instead of nint
  abline(h = 0)				#
  ##-------- drawing the boxplot --------------

  ##-- scale a range
  scale.r <- function(x1,x2, fact = 1.1)
    (x1+x2)/2 + c(-fact,fact) * (x2-x1)/2

  ##-- since 4% extra space above x-axis (just below ymin):
  ##-   cat("par$usr[3:4]:", par("usr")[3:4],
  ##- 	    "  ymin -.04 *(ymax-ymin)",ymin -.04 *(ymax-ymin),"\n")
  ##-- NOTE: Always have (seemingly): par("usr")[3] == ymin -.04 *(ymax-ymin)

##-O- ORIGINAL VERSION (Keller & Keller) :
##-O-   p.hboxp(x, ymin, -.04 *(ymax-ymin),
##-O- 	  boxcol=boxcol, medcol=medcol,
##-O- 	  medlwd=medlwd, whisklty=whisklty, staplelty=staplelty)

  ##---- This is  much better for width <=.1 or so...
  ##-- but you should leave some white space -> scale down
  ##-- The scaling factor is really a  KLUDGE but works for a wide range!
  p.hboxp(x, scale.r(par("usr")[3], 0, ## ph, 8.5.00: changed f=.9 to f=.8
		     fact = .8 - max(0, .15 - width)*(1+(par("mfg")[3] >= 3))),
	  boxcol = boxcol, medcol = medcol,
	  medlwd = medlwd, whisklty = whisklty, staplelty = staplelty)
}


##-#### Print & Strings  ########
##-###  ===============  ########

ccat <-  ## character 'concat'
  function(...)     paste0(..., collapse = "")
vcat <- ## (numeric) vector 'concat'
  function(vec, sep = " ") paste(vec, collapse = sep)

paste.vec <- function(name, digits = options()$digits)
{
  ## Purpose: Utility for "showing vectors"
  ## -------------------------------------------------------------------------
  ## Example: x <- 1:4;  paste.vec(x)   ##->  "x = 1 2 3 4"
  paste(paste(deparse(substitute(name))), "=",
	paste(format(name, digits = digits), collapse = " "))
}
signi <- function(x, digits = 6) round(x, digits - trunc(log10(abs(x))))

##' NB:  Since ~ R 3.3.0 (May 2016),  use base R's "new"   strrep(x, times)  instead

repChar <- function(char, no) paste(rep.int(char, no), collapse = "")
## correct, but slower than the next one:
bl.string <- function(no) repChar(" ", no)
## faster:
bl.string <- function(no) sprintf("%*s", no, "")

### symnum :  standard R function !!

wrapFormula <- function(f, data, wrapString = "s(*)")
{
    ## Purpose: Mainly: Construct a useful gam() formula from  "Y ~ ."
    ## ----------------------------------------------------------------------
    ## Arguments: f   : the initial formula; typically something like "Y ~ ."
    ##            data: data.frame to which the formula applies
    ## ----------------------------------------------------------------------
    ## Author: Martin Maechler, Date: 22 May 2007, 18:03

    form <- formula(terms(f, data = data))
    if(length(form) != 3)
        stop("invalid formula; need something like  'Y ~ .'")
    wrapS <- strsplit(wrapString, "\\*")[[1]]
    stopifnot(length(wrapS) == 2)
    cc <- gsub("([^+ ]+)", paste0(wrapS[1], "\\1", wrapS[2]),
	       format(form[[3]]))
    form[[3]] <- parse(text = cc, srcfile = NULL)[[1]]
    form
}

##' Capture Output and print first and last parts, eliding middle parts.
##' Particularly useful for teaching purposes, and e.g., in Sweave
##'
##' @title Capture output and Write / Print First and Last Parts
##' @param EXPR the (literal) expression the output is to be captured
##' @param first integer: how many lines should be printed at beginning
##' @param last integer: how many lines should be printed at the end.
##' @param middle numeric (or NA logical):
##' @param i.middle index start of middle part
##' @param dotdots string to be used for elided lines
##' @param n.dots number of \code{dotdots}  ....{FIXME}
##' @return return value of \code{\link{capture.output}(EXPR)}
##' @author Martin Maechler
## -> ../man/capture-n-write.Rd
capture.and.write <- function(EXPR, first, last = 2,
                              middle = NA, i.middle,
                              dotdots = " ....... ", n.dots = 2) {
    co <- capture.output(EXPR)
    writeLines(head(co, first))
    catDots <- function(M) cat(rep.int(paste0(dotdots,"\n"), M), sep="")
    catDots(n.dots)
    if(is.numeric(middle)) {
        stopifnot(length(middle) == 1, middle >= 0, middle == round(middle))
        i0 <- first+2
        if(missing(i.middle)) {
            i.middle <- max(i0, length(co) %/% 2 - middle %/% 2)
        } else { ## !missing(i.middle)
            if(i.middle < i0)
                stop("'i.middle' is too small, should be at least ", i0)
        }
        writeLines(co[i.middle-1 + seq_len(middle)])
        catDots(n.dots)
    }
    writeLines(tail(co, last))
    invisible(co)
}



##-#### "Calculus" Mathematical stuff ########
##-###  ----------------------------- ########

polyn.eval <- function(coef, x)
{
  ## Purpose: compute coef[1] + coef[2]*x + ... + coef[p+1]* x^p
  ##	if coef is vector, x can be any array; result      : of same dim. as x
  ##	if coef is matrix, x must be vector;   dim(result) = len(x) * nrow(coef)
  ##	    coef = matrix: evaluate SEVERAL polynomials (of same degree)
  ##	    ----   contains coefficient-vectors as ROWS ==> coef[,i] <-> x^{i-1}
  ## Author: Martin Maechler <maechler@stat.math.ethz.ch>
  if(is.null(dim(coef))) {
    lc <- length(coef)
    if (lc == 0L) 0
    else {
      r <- coef[lc]
      if (lc == 1L)
        r + 0*x # keep dim(x)
      else { # lc > 1
	for (i in (lc-1):1) r <- coef[i] + r*x
        r
      }
    }
  } else { #-- coef is MATRIX --
    dc <- dim(coef)
    lc <- dc[2]; dc <- dc[1]
    n <- length(x)
    if (lc == 0) matrix(0, n, dc) else {
      r <- matrix(coef[,lc], n, dc, byrow = TRUE)
      if (lc > 1)
	for (i in (lc-1):1) r <- r*x + matrix(coef[,i], n, dc, byrow = TRUE)
      r
    }
  }
}

## negative x .. may make sense in some cases,.... but not yet :
##digitsBase <- function(x, base = 2, ndigits = 1 + floor(log(max(abs(x)),base)))
digitsBase <- function(x, base = 2, ndigits = 1 + floor(1e-9 + log(max(x,1), base)))
{
    ## Purpose: Give the vector A of the base-_base_ representation of _n_:
    ## -------  n = sum_{k=0}^M  A_{M-k} base ^ k ,   where  M = length(a) - 1
    ## Value: MATRIX  M where  M[,i]  corresponds to  x[i]
    ## Author: Martin Maechler, Date: Dec 4, 1991
    ## ----------------------------------------------------------------
    ## ---->  help(digitsBase) !
    ## ------------------------------
    if(any(x < 0))
	stop("'x' must be non-negative integers")
    if(any(x != trunc(x)))
	stop("'x' must be integer-valued")
    r <- matrix(0, nrow = ndigits, ncol = length(x))
    if(ndigits >= 1) for (i in ndigits:1) {
        r[i,] <- x %% base
        if (i > 1) x <- x %/% base
    }
    class(r) <- "basedInt"
    attr(r, "base") <- base
    r
}

bi2int <- function(xlist, base)
    vapply(xlist, function(u) polyn.eval(rev(u), base), numeric(1))

as.intBase <- function(x, base = 2) {
   xl <- if(is.character(x)) lapply(strsplit(x,""), as.integer)
        else if(is.numeric(x) && is.matrix(x)) tapply(x, col(x), c)
        else if(!is.list(x))
            stop("'x' must be character, list or a digitsBase() like matrix")
   bi2int(xl, base)
}

as.integer.basedInt <- function(x, ...)
    as.intBase(x, base = attr(x, "base"))

print.basedInt <- function (x, ...) {
    cat(sprintf("Class 'basedInt'(base = %d) [1:%d]\n",
                attr(x,"base"), ncol(x)))
    cx <- x
    attr(cx,"base") <- NULL
    print(unclass(cx), ...)
    invisible(x)
}

sHalton <- function(n.max, n.min = 1, base = 2, leap = 1)
{
    ## Purpose: Halton sequence H(k,b) for k=n.min:n.max -- for Quasi Monte Carlo
    ## ----------------------------------------------------------------------
    ## Author: Martin Maechler, Date: 29 Jul 2004, 21:34

    stopifnot((leap <- as.integer(leap)) >= 1)
    ## now do this via digitsBase(), later go directly
    nd <- as.integer(1 + log(n.max, base))
    dB <- digitsBase(if(leap == 1) n.min:n.max else seq(n.min, n.max, by=leap),
		     base = base, ndigits = nd)
    colSums(dB/base^(nd:1))
}

QUnif <- function(n, min = 0, max = 1, n.min = 1, p, leap = 1, silent = FALSE)
{
  ## Purpose: p-dimensional ''Quasi Random'' sample in  [min,max]^p
  ## ----------------------------------------------------------------------
  ## Author: Martin Maechler, Date: 29 Jul 2004, 21:43
  ## Example: plot(QUnif(1000, 2), cex=.6, pch=20, xaxs='i', yaxs='i')
    stopifnot(1 <= (n <- as.integer(n)), length(n) == 1,
              1 <= (p <- as.integer(p)), length(p) == 1,
	      length(min) == p || length(min) == 1,
	      length(max) == p || length(max) == 1,
	      1 <= (n.min <- as.integer(n.min)),
              1 <= (leap  <- as.integer(leap)),
              (n.max <- n.min + (n - 1:1)*leap) < .Machine$integer.max)
    pr. <- c(2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,
             89,97,101,103,107,109,113,127,131,137,139,149,151,157,163,167,173,
             179,181,191, 193,197,199,211,223,227,229,233,239,241,251,257,263,
             269,271,277,281,283,293,307,311,313,317,331,337,347,349,353,359,
             367,373,379,383,389,397,401,409,419,421,431,433,439,443,449,457)
    if(length(pr.) < p) {
	if(!silent)
	    message("enlarging internal prime table for \"large\" p=",p)
	Lp <- log(p)
	pr. <- primes(p*(Lp + log(Lp))) ## using p_n/n < log n + log log n
    }
    pr <- pr.[1:p]
    if(leap > 1 && any(leap == pr) && length(pr.) >= p+1) # take a non-leap pr
        pr <- c(pr[leap != pr], pr.[p+1])
    max <- rep.int(max, p)
    min <- rep.int(min, p)
    dU <- max - min
    r <- matrix(0., n, p)
    for(j in 1:p)
	r[,j] <- min[j] + dU[j] *
	    sHalton(n.max, n.min, base = pr[j], leap = leap)
    r
}



chars8bit <- function(i = 1:255)
{
    ## Purpose: Compute a character vector from its "ASCII" codes.
    ## We seem to have to use this complicated way thru text and parse.

    ## Author: Martin Maechler, Original date: Wed Dec 4, 1991
    ## this is an improved version of  make.ASCII() from ~/S/Good-string.S !
    ## ----------------------------------------------------------------
    i <- as.integer(i)
    if(any(i < 0 | i > 255)) stop("'i' must be in 0:255")
    if(any(i == 0))
	warning("\\000 (= 'nul') is no longer allowed in R strings")
    i8 <- apply(digitsBase(i, base = 8), 2, paste, collapse="")
    c8 <- paste0('"\\', i8, '"')
    eval(parse(text = paste0("c(",paste(c8, collapse=","),")")))
}

strcodes <- function(x, table = chars8bit(1:255))
{
    ## Purpose: R (code) implementation of old S's ichar()
    ## ----------------------------------------------------------------------
    ## Arguments: x: character vector
    ## ----------------------------------------------------------------------
    ## Author: Martin Maechler, Date: 23 Oct 2003, 12:42

    lapply(strsplit(x, ""), match, table = table)
}

## S-PLUS has  AsciiToInt() officially, and   ichar() in  library(examples):
AsciiToInt <- ichar <- function(strings) unname(unlist(strcodes(strings)))

helppdf <- function(topic, viewer = getOption("pdfviewer"), quiet = !interactive(),
                    ...) {
    if(!tryCatch(is.character(topic) && length(topic) == 1L,
		 error = function(e) FALSE))
	topic <- deparse1(substitute(topic))
    hh <- help(topic, help_type = "pdf", ...)
    pdfile <- with(attributes(hh), paste(topic, type, sep="."))
    ## almost all rendering & pdf creation happens here:
    print(hh, msg=!quiet)# utils:::print.help_files_with_topic() |--> .show_help_on_topic_offline(<texf>, ..)
                                        #                             ~~~~~~~~~~~~~~~~~~~~~~~~~~~ ..........
    if(length(viewer))
	system(paste(viewer, pdfile), wait = FALSE)
    ans <- file.path(getwd(), pdfile)
    if(quiet) invisible(ans) else ans
}




##-#### "Miscellaneous" (not any other category) ########
##-###   ============= ------------------------- ########

uniqueL <- function(x, isuniq = !duplicated(x), need.sort = is.unsorted(x))
{
    ## return list(ix, uniq)
    ## such that   all(x == uniq[ix])  and (of course)	uniq == x[isuniq]
    if(need.sort) {
	xs <- sort(x, index.return = TRUE)
	ixS <- xs $ ix
	isuniq <- isuniq[ixS]
	x <- xs$x
    }
    ix <- as.integer(cumsum(isuniq))
    if(need.sort)
	ix <- ix[sort.list(ixS)]
    list(ix = ix, xU = x[isuniq])
}


##' Constructor of a "list" (really an environment) of functions (and more)
##' which all *share* the same environment in which they exist
##' --> ../man/funEnv.Rd
##'  	~~~~~~~~~~~~~~~~
funEnv <- function(..., envir = NULL, parent = parent.frame(),
                   hash = (...length() > 100), size = max(29L, ...length())) {
    e <- list2env(list(...), envir=envir, parent=parent, hash=hash, size=size)
    for(n in names(e)) ## iff function or formula, set environment to 'e':
	if(is.function(e[[n]]) || (is.call(e[[n]]) &&
				   inherits(e[[n]], "formula")))
	    environment(e[[n]]) <- e
    e
}


is.whole <- function(x, tolerance = sqrt(.Machine$double.eps))
{
    ## Tests if a numeric scalar (or vector, matrix or array) is a whole
    ## number; returns an boolean object of the same dimension as x, each entry
    ## indicating whether the corresponding entry in x is whole.
    is.whole.scalar <-
	if (is.integer(x)) {
	    function(x) TRUE
	} else if (is.numeric(x)) {
	    function(x) isTRUE(all.equal(x, round(x), tolerance = tolerance))
	} else if (is.complex(x)) {
	    function(x)
		isTRUE(all.equal(Re(x), round(Re(x)), tolerance = tolerance)) &&
		isTRUE(all.equal(Im(x), round(Im(x)), tolerance = tolerance))
	} else stop("Input must be of type integer, numeric or complex.")

    if (is.null(dim(x)))
	vapply(x, is.whole.scalar, NA)
    else
	apply(x, seq_along(dim(x)), is.whole.scalar)
}

##'
##' @title Generate Random Date/Time Sequences
##' @param n number of entries to generate
##' @param min, max character strings or \R objects inheriting from \code{"POSIXt"}.
##' @return vector
##' @author Martin Maechler
##
## __ NOT YET EXPORTED
## FIXME: consider  'mean = Sys.time(), delta.tim = "1 month"'
## -----  ==> min = mean - as.difftime(delta.tim),
##            max = mean - as.difftime(delta.tim)
##  now <- Sys.time(); del <- as.difftime(100, units="weeks")
##  rDatetime(100, now-del, now+del)
rDatetime <- function(n, min = "1900-01-01", max = "2100-12-31") {
    if(is.character(min) || inherits(min, "POSIXt"))
        min <- as.POSIXct(min)
    else stop("'min' must be string (coercable to \"POSIXct\") or \"POSIXt\" object")
    if(is.character(max) || inherits(max, "POSIXt"))
        max <- as.POSIXct(max)
    else stop("'max' must be string (coercable to \"POSIXct\") or \"POSIXt\" object")
    stopifnot(length(min) == 1, length(max) == 1)
    struct(runif(n, as.numeric(min), as.numeric(max)),
           class = c("POSIXct", "POSIXt"), tzone = "")
}

###
### autoreg(),  mean.cor()  etc ... not yet
###
### if  we take them, use different file !!



####========== This is from /u/maechler/S/Good.S =============
####========== --------------------------------- =============

##-#### Plot / Devices  related stuff ########
##-### ----------------------------- ########

mpl <- function(mat, ...) {
  matplot(1:nrow(mat), mat, xaxt = 'n',...)
  if(0 == length(dn <- dimnames(mat)[[1]]))
    axis(1) else
    axis(1, at = 1:nrow(mat), labels = dn)
}

roundfixS <- function(x, method = c("offset-round", "round+fix", "1greedy"))
{
    ## Purpose: y := r2i(x) with integer y  *and* sum(y) == sum(x)
    ## Author: Martin Maechler, 28 Nov 2007
    n <- length(x)
    x0 <- floor(x)
    e <- x - x0 ## == (x %% 1) in [0, 1)
    S. <- sum(e)
    stopifnot(all.equal(S., (S <- round(S.))))
    method <- match.arg(method)

    ## The problem is equivalent to transforming
    ##   e[] \in [0,1)  into  f[] \in {0,1},  with sum(e) == sum(f)
    ## Goal: transform e[] into f[] gradually, by "shifting" mass
    ##       such that the sum() remains constant

    switch(method,
           "offset-round" = {
               ## This is going to be equivalent to
               ##  r := round(x + f)  with the correct     f \in [-1/2, 1/2], or
               ##  r == floor(x + f + 1/2) = floor(x + g), g \in    [0, 1]
               ##
               ## Need  sum(floor(e + g)) = S;
               ## since sum(floor(e)) == 0, sum(floor(e+1)) == n,
               ## we just need to floor(.) the S smallest, and ceiling(.) the others
	       if(S > 0) {
		   r <- numeric(n) # all 0; set to 1 those corresponding to large e:
		   r[sort.list(e, decreasing=TRUE)[1:S]] <- 1
		   x0 + r
	       } else x
           }, ## end{offset-round}

           "round+fix" = {
               r <- round(e)
               if((del <- S - sum(r)) != 0) { # need to add +/- 1 to 'del' entries
		   s <- sign(del) ## +1 or -1: add +1 only to r < x entries,
		   aD <- abs(del) ##          and -1 only to r > x entries,
                   ## those with the "worst" rounding are made a bit worse
                   if(del > 0) {
                       iCand <- e > r
                       dx <- (e - r)[iCand] # > 0
                   } else { ## del < 0
                       iCand <- e < r
                       dx <- (e - x)[iCand] # > 0
                   }
                   ii <- sort.list(dx, decreasing = TRUE)[1:aD]
		   r[iCand][ii] <- r[iCand][ii] + s
               }

               return(x0 + r)

           }, ## end{round+fix}

           "1greedy" = {
               ii <- e != 0
               while(any(ii)) {
                   ci <- cumsum(ii) # used to revert  u[ii] subsetting
                   m <- length(e. <- e[ii])
                   ie <- sort.list(e.)  # both ends are relevant
                   left <- e.[ie[1]] < 1 - e.[ie[m]]
                   iThis  <- if(left) 1 else m
                   iother <- if(left) m else 1
                   J <- which.max(ci == ie[iThis]) ## which(.)[1]  but faster
                   I <- which.max(ci == ie[iother])
                   r <- x[J]
                   x[J] <- k <- if(left) floor(r) else ceiling(r)
                   mass <- r - k        # if(left) > 0 else < 0
                   if(m <= 2) {   # short cut and evade rounding error
                       if(m == 1) {     # should happen **rarely**
                           if(!(min(abs(mass), abs(1-mass)) < 1e-10))
                               warning('m==1 in "1greedy" w/ mass not close to {0,1}')
                       } else { ## m==2
                           x[I] <- round(x[I] + mass)
                       }
                       break ## ii <- FALSE
                   }
                   else { ## m >= 3
                       e[J] <- if(left) 0 else 1
                       ii[J] <- FALSE
                       ## and move it's mass to the other end:
                       e.new <- e[I] + mass
                       if(e.new > 1)
                           stop("e[I] would be > 1 -- internal error")
                       else if(e.new < 0)
                           stop("e[I] would be < 0 -- internal error")
                       x[I] <- x[I] + mass
                       e[I] <- e.new
                   } ## m >= 3
               }     ## end{while}
               x

           }) # end{switch}
}## roundfixS


seqXtend <- function(x, length., method = c("simple","aim","interpolate"),
                    from = NULL, to = NULL)
{
  ## Purpose: produce a seq(.) covering the range of 'x' and INCLUDING x
  ## Author: Martin Maechler, Date: 28 Nov 2007 =======> ../man/seqXtend.Rd
    x <- unique(sort(x))
    n <- length(x)
    method <- match.arg(method)
    if(length. > n) {
        if((from_is1 <- is.null(from))) from <- x[1]
        if((from_isL <- is.null(to)))   to   <- x[n]
        if(method == "interpolate") {
            if(!from_is1) {
                if(from > x[1])
                    stop("'from' > min(x) not allowed for method ", method)
                x <- c(from, x)
            }
            if(!from_isL) {
                if(to < x[n])
                    stop("'to' < max(x) not allowed for method ", method)
                x <- c(x, to)
            }
            n <- length(x)
            dx <- x[-1] - x[-n] ## == diff(x)
            w  <- as.numeric(x[n]  - x[1])  ## == sum(dx);
            ##    as.n..(.) -> works with "Date" etc
            nn <- length. - n ## need 'nn' new points in 'n - 1' intervals
            ## how many in each?
            ## Want them approximately equidistant, ie. of width ~=  w / (nn + 1)
            ## but do this smartly such that  dx[i] / (k1[i] + 1) {= stepsize in interval i}
            ## is approximately constant
            k1 <- (nn + n-1) * dx / w  - 1 ## ==> sum(k1) == nn
            ## now "round" the k1[] such that sum(.) remains == nn
            k <- roundfixS(k1) ## keep the right border, drop the left
            seqI <- function(i) seq(x[i], x[i+1], length.out=k[i]+2)[-1]
            l.seq <- lapply(1:(n-1), seqI)
            ## do.call(c, *), e.g. for new (R-devel 4.1.x) c.Date() [KH]:
            c(x[1], if(is.object(x)) do.call(c, l.seq) else unlist(l.seq))
        } else {
            nn <- switch(method, "simple" = length.,
                         "aim" = length. - n + from_is1 + from_isL)
            ## a more sophisticated 'method' would have to use iteration, *or*
            ## interpolate between the 'x' values instead
            ## which might be considered to be too far from seq()
            unique(sort(c(x, seq(from, to, length.out = nn))))
        }
    } else x
}## {seqXtnd}

plotDS <-
function(x, yd, ys, xlab = "", ylab = "", ylim = rrange(c(yd, ys)),
         xpd = TRUE, do.seg = TRUE, seg.p = .95,
         segP = list(lty = 2, lwd = 1,   col = 2),
         linP = list(lty = 1, lwd = 2.5, col = 3), ...)
{
    ## Purpose:   Plot Data & Smooth
    ## -------------------------------------------------------------------------
    ## Arguments: do.seg: logical, plot "residual segments" iff T (= default).
    ## -------------------------------------------------------------------------
    ## Author: Martin Maechler, 1990-1994
    ##  2007: allow  ys to be a  (xs,ys)-xycoords structure, where {x[] \in xs[]}
    if((hasMoreSmooth <- !is.numeric(ys))) {
        ysl <- xy.coords(ys)
        ixs <- match(x, ysl$x)
        if(any(is.na(ixs)))
            stop("'x' inside the 'ys' structure must contain all the observational 'x'")
        ys <- ysl$y[ixs]
    }
    if(is.unsorted(x)) {
        i <- sort.list(x)
        x <- x[i]
        yd <- yd[i]
        ys <- ys[i]
    }
    addDefaults <- function(listArg) {
        ## trick such that user can call 'segP = list(col = "pink")' :
        nam <- deparse(substitute(listArg))
        P <- as.list(formals(sys.function(sys.parent()))[[nam]])[-1] # w/o "list"
        for(n in names(listArg)) P[[n]] <- listArg[[n]]
        P
    }

    plot(x, yd, xlab = xlab, ylab = ylab, ylim = ylim, ...) #pch = pch,
    if(!missing(linP))
        linP <- addDefaults(linP)
    if(hasMoreSmooth)
        lines(ysl,    xpd = xpd, lty = linP$lty, lwd = linP$lwd, col = linP$col)
    else lines(x, ys, xpd = xpd, lty = linP$lty, lwd = linP$lwd, col = linP$col)
    if(do.seg) {
        if(!missing(segP))
            segP <- addDefaults(segP)
        segments(x, seg.p*ys + (1-seg.p)*yd, x, yd,
                 xpd = xpd, lty = segP$lty, lwd = segP$lwd, col = segP$col)
    }
    invisible()
}



##-#### Matrix (or higher Array) stuff ########
##-### ------------------------------ ########

colcenter <- function(mat)  sweep(mat,2, apply(mat,2,mean))

col01scale <- function(mat, scale.func = function(x) diff(range(x)),
		       location.func = mean)
{
  ##-- See also 'scale' (std. S func) --
  mat <-  sweep(mat,2, apply(mat,2, location.func))
  sweep( mat, 2, apply(mat,2, scale.func), "/")
}

## diag.ex <- function(n)  --- now renamed :
diagX <- function(n)
{
  ## Purpose: Returns "the other diagonal" matrix
  ## Author: Martin Maechler, Date: Tue Jan 14 1992; Nov.2002
  ## ----------------------------------------------------------------
  ## Arguments: n: integer dimension of matrix
  ## ----------------------------------------------------------------
    m <- numeric(n * n)
    m[1L+ (n-1L)* seq_len(n)] <- 1
    dim(m) <- c(n,n)
    m
}

xy.grid <- function(x,y)
{
  ## Purpose: Produce the grid used by  persp, contour, .. as  N x 2 matrix
  nx <- length(x)
  ny <- length(y)
  cbind(rep.int(x,rep.int(ny,nx)),	rep.int(y,nx))
}

rot2 <- function(xy, phi)
{
  ## Purpose:  rotate xy-points by angle  'phi' (in radians)
  ## -------------------------------------------------------------------------
  ## Arguments: xy :  n x 2 matrix;   phi: angle (in [0, 2pi])
  ## -------------------------------------------------------------------------
  ## Author: Martin Maechler, Date: 26 Oct 94, 22:16
  co <- cos(phi); s <- sin(phi)
  xy %*% t( matrix(c(co,s, -s, co), 2,2) )
}

tapplySimpl <- function(X, INDEX, FUN, ...)
{
  ## Purpose: Nicer result for tapply(..) when Function returns
  ## 	      vector AND there is >= 2 "INDEX", i.e., categories.
  ## -------------------------------------------------------------------------
  ## Arguments: as for tapply,
  ##	FUN: Must return [named, if possible] FIXED length vector
  ##	      [num/char]   EVEN for  NULL and NA !
  ## -------------------------------------------------------------------------
  ## Author: Martin Maechler, Date: 14 Jun 93, 17:34
  rl <- tapply(X, INDEX, FUN, ..., simplify = TRUE)
  if (is.list(rl)) { #-- when  >=2 indices  AND  length(FUN(x)) > 1  ---
    if(any(Nas <- unlist(lapply(rl, is.null))))
      rl[Nas]  <- list(FUN(NULL))
    array(unlist(rl),
	  dim = c(length(rl[[1]]), dim(rl)),
	  dimnames = c(list(names(rl[[1]])), dimnames(rl)) )
  } else rl
}


##-#### "Calculus" Mathematical stuff ########
##-### ----------------------------- ########

u.log <- function(x, c = 1)
{
  ## Purpose:  log(.) only for high x- values ... identity for low ones
  ##  This  f(x) is  continuously differentiable (once).
  ##  f(x) = x				  for |x| <= c
  ##  f(x) = sign(x)*c*(1 + log(|x|/c))       for |x| >= c
  ## -------------------------------------------------------------------------
  ## Arguments: x: numeric vector;  c: scalar > 0
  ## -------------------------------------------------------------------------
  ## Author: Martin Maechler, Date: 24 Jan 95, 17:28
  if(!is.numeric(c)|| c < 0) stop("'c' must be positive number")
  r <- x
  to.log <- abs(x) > c ; x <- x[to.log]
  r[to.log] <- sign(x) * c * (1 + log(abs(x/c)))
  r
}

xy.unique.x <- function(x, y, w, fun.mean = mean, ...)
{
    ## Purpose: given 'smoother data' (x_i, y_i) [and maybe weight  w_i]
    ##	      with multiple x_i, use unique x's, replacing y's by their mean
    ## -------------------------------------------------------------------------
    ## Author: Martin Maechler, Date:  8 Mar 93, 16:36
    ##--*--*--*--*--*--*--*--*--*-- x,y,w  treatment --*--*--*--*--*--*--*--*--
    if(missing(x)) x <- time(y)  else
    if(missing(y)) {
        if(is.list(x)) {
            if(any(is.na(match(c("x", "y"), names(x)))))
                stop("cannot find x and y in list")
            y <- x$y; x <- x$x; if(!is.null(x$w)) w <- x$w
        } else if(is.complex(x)) {
            y <- Im(x); x <- Re(x)
        } else if(is.matrix(x) && ncol(x) == 2) {
            y <- x[, 2]; x <- x[, 1]
        } else if(is.matrix(x) && ncol(x) == 3) {
            y <- x[, 2]; w <- x[, 3]; x <- x[, 1]
        } else {
            y <- x; x <- time(x)
        }
    }
    n <- length(x)
    if(n != length(y)) stop("lengths of x and y must match")
    if(missing(w))  w <- rep.int(1,n)
    else if(n != length(w)) stop("lengths of x and w must match")
    ##--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--*--
    gr <- match(x, ux <- unique(x, ...))
    cbind(x = ux,
          y = tapply(y, gr, FUN = fun.mean),
          w = tapply(w, gr, FUN = sum))
}



##-#### Non-calculus ("Discrete") Mathematical stuff ########
##-### -------------------------------------------- ########

lseq <- function(from, to, length)
{
    ## Purpose: seq(.) : equidistant on log scale
    ## ----------------------------------------------------------------------
    ## Author: Martin Maechler, Date:  3 Feb 2005, 08:34
    stopifnot(from > 0)
    exp(seq(log(from), log(to), length.out = length))
}

inv.seq <- function(i) {
  ## Purpose: 'Inverse seq': Return a short expression for the 'index'  'i'
  ## --------------------------------------------------------------------
  ## Arguments: i: vector of (usually increasing) integers.
  ## --------------------------------------------------------------------
  ## Author: Martin Maechler, Date:  3 Oct 95, 18:08
  ## --------------------------------------------------------------------
  ## EXAMPLES: cat(rr <- inv.seq(c(3:12, 20:24, 27, 30:33)),"\n"); eval(rr)
  ##           r2 <- inv.seq(c(20:13, 3:12, -1:-4, 27, 30:31)); eval(r2); r2
  li <- length(i <- as.integer(i))
  if(li == 0) return(expression(NULL))
  else if(li == 1) return(as.expression(i))
  ##-- now have: length(i) >= 2
  di1 <- abs(diff(i)) == 1	#-- those are just simple sequences  n1:n2 !
  i <- i + 0 # coercion to "double", so result has no 'L' appended integers.
  s1 <- i[!c(FALSE,di1)] # beginnings
  s2 <- i[!c(di1,FALSE)] # endings
  mkseq <- function(i, j) if (i==j) i else call(':', i, j)
  as.call(c(list(as.name('c')),
	    mapply(s1, s2, FUN=mkseq, SIMPLIFY=FALSE, USE.NAMES=FALSE)))
}

iterate.lin.recursion <- function(x, coeff, delta = 0, nr.it)
{
  r <- c(x, numeric(nr.it))
  n <- length(x)
  ic <- length(coeff):1
  for(i in 1:nr.it)
    r[n + i] <- delta + c(coeff %*% r[n + i - ic])
  r
}

quadrant <- function(x,y=NULL) {
    xy <- xy.coords(x,y); x <- xy$x; y <- xy$y
    Sgn <- function(u) ifelse(u >= 0, 1, -1)
    y <- Sgn(y); 2 - y + (y != Sgn(x))
}

n.code <- function(n, ndig = 1, dec.codes = c("","d","c","k"))
{
  ##-- convert "round integers" to short char.strings
  ##-- useful to build-up  variable names in simulations
  ##-- e.g.,
  nd <- length(dec.codes)
  e10 <- pmin(floor(log10(n) + 1e-12), nd - 1)
  if (any(e10 < 0)) {
      e10 <- pmax(0, e10) ; warning("some 'n' too small")
  }
  ## IDEA: Things like
  ## ---- n.code(c(2000,1e4,5e4,6e5,7e6,8e7),
  ##             dec. = c("","d","c","k","-","-","M"))
  ## could work;  (not quite yet, see ex. above)
##-   if(any(id <- is.na(dec.codes) | dec.codes == "-")) {
##-       ## then use previous code for these (things like "20k", "300k")
##-       ## sequentially from the left:
##-       for(k in which(id)) {
##-           dec.codes[k] <- dec.codes[k - 1]
##-           ii <- 1+e10 == k
##-           e10[ii] <- e10[ii] - 1
##-       }
##-   }
  paste0(round(n/ 10^(e10 + 1 - ndig)), dec.codes[1 + e10])
}

code2n <- function(ncod, ndig = 1, dec.codes = c("","d","c","k"))
{
  ## The inverse function to n.code
  le <- nchar(ncod)
  cod <- substring(ncod, le, le)
  as.integer(substring(ncod, 1, le-1)) * 10^(match(cod, dec.codes)-1)
}

nr.sign.chg <- function(y)
{
  ## Purpose:  Compute number of sign changes in sequence
  ## Be careful with y[i] that were 0 !!
  y <- sign(c(y))
  y <- y[y != 0]
  sum(y[-1] != y[-length(y)])
}

unif <- function(n, round.dig = 1 + trunc(log10(n)))
{
  ## Purpose: Give regular points on [-c,c] with mean 0 and variance ~= 1
  if(n %% 2 == 0) {
    if(n > 0) round((2 * 1:n - (n + 1)) * sqrt(3/(n * (n + 1))), round.dig)
  } else {
    m <- n %/% 2 #--> m+1 = (n+1)/2
    ( - m:m) * round(sqrt(6/((m + 1) * n)), round.dig)
  }
}

prt.DEBUG <- function(..., LEVEL = 1) {
  stop("prt.DEBUG() is defunct: use a 'verbose' argument or options(verbose=.) instead")
  ## if (exists("DEBUG", where = 1) && DEBUG >= LEVEL )#
  ## ##
  ## cat(paste0("in '", sys.call(sys.nframe()-1)[1], "':"), ..., "\n")
}



##' @title Read an Emacs Org Table by read.table()
## --> ../man/read.org.table.Rd
read.org.table <- function(file, header = TRUE, skip = 0,
                           encoding = "native", fileEncoding = "",
                           text, quiet=FALSE, ...) {
    ## file - text   handling --- cut'n'paste from read.table()'s header
    if (missing(file) && !missing(text)) {
	file <- textConnection(text, encoding = "UTF-8")
	on.exit(close(file))
    }
    if(is.character(file)) {
        file <- if(nzchar(fileEncoding))
            file(file, "rt", encoding = fileEncoding) else file(file, "rt")
        on.exit(close(file))
    }
    if(!inherits(file, "connection"))
        stop("'file' must be a character string or connection")
    if(!isOpen(file, "rt")) {
        open(file, "rt")
        on.exit(close(file))
    }
    if(skip > 0L) readLines(file, skip)
    ll <- readLines(file, encoding=encoding)
    close(file); on.exit()
    ## drop |--------+---------+--------+--|  :
    if(length(i <- grep("---+\\+--", ll[1:3]))) ll <- ll[-i]
    ## drop beginning and ending "|" :
    ll <- sub("^ *\\|", "",
              sub("\\| *$", "", ll))
    if(header) { ## assume header in first 2 lines
        ii <- if(nchar(ll[1]) < 2) 2 else 1
        ## header line
        hl <- ll[ii]
        ## drop header line(s)
        ll <- ll[-seq_len(ii)]
        ## split the header lines into column names
        col.names <- sub("^ +", "", sub(" +$", "", strsplit(hl, " *\\| *") [[1L]]))
    }
    ## drop empty lines at end only
    while(grepl("^ *$", tail(ll, 1L))) ll <- ll[-length(ll)]
    nent <- count.fields(textConnection(ll), sep="|")
    ok <- nent == (p <- length(col.names))
    nrows <- if(all(ok)) length(ok)
             else match(FALSE, ok) - 1L # 1..n, where n+1 contains the first non-match
    if("nrows" %in% ...names())
        nrows <- min(nrows, list(...)$nrows)
    else if(!quiet && nrows < 0.95*length(nent))
        message(
            sprintf("Will read the first %d rows (of each %d columns) of %d (non-empty non-header) lines",
                    nrows, p, length(nent)))
    f.ll <- textConnection(ll)# , encoding = "UTF-8"  is *NOT* good
    on.exit(close(f.ll))
    read.table(f.ll, header=FALSE, sep = "|",
               col.names = col.names, nrows=nrows, ...) # , encoding = "UTF-8" *not* good
}

## as compact numeric format() as possible
formatN <- function(x, digits = 1L, sci = c(-1L,-2L)) {
    stopifnot(is.numeric(x) || is.complex(x), sci == as.integer(sci))
    if(length(sci) == 1L) sci <- c(sci,sci)
    sub("e-0*", "e-",
        sub("e\\+0*","e",
            vapply(x, function(u) format(u, digits=digits, scientific = sci[if(abs(u) < 1) 1 else 2]), "")))
}

## called this 'show01()` first --- useful to show long bit-strings / -vectors
##' @example z <- rbinom(120, size = 1, prob = 1/2); compact01(z)  compact01(z, 40) ...
compact01 <- function(z, ncol = 50L) {
    stopifnot(ncol == (nc <- as.integer(ncol)), nc >= 1L)
    if((lz <- length(z)) == 0L)
        return(matrix(character(), 0L, 1L))
    sn <- symnum(z == 1)# sn[]: character & 'noquote'
    if (nc > lz)
        nc <- lz
    else if((r <- lz %% nc) != 0) # fill "remainder" with blanks
        sn[lz + seq_len(nc - r)] <- " "
    ss <- matrix(sn, ncol=nc, byrow=TRUE)
    ## return character matrix  {K x 1} each row a string of length `nc`,
    ##                                  with empty colname and 'bit counting' rownames):
    ## if R >= 4.1.0  apply(ss, 1L, paste, collapse="") |> cbind(deparse.level = 0L) |> noquote(),
    `dimnames<-`(noquote(cbind(apply(ss, 1L, paste, collapse=""), deparse.level = 0L)),
                 list(format(1 + nc*(0:(nrow(ss)-1L))), ""))
}
