It would be about the same amount of work (i.e. not much) to re-write the algorithms in MATLAB or some other high-level language (you could probably do it in Mathematica but it would almost certainly be excruciatingly slow), and it would be more work but would run much faster to code them in C or C++ (or FORTRAN); FFT routines should be available from netlib or Numerical Recipes (Press et al., Cambridge University Press).
I hope the examples below should be relatively coherent even if you don't know S: a couple of things to note are that "<-" is the assignment symbol in S, and that "outer" is an "outer product" function for generating tables. ("rnorm" and "runif" generate vectors of Gaussian and uniform deviates respectively. "apply" applies a function to a vector or matrix.)
fland1 <- function(H=0.5,n=16) {
b <- H+0.5
sdvec <- (1:n)^(-b)
return(Re(fft(complex(modulus=rnorm(n,mean=0,sd=sdvec),
arg=runif(n,-pi,pi)),
inverse=T)))
}
Examples with H ranging from 0.1 to 0.9
(results from the following).
par(mfrow=c(3,3)) for (i in seq(0.1,0.9,by=0.1)) plot(fland1(H=i,n=1024),type="l")
fland2 <- function(H=0.5,n=16) {
b <- H+0.5
sdvec <- (1:n)^(-b)
sdmat <- outer(sdvec,sdvec,"*")
return(Re(fft(matrix(complex(modulus=rnorm(n^2,mean=0,sd=sdmat),
arg=runif(n^2,-pi,pi)),
nrow=n),
inverse=T)))
}
# example
image(fland2(H=0.7,n=256))
Does anyone know why this looks so "rectangular"?
(Actually, I think I do now -- and I'm surprised that Hastings
and Sugihara don't think about this, if I'm right.
The formula for the power spectrum is S(m,n) = c*m^(-B)*n^(-B) (with
B=2*H+1), which says that power falls off linearly in the
x and y directions, not radially. I would guess that
S(m,n) = c*(m^2+n^2)^(-B/2) would work better.)
This is what it looks like (for example)
if I take the modulus instead of the
real part of the landscape ...
# randomize phase of a 2-D landscape
rland1 <- function(x) {
n <- nrow(x) # linear dimension
l <- length(x) # total number of nodes
y <- fft(x)
z <- matrix(complex(modulus=Mod(y),arg=runif(l,-pi,pi)))
y <- fft(z,inverse=T)/l
return(matrix(Mod(y),nrow=n))
}
Once we have that code, we can generate a "landscape" which
is really just the two-dimensional realization of the radial
correlation function. This code takes a vector or a function
of distance representing the radial ACF, applies it to a 2-D
grid, and passes the result to the previous code.
simland2 <- function(r,n) {
## given a correlation-by-(radial)-distance function r
## (which can be either a vector or a function)
## simulate a 2-D landscape with linear size n
## substitute into a grid of radial distances
g <- outer(1:n,1:n,function(x,y)sqrt(x^2+y^2))
if (is.function(r)) {
gr <- apply(g,c(1,2),r)
} else if (is.numeric(r)) {
gr <- matrix(r[round(g)],nrow=n) }
rland1(gr) # pass it to a function which FFTs, randomizes phases,
# and inverse-FFTs
}
A sample, using correlation function exp(-x/20)+0.1*N(0,1):
n <- 128 # pick some correlation function ## test: exponential + Gaussian noise r <- exp(-(1:(2*n))/20)+0.1*rnorm(2*n) r[r>1] <- 1 g2 <- simland2(r,n)With this correlation function:
We get this landscape:
Of course, I should check this by going back and computing the ACF or the variogram from the simulated landscape.
Update, June 1999: playing around with this some more with one-dimensional landscapes, actually checking the covariances, gives the following conclusions:
chop to get rid of small numeric gunk);
chop _ function(x,eps=1e-10) {
if (is.complex(x)) {
x _ complex(real=chop(Re(x)),imaginary=chop(Im(x)))
if (all(Im(x)==0)) x _ as.numeric(x)
}
else
x[abs(x)0) cat("warning: making n even\n")
N <- n %/% 2
g <- (1:N)*dx
if (is.function(r)) {
gr <- sapply(c(g,rev(g)),r)
} else if (is.numeric(r)) {
if (length(r)>N) {
cat("warning: truncating r to allow symmetry\n")
r <- r[1:N]
}
if (length(r)
- note constraint that sum(cov(x))=0
- examples:
autocov _ function(x) { # kluge!! to match autocov.cvec()
# two FFT calls
N _ length(x)
f _ Mod(fft(x))^2/(N*(N-1)) # (why /(N*(N-1))?)
f0 _ f[1] # mean correction (??)
chop(fft(f,inverse=TRUE))-f0
}
autocorr _ function(x)
autocov(x)/var(x)
x <- (0:63)/16
c1 <- exp(-x)
c1 <- exp(-x^2)
c2 <- autocorr(simland1c(c1,256))
c2b <- autocorr(simland1c(c1,1024))
c2c <- autocorr(simland1c(c1,4096))
plot((0:255)/16,c2,xlim=c(0,8))
lines((0:1023)/16,c2b)
lines((0:4095)/16,c2c)
lines(x,c1,col="red") # scaling:
c1m <- mean(c1)*(2*length(c1))/256
c1c <- c1*(1+c1m)-c1m
lines(x,c1c,col="blue")
- also note that none of this is thoroughly tested yet.
- Some C code (relies on Numerical Recipes routines
realft/four1:)
#include
#include
#include "machdefs.h"
#include "utils.h"
#include "four1.h"
#include "ran.h"
double complex_modarg_to_reim(double *x, double *y) {
double m;
m=*x;
*x = m*cos(*y);
*y = m*sin(*y);
}
int main (int argc, char **argv) {
int i;
int n=64;
float *x;
float len=100;
float stddev=0.2;
double a,b;
INITRAN(1234);
x = (float *)malloc(sizeof(float)*n);
for (i=0; i