U1xU1

2 Dim U(1)xU(1)

create a lattice N\times N

create_random_lattice_2d<-function(N, tot=FALSE){
  if (tot){
    p<- matrix(runif(2*N), nrow=N,ncol=2)
    if (N>1) p<- p[order(p[,1]),]
    return (p)  
  }
  p<- matrix(runif(2*N^2), nrow=N^2,ncol=2)
  p<- p[order(p[,1]),]
  return (p)
}
p<-create_random_lattice_2d(3)
p
              [,1]      [,2]
 [1,] 0.0009919195 0.9456841
 [2,] 0.0302922411 0.2220373
 [3,] 0.0406279170 0.6940398
 [4,] 0.2072489182 0.1544641
 [5,] 0.2260830447 0.1831051
 [6,] 0.3932611791 0.3445549
 [7,] 0.4060240148 0.9589056
 [8,] 0.4533820602 0.8299881
 [9,] 0.4972145776 0.8270449
create_regular_lattice_2d<-function(N){
  pr<-matrix(rep(0,N^2), nrow=N^2,ncol=2)
  for (i in c(0:(N-1) ) )
    for (j in c(0:(N-1) ) ){
      pr[ i+j*N+1, 1]<- (i)/N
      pr[ i+j*N+1, 2]<- (j)/N
    }
  return(pr)
}
noise_on_the_regular_lattice_2d<-function(N, R){
  p<-create_regular_lattice_2d(N)
  for (i in seq_along(p[,1])){
    p[i,1]<- p[i,1] +runif(1)*R
    p[i,2]<- p[i,2] +runif(1)*R
  }
  return(p)
}

p<-create_regular_lattice_2d(3)
p
           [,1]      [,2]
 [1,] 0.0000000 0.0000000
 [2,] 0.3333333 0.0000000
 [3,] 0.6666667 0.0000000
 [4,] 0.0000000 0.3333333
 [5,] 0.3333333 0.3333333
 [6,] 0.6666667 0.3333333
 [7,] 0.0000000 0.6666667
 [8,] 0.3333333 0.6666667
 [9,] 0.6666667 0.6666667

geometry

compute_distance_forward<-function(p,n,mu){
  # for the direction mu we compute the distance always forward 
  h<-(n[mu]-p[mu])
  if (h<=0){
    h<-  h+1
  }
  nu<- c(1,2)[-mu]
  # for this direction we need to consider that the distance across the boundary
  # could be smaller
  h12<-min( (n[nu]-p[nu])^2, (1+n[nu]-p[nu])^2 )

  return(h^2+ h12)
}

forward_neigh_2d<-function(p,mu){
  N<-dim(p)[1]
  res<- list()
  res$id<-array(dim = c( N,2 ))# id first neig, id second neigh
  res$h<-array(dim = c( N,2 ))# h first neig, h second neigh
  for( i in seq_along(p[,1])){
  
    count <- 1
    neig<- list()
    neig$h<-rep(i, N-1)
    neig$id<-rep(i, N-1)
    for( j in seq_along(p[,1])[-i]){
      neig$h[count]<-compute_distance_forward(p[i,],p[j,],mu)
      neig$id[count]<- j
      count<- count +1
    }
    # print(neig$h)
    # print(neig$id)
    found<- 0
    # print((neig$h))
    # print((neig$id))
    for (j in order(neig$h)){
      # cat(j, found,"\n")
      if (found==0){
        # cat("found =0 \n")
        res$id[i,1]<-neig$id[j]
        res$h[i,1]<-neig$h[j]
        found <- found +1
        next
      }
      else if (found==1){
        # cat("found =1 \n")
        #print(neig$h[j])
        #print(neig$id[j])
        v1<-p[res$id[i,1], ]- p[i,]
        v2<-p[neig$id[j],]- p[i,]
        cth<- sum(v1*v2)/sqrt(sum(v1^2) *sum(v2^2))
        if( (1-abs(cth))>1e-9){
          res$id[i,2]<-neig$id[j]
          res$h[i,2]<-neig$h[j]
          fount <- found +1
          break
        }
      }
      else{
        break
      }
    }
    
  }
    
  return(res)
}
nxp<-forward_neigh_2d(p,1)
nxp
$id
      [,1] [,2]
 [1,]    2    5
 [2,]    3    6
 [3,]    1    4
 [4,]    5    2
 [5,]    6    3
 [6,]    4    1
 [7,]    8    5
 [8,]    9    6
 [9,]    7    4

$h
           [,1]      [,2]
 [1,] 0.1111111 0.2222222
 [2,] 0.1111111 0.2222222
 [3,] 0.1111111 0.2222222
 [4,] 0.1111111 0.2222222
 [5,] 0.1111111 0.2222222
 [6,] 0.1111111 0.2222222
 [7,] 0.1111111 0.2222222
 [8,] 0.1111111 0.2222222
 [9,] 0.1111111 0.2222222

compute L

compute_gamma<- function(i, p, neigh, mu){
  in1<- neigh$id[i,1]
  in2<- neigh$id[i,2]
  
  v<-  matrix(c(0,0,0,0), nrow=2,ncol=2)
  v[,1]<- p[in1,]-p[i, ] 
  v[,2]<- p[in2,]-p[i, ] 
  if(v[mu,1]<=0) v[mu,1]<-v[mu,1]+1
  if(v[mu,2]<=0) v[mu,2]<-v[mu,2]+1
  nu<- c(1,2)[-mu]
  v[nu,1] <-  min(p[in1,nu]-p[i,nu ], p[in1,nu]-p[i, nu]+1)
  v[nu,2] <-  min(p[in2,nu]-p[i,nu ], p[in2,nu]-p[i, nu]+1)
  ## solve the SLE V * amu = emu
  emu <- c(0,0)
  emu[mu] <- 1
  ## vectors in v do not need to be independent
  g <- solve(a=v, b=emu)

  return(g)
}


compute_deriv<- function(p,mu){
  N<-dim(p)[1]
  h<-1/dim(p)[1]
  nxp<-forward_neigh_2d(p,mu)
  L<-matrix(rep(0,N*N), nrow=N,ncol=N)
  
  for(i in c(1:N)){
    g<-compute_gamma(i,p,nxp,mu)
   
    L[i,i]<-L[i,i] -(g[1]+g[2])
    
    L[i,nxp$id[i,1]]<- L[i,nxp$id[i,1]]+ g[1]
    L[i,nxp$id[i,2]]<- L[i,nxp$id[i,2]]+ g[2]
  }
  return(-1i*L)
}

plot grid

library(ggplot2)
library(plotly)

Attaching package: 'plotly'
The following object is masked from 'package:ggplot2':

    last_plot
The following object is masked from 'package:stats':

    filter
The following object is masked from 'package:graphics':

    layout
wrap_neighbour<-function(p,nxpi,mu){
  N<-dim(p)[1]
  enda<-p[nxpi, ]
 
  nu<-c(1,2)[-mu]
  enda[which(enda[,mu]-p[,mu]<0),mu] <- enda[which(enda[,mu]-p[,mu]<0),mu]+1
  for (i in seq_along(enda[,nu])){
    h1<-(enda[i,nu]-p[i,nu])^2
    h2<-(enda[i,nu]-p[i,nu]+1)^2
    if(h2< h1 )
      enda[i,nu]<- enda[i,nu]+1
  }
  return(enda)
}


plot_grid<-function(p,nxp,mu=1){
  gg<-ggplot()+theme_bw()
  N<-dim(p)[1]
  enda<-wrap_neighbour(p, nxp$id[,1] ,mu)
  enda2<- (enda+p)/2
  
  gg<-gg+ geom_segment(aes(x = p[,1], y = p[,2],
                       xend = enda[,1], yend = enda[,2]) ,color="black",
                       )
  

  endb<-wrap_neighbour(p, nxp$id[,2] ,mu)
  endb2<- (endb+p)/2
  
  gg<-gg+ geom_segment(aes(x = p[,1], y = p[,2],
                        xend = endb[,1], yend = endb[,2]) ,color="blue"
                       )
  
  d<- data.frame(x=c(p[,1],enda[,1],endb[,1]), y=c(p[,2],enda[,2],endb[,2]) , t=rep(c(1:N),3) )
  gg<-gg+geom_polygon(data = d, alpha = 0.5 , mapping = aes(x=x, y= y , fill=as.factor(t))) 
  
  gg<-gg + scale_fill_manual(values = rep(2,3*N))+ theme(legend.position="none")
  
  gg<- gg+ geom_point(aes(x=p[,1], y=p[,2]), color="red")
  gg<- gg+ geom_point(aes(x=p[,1]+1, y=p[,2]), color="violet")
  gg<- gg+ geom_point(aes(x=p[,1], y=p[,2]+1), color="violet")
  gg<- gg+ geom_point(aes(x=p[,1]+1, y=p[,2]+1), color="violet")
  gg <- gg+ geom_vline(xintercept = 1, color="green")
  gg <- gg+ geom_hline(yintercept = 1, color="green")
  gg<-gg +xlim(0,2)+ylim(0,2)
  fig<-ggplotly(gg,dynamicTicks = TRUE) %>% 
    add_annotations(text = "",
                    showarrow = TRUE,
                    xref = "x", axref = "x",
                    yref = "y", ayref = "y",
                    x = endb2[,1],
                    ax = p[,1],
                    y = endb2[,2],
                    ay = p[,2],   arrowcolor='blue') %>%
    add_annotations(text = "",
                    showarrow = TRUE,
                    xref = "x", axref = "x",
                    yref = "y", ayref = "y",
                    x = enda2[,1],
                    ax = p[,1],
                    y = enda2[,2],
                    ay = p[,2])
return(fig)
}

p<-create_random_lattice_2d(5)
nxp<-forward_neigh_2d(p,1)
plot_grid(p,nxp,1)
p<-create_regular_lattice_2d(5)
nxp<-forward_neigh_2d(p,1)
plot_grid(p,nxp,1)
mu<-1
p<-create_random_lattice_2d(10)
L<-compute_deriv(p,mu)
mean(Im(diag(L)))
[1] 22.10056
p<-create_random_lattice_2d(20)
L<-compute_deriv(p,mu)
mean(Im(diag(L)))
[1] -44.21071

compute eigen

compute_eigen<- function(Ns, lattice, ...){
  df<-data.frame("id"=c(0), "l"=c(0), "N"=c(0), "type"=c(0))
  df<-df[-1,]
  for (N in Ns){
    p<- lattice(N,...)
    
    L2<-matrix(rep(0,N^4), nrow=N^2,ncol=N^2)
    L2r<-matrix(rep(0,N^4), nrow=N^2,ncol=N^2)
    for(mu in c(1,2)){
      L<-compute_deriv(p,mu)
      L2<-L2+ Conj(t(L)) %*% L
      Lr<- (L + Conj(t(L)) )/2
      L2r<-L2r+ Lr %*% Lr
    }
    l<-eigen(L2)$values[(N^2):1]
    df<-rbind(df,data.frame("id"=(c(1:N^2)-1), "l"=l, "N"=rep(N^2,N^2), "type"=rep("(ada)",N^2) ))
    #l<-eigen(L2r)$values[(N^2):1]/N^2# N^2 removes the lattice spacing so that
    # the eigenvalues from 1/a sin^2(ap) --->  sin^2(ap)
    #df<-rbind(df,data.frame("id"=(c(1:N^2)-1)/N^2, "l"=l, "N"=rep(N^2,N^2), "type"=rep("(a+ad)",N^2) ))
    
  }
  return(df)
}

For regular lattice the eigenvalues are

\sum_\mu(-i\tilde\nabla)^2 \to\sum_\mu L^2\sin^2(2\pi p_\mu/L) \sum_\mu -i\nabla^+(-i\nabla^+)^\dagger = \sum_\mu -\nabla^+\nabla^- = \sum_\mu 4L^2\sin^2(\frac{2\pi p_\mu}{2L}) in both cases p_\mu=0, 1, 2, ..., (L-1). In the continuum limit the eigenvalues are:

  1. Zero with degeneracy 1

  2. 2(2\pi)^2 with degeneracy 4. It comes from the limit

\lim_{L\to\infty}L^2 \sin^2(2\pi/L)=(2\pi)^2 the degeneracy is due to the choices p_1=1,-1 with p_2=0 and p_1=0 with p_2=1,-1.

  1. 2(2\pi)^2, with degeneracy 4 (\pm p times 2 dimension)

  2. (4\pi)^2, with degeneracy 4 (2 swapping p_1 and p_2 times 2 for \pm p).

  3. (4\pi)^2+(2\pi)^2, with degeneracy 8 (2 dimension times \pm p, times 2 for the swapping of p_1 and p_2).

Below we compute the eigenvalues for the -\nabla^-\nabla^+ operator

df<-compute_eigen(c(2,3,4,10,20), create_regular_lattice_2d)
gg<-ggplot()+theme_bw()
gg<- gg+ geom_point(data=df, aes(x=id, y=l, shape=as.factor(N), color=as.factor(N) ),
                    )+ylim(0,(4*pi)^2+(2*pi)^2+1)+xlim(0,1+4+4+4+8)

gg<- gg +geom_line(aes(x=c(1:4), y=(2*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(5:8), y=2*(2*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(9:12), y=(4*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(13:20), y=(4*pi)^2+(2*pi)^2, color="continuum", shape="continuum"  ))
ggplotly(gg, dynamicTicks = TRUE) %>% layout(yaxis = list(autorange=FALSE),
                                             xaxis = list(autorange=FALSE))

on a lattice 10x10 we add some noise r to the point and we compute the spectrum

#df<-compute_eigen(c(2,3,4,10,20), noise_on_the_regular_lattice_2d,1e-9)
df<-compute_eigen(c(10), noise_on_the_regular_lattice_2d,1e-9)
df$r<- 1e-9
df1<-compute_eigen(c(10), noise_on_the_regular_lattice_2d,1e-6)
df1$r<- 1e-6
df<-rbind(df,df1)
df1<-compute_eigen(c(10), noise_on_the_regular_lattice_2d,1e-5)
df1$r<- 1e-5
df<-rbind(df,df1)

gg<-ggplot()+theme_bw()
gg<- gg+ geom_point(data=df,aes(x=id, y=l, shape=as.factor(r), color=as.factor(r) ),
                    )+ylim(0,(4*pi)^2+(2*pi)^2+1)+xlim(0,1+4+4+4+8)

gg<- gg +geom_line(aes(x=c(1:4), y=(2*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(5:8), y=2*(2*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(9:12), y=(4*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(13:20), y=(4*pi)^2+(2*pi)^2, color="continuum", shape="continuum"  ))
ggplotly(gg, dynamicTicks = TRUE) %>% layout(yaxis = list(autorange=FALSE),
                                             xaxis = list(autorange=FALSE))

Delaunay triangulation

compute_distance2_wrapped<-function(p,n){
  # for the direction mu we compute the distance always forward 
  # h1<-min(  (n[1]-p[1])^2, (1+n[1]-p[1])^2 )
  # h2<-min(  (n[2]-p[2])^2, (1+n[2]-p[2])^2 )
  h1<- (n[1]-p[1])^2
  h2<- (n[2]-p[2])^2
  
  return(h1+ h2)
}


wrap<-data.frame("x"=c(0,0,0,1,1,1,-1,-1,-1) , "y"=c(0,1,-1,0,1,-1,0,1,-1)  )

check_point_inside_wrapped<-function(center, r2 ,p){
  for (w in seq_along(wrap[,1])){
    d2<-compute_distance2_wrapped( p+wrap[w,], center )
    if(d2<r2) return(TRUE)
  }
  
  return(FALSE)
}
#c(1,3,4), c(2,5,6)
sort_links<-function(links, p1, p2  ){
  to_switch<-which(links[,p1[1]]>links[,p2[1]])
  
  for (i in seq(p1)){
    tmp <- links[to_switch, p2[i]]
    links[to_switch, p2[i]] <- links[to_switch, p1[i]]
    links[to_switch, p1[i]] <- tmp
  }
  return(links)
}

library(dplyr) #use to remove doublers  

Attaching package: 'dplyr'
The following objects are masked from 'package:stats':

    filter, lag
The following objects are masked from 'package:base':

    intersect, setdiff, setequal, union
remove_doublers<-function(links){
  #df<- links[,c(1,2)]
  #df$l2<-  (links$p1x-links$p2x)^2+(links$p1y-links$p2y)^2
  o<-order(links$p1)
  links<- links[o,]
  
  #links<-distinct(links,p1,p2,l2,.keep_all = TRUE)
  rem<-c()
  for (i in c(1:(length(links$l2)-1) )   ){
    for (j in c((i+1):length(links$l2))){
      if (links$p1[i]==links$p1[j])
        if (links$p2[i]==links$p2[j])
          if (abs(links$l2[i]-links$l2[j])<1e-8){
            rem<-c(rem,j)
          }
    }
  }
  links<-links[-rem,]
  #df<-distinct(df)
  return(links)
}

Delaunay_triangulation<-function(p){
links<-data.frame("p1"=c(0), "p2"=c(0), "p1x"=c(0), "p1y"=c(0), "p2x"=c(0), "p2y"=c(0), 
                  "p3"=c(0), "p3x"=c(0), "p3y"=c(0) )#, "cx"=c(0), "cy"=c(0), "r"=c(0))
links<-links[-1,]
# we will compute the circle using the form
# x^2+y^2+Ax+By+C=0
# (x1  y1 1) (A) = -(x1^2+y1^2)
# (x2  y2 1) (B) = -(x2^2+y2^2)
# (x3  y3 1) (C) = -(x3^2+y3^2)
# then we relare to 
# (x-cx)^2+(y-cy)^2=r^2
M<-array(dim = c( 3,3 ))
M[1,3]<-1
M[2,3]<-1
M[3,3]<-1
b<-c(1,1,1)
point_list<-seq_along(p[,1])
point_list<- c(1: (length(p[,1])*9) )
wp<- p

pp<-p
pp[,1]<-pp[,1]-1
pp[,2]<-pp[,2]-1
wp<- rbind(wp,pp )
pp<-p
pp[,2]<-pp[,2]-1
wp<- rbind(wp,pp )
pp<-p
pp[,1]<-pp[,1]-1
wp<- rbind(wp,pp )


pp<- p
pp[,2]<-pp[,2]+1
wp<- rbind(wp,pp )



pp<-p
pp[,1]<-pp[,1]+1
pp[,2]<-pp[,2]+1
wp<- rbind(wp,pp )
pp<-p
pp[,1]<-pp[,1]+1
wp<- rbind(wp,pp )
pp<-p
pp[,1]<-pp[,1]+1
pp[,2]<-pp[,2]-1
wp<- rbind(wp,pp )


pp<-p
pp[,1]<-pp[,1]-1
pp[,2]<-pp[,2]+1
wp<- rbind(wp,pp )


wpm<-p
pp<-p
pp[,1]<-pp[,1]-1
pp[,2]<-pp[,2]-1
wpm<- rbind(wpm,pp )
pp<-p
pp[,2]<-pp[,2]-1
wpm<- rbind(wpm,pp )
pp<-p
pp[,1]<-pp[,1]-1
wpm<- rbind(wpm,pp )

#we do not need to check the last one
#point_list<-point_list[-length(point_list)]
#for (i in point_list){
for (i in seq_along(p[,1])){
  for (j in seq_along(wpm[,1])[-i] ){
    for (k in seq_along(wp[,1])[c(-i,-j)] ){
        M[1,1]<-p[i,1]
      M[2,1]<-as.numeric(wp[j,1])
      M[3,1]<-as.numeric(wp[k,1])
      M[1,2]<-p[i,2]
      M[2,2]<-as.numeric(wp[j,2])
      M[3,2]<-as.numeric(wp[k,2])
      b[1]<- -(M[1,1]^2+M[1,2]^2)
      b[2]<- -(M[2,1]^2+M[2,2]^2)
      b[3]<- -(M[3,1]^2+M[3,2]^2)
      if (abs(det(M)) < 1e-6) next
      x<-solve(M,b)
      center<-c(-x[1]/2, -x[2]/2)
      r2<-center[1]^2+center[2]^2-x[3]
      # check if there are point inside the circle
      inside=FALSE
      for (l in seq_along(wp[,1])[c(-i,-j,-k)]){# check all point since the wrapped can be inside
        
       d2<-compute_distance2_wrapped( wp[l,], center )
       if(d2<r2) inside<-TRUE
       
        if(inside) break
      }
      if(!inside){# we found a cluster of point
        
        jj<-((j-1)%%length(p[,1]))+1
        kk<-((k-1)%%length(p[,1]))+1
        links[length(links[,1])+1, c(1,2)]<-c(i,jj  )
        links[length(links[,1]), 3]<-M[1,1]
        links[length(links[,1]), 4]<-M[1,2]
        links[length(links[,1]), 5]<-M[2,1]
        links[length(links[,1]), 6]<-M[2,2]
        links[length(links[,1]), 7]<-kk
        links[length(links[,1]), 8]<-M[3,1]
        links[length(links[,1]), 9]<-M[3,2]
        # links[length(links[,1]), c(7,8,9)]<- c(center[1],center[2], sqrt(r2))   
          # ij<-TRUE
        # }
        links[length(links[,1])+1, c(1,2)]<-c(i,kk)
        links[length(links[,1]), 3]<-M[1,1]
        links[length(links[,1]), 4]<-M[1,2]
        links[length(links[,1]), 5]<-M[3,1]
        links[length(links[,1]), 6]<-M[3,2]
        links[length(links[,1]), 7]<-jj
        links[length(links[,1]), 8]<-M[2,1]
        links[length(links[,1]), 9]<-M[2,2]
        ### links[length(links[,1]), c(7,8,9)]<- c(center[1],center[2], sqrt(r2))
        ## no need to link j with k it will be found later
        # if(j<=length(p[,1]) || k<=length(p[,1]) ){
        #   links[length(links[,1])+1, c(1,2)]<-c(jj,kk)
        #   links[length(links[,1]), 3]<-M[2,1]
        #   links[length(links[,1]), 4]<-M[2,2]
        #   links[length(links[,1]), 5]<-M[3,1]
        #   links[length(links[,1]), 6]<-M[3,2]
        #   ### links[length(links[,1]), c(7,8,9)]<- c(center[1],center[2], sqrt(r2))
        # }
      }
      
    }
  }
}

links<-sort_links(links,c(1,3,4),c(2,5,6))
links<-sort_links(links,c(1,3,4),c(7,8,9))
links<-sort_links(links,c(2,5,6),c(7,8,9))

links$l2<- (links$p1x-links$p2x)^2+(links$p1y-links$p2y)^2
links$l13<- (links$p1x-links$p3x)^2+(links$p1y-links$p3y)^2
links$l23<- (links$p2x-links$p3x)^2+(links$p2y-links$p3y)^2

return(links)
}

a faster version that rely on the fact that the noise on the point is less then the distance, thus adding noise to the point do not change the neighbor structure

#7 6 5 
#8 0 4 
#1 2 3
compute_sector<-function(ixp,iyp,N){
  if (ixp>=N ){
    if(iyp>=N)
      sector<- 5
    else if (iyp<0)
      sector<- 3
    else 
      sector<- 4  
  }
  else if (ixp<0){
    if(iyp>=N)
      sector<- 7
    else if (iyp<0)
      sector<- 1
    else 
      sector<- 8  
  }
  else  {
    if(iyp>=N)
      sector<- 6
    else if (iyp<0)
      sector<- 2
    else 
      sector<- 0  
  }
  
  return(sector)
}


nearest_nieghbour<-function(i,N){
  
  neig<-c(NA,NA,NA,NA,NA,NA,NA,NA)
  il<- as.integer(i-1)
  ix<- as.integer(il%% N)
  iy<- as.integer(il/ N)
  
  ixp<- ix+1
  iyp<- iy+1
  sector<-compute_sector(ixp,iyp,N)
  neig[1]<- (ixp %%N)+(iyp%%N)*N+ sector*N*N   +1
  
  ixp<- ix
  iyp<- iy+1
  sector<-compute_sector(ixp,iyp,N)
  neig[2]<- (ixp )+(iyp%%N)*N+ sector*N*N   +1
  
  ixp<- ix-1
  iyp<- iy+1
  sector<-compute_sector(ixp,iyp,N)
  neig[3]<- (ixp %%N)+(iyp%%N)*N+ sector*N*N   +1
  
  ixp<- ix+1
  iyp<- iy
  sector<-compute_sector(ixp,iyp,N)
  neig[4]<- (ixp %%N)+(iyp%%N)*N+ sector*N*N   +1
  
  # ixp<- ix
  # iyp<- iy
  # sector<-compute_sector(ixp,iyp,N)
  # neig[5]<- (ixp )+(iyp%%N)*N+ sector*N*N   +1
   
  ixp<- ix-1
  iyp<- iy
  sector<-compute_sector(ixp,iyp,N)
  neig[5]<- (ixp %%N)+(iyp%%N)*N+ sector*N*N   +1
  
  ixp<- ix+1
  iyp<- iy-1
  sector<-compute_sector(ixp,iyp,N)
  neig[6]<- (ixp %%N)+(iyp%%N)*N+ sector*N*N   +1
  
  ixp<- ix
  iyp<- iy-1
  sector<-compute_sector(ixp,iyp,N)
  neig[7]<- (ixp )+(iyp%%N)*N+ sector*N*N   +1
  
  ixp<- ix-1
  iyp<- iy-1
  sector<-compute_sector(ixp,iyp,N)
  neig[8]<- (ixp %%N)+(iyp%%N)*N+ sector*N*N   +1
  
  return(neig)
}


Delaunay_triangulation_fast<-function(p){
links<-data.frame("p1"=c(0), "p2"=c(0), "p1x"=c(0), "p1y"=c(0), "p2x"=c(0), "p2y"=c(0), 
                  "p3"=c(0), "p3x"=c(0), "p3y"=c(0) )#, "cx"=c(0), "cy"=c(0), "r"=c(0))
links<-links[-1,]
# we will compute the circle using the form
# x^2+y^2+Ax+By+C=0
# (x1  y1 1) (A) = -(x1^2+y1^2)
# (x2  y2 1) (B) = -(x2^2+y2^2)
# (x3  y3 1) (C) = -(x3^2+y3^2)
# then we relare to 
# (x-cx)^2+(y-cy)^2=r^2
M<-array(dim = c( 3,3 ))
M[1,3]<-1
M[2,3]<-1
M[3,3]<-1
b<-c(1,1,1)
point_list<-seq_along(p[,1])
point_list<- c(1: (length(p[,1])*9) )
wp<- p

pp<-p
pp[,1]<-pp[,1]-1
pp[,2]<-pp[,2]-1
wp<- rbind(wp,pp )
pp<-p
pp[,2]<-pp[,2]-1
wp<- rbind(wp,pp )
pp<-p
pp[,1]<-pp[,1]+1
pp[,2]<-pp[,2]-1
wp<- rbind(wp,pp )
pp<- p
pp[,2]<-pp[,2]+1
wp<- rbind(wp,pp )
pp<-p
pp[,1]<-pp[,1]+1
pp[,2]<-pp[,2]+1
wp<- rbind(wp,pp )
pp<-p
pp[,1]<-pp[,1]+1
wp<- rbind(wp,pp )
pp<-p
pp[,1]<-pp[,1]-1
pp[,2]<-pp[,2]+1
wp<- rbind(wp,pp )
pp<-p
pp[,1]<-pp[,1]-1
wp<- rbind(wp,pp )

#7 6 5 
#8 0 4 
#1 2 3


N<-as.integer(sqrt(length(p[,1])))
for (i in seq_along(p[,1])){
  
  neig<-nearest_nieghbour(i,N)
  for (j in neig ){
    neig1<-neig[which(neig!=j)]
    for (k in neig1 ){
      M[1,1]<-p[i,1]
      M[2,1]<-as.numeric(wp[j,1])
      M[3,1]<-as.numeric(wp[k,1])
      M[1,2]<-p[i,2]
      M[2,2]<-as.numeric(wp[j,2])
      M[3,2]<-as.numeric(wp[k,2])
      b[1]<- -(M[1,1]^2+M[1,2]^2)
      b[2]<- -(M[2,1]^2+M[2,2]^2)
      b[3]<- -(M[3,1]^2+M[3,2]^2)
      if (abs(det(M)) < 1e-12) next
      x<-solve(M,b)
      center<-c(-x[1]/2, -x[2]/2)
      r2<-center[1]^2+center[2]^2-x[3]
      
      inside=FALSE
      for (l in seq_along(wp[,1])[c(-i,-j,-k)]){
        
      d2<-compute_distance2_wrapped( wp[l,], center )
       if(d2<r2) inside<-TRUE
        
        
        if(inside) break
      }
      
      if(!inside){
        jj<-((j-1)%%length(p[,1]))+1
        kk<-((k-1)%%length(p[,1]))+1
        links[length(links[,1])+1, c(1,2)]<-c(i,jj  )
        links[length(links[,1]), 3]<-M[1,1]
        links[length(links[,1]), 4]<-M[1,2]
        links[length(links[,1]), 5]<-M[2,1]
        links[length(links[,1]), 6]<-M[2,2]
        links[length(links[,1]), 7]<-kk
        links[length(links[,1]), 8]<-M[3,1]
        links[length(links[,1]), 9]<-M[3,2]
       
        links[length(links[,1])+1, c(1,2)]<-c(i,kk)
        links[length(links[,1]), 3]<-M[1,1]
        links[length(links[,1]), 4]<-M[1,2]
        links[length(links[,1]), 5]<-M[3,1]
        links[length(links[,1]), 6]<-M[3,2]
        links[length(links[,1]), 7]<-jj
        links[length(links[,1]), 8]<-M[2,1]
        links[length(links[,1]), 9]<-M[2,2]
       
      }
      
    }
  }
}

links<-sort_links(links,c(1,3,4),c(2,5,6))
links<-sort_links(links,c(1,3,4),c(7,8,9))
links<-sort_links(links,c(2,5,6),c(7,8,9))

links$l2<- (links$p1x-links$p2x)^2+(links$p1y-links$p2y)^2
links$l13<- (links$p1x-links$p3x)^2+(links$p1y-links$p3y)^2
links$l23<- (links$p2x-links$p3x)^2+(links$p2y-links$p3y)^2

return(links)
}
library(ggplot2)
library(plotly)
library(ggforce)

plot_Delaunay_triangulation<-function(p,links, numbers=FALSE, zoomin=TRUE){
  gg<-ggplot()+theme_bw()
  N<-dim(p)[1]
  sqN<-sqrt(N)
  margin<- 3.0/(2.0*sqN)
  distance<- 1.0/sqN
  links_in<- links
  links_out<- links
  for (i in seq_along(links[,1])){
    links_in[i,1]<-links[i,3]
    links_in[i,2]<-links[i,4]
    links_out[i,1]<-links[i,5]
    links_out[i,2]<-links[i,6]
    
  }
  
  gg<- gg+ geom_point(aes(x=p[,1], y=p[,2]), color="red")
  if(numbers)
  gg<- gg + geom_text( aes(x=p[,1], y=p[,2], label= as.character(c(1:N)) ), 
          size = distance/4.0, vjust = distance/3.0, hjust = distance/3.0)
  gg<- gg+ geom_point(aes(x=p[,1]+1, y=p[,2]), color="violet")
  gg<- gg+ geom_point(aes(x=p[,1], y=p[,2]+1), color="violet")
  gg<- gg+ geom_point(aes(x=p[,1]+1, y=p[,2]+1), color="violet")
  gg<- gg+ geom_point(aes(x=p[,1], y=p[,2]-1), color="violet")
  gg<- gg+ geom_point(aes(x=p[,1]+1, y=p[,2]-1), color="violet")
  gg<- gg+ geom_point(aes(x=p[,1]-1, y=p[,2]-1), color="violet")
  gg<- gg+ geom_point(aes(x=p[,1]-1, y=p[,2]), color="violet")
  gg<- gg+ geom_point(aes(x=p[,1]-1, y=p[,2]+1), color="violet")
  gg <- gg+ geom_vline(xintercept = 1, color="green")
  gg <- gg+ geom_hline(yintercept = 1, color="green")
  gg <- gg+ geom_vline(xintercept = 0, color="green")
  gg <- gg+ geom_hline(yintercept = 0, color="green")
  gg<-gg+ geom_segment(aes(x = links$p1x, y = links$p1y,
                        xend = links$p2x, yend = links$p2y) ,color="black",
                        )
  gg<-gg+ geom_segment(aes(x = links$p1x, y = links$p1y,
                        xend = links$p3x, yend = links$p3y) ,color="black",
                        )
  gg<-gg+ geom_segment(aes(x = links$p2x, y = links$p2y,
                        xend = links$p3x, yend = links$p3y) ,color="black",
                        )
  
   
 
  gg<-gg + scale_fill_manual(values = rep(2,3*N))+ theme(legend.position="none")
  
  
  if (zoomin)
    gg<-gg +xlim(0-margin,1+margin)+ylim(0-margin,1+margin)
  else
    gg<-gg +xlim(-1,2)+ylim(-1,2)
 
  
  fig<-ggplotly(gg,dynamicTicks = TRUE) %>% style(textposition = "right")
  
 return(fig)
}
p<-noise_on_the_regular_lattice_2d(4,0.15)
#p<-create_regular_lattice_2d(3)
#p<-create_random_lattice_2d(6,tot = TRUE)
links<-Delaunay_triangulation(p)
# links1<-remove_doublers(links)
plot_Delaunay_triangulation(p,links)
# library(interp)
# dd<-tri.mesh(p[,1], p[,2], duplicate = "error")
# print(dd)
# plot(dd)

In the second implementation some doublers are removed

links<-Delaunay_triangulation_fast(p)
# links1<-remove_doublers(links)
plot_Delaunay_triangulation(p,links)

To remove all the doublers

find_triangles<-function(links){
  rem<-c()
  for (i in c(1:(length(links[,1])-1)  ) ){
    for (j in c((i+1):(length(links[,1])  ) ) ){
        if (links$p1[i]!=links$p1[j]) next
        else if (links$p2[i]!=links$p2[j]) next
        else if (links$p3[i]!=links$p3[j]) next
        else if (abs(links$l2[i]-links$l2[j])>1e-8) next
        else if (abs(links$l13[i]-links$l13[j])>1e-8) next
        else if (abs(links$l23[i]-links$l23[j])>1e-8) next
        else rem<-c(rem,j)
       
    }
  }
  order_col<-c(1,2,7,3,4,5,6,8,9)
  if (!is.null(rem))
   return(links[-rem, order_col])
  else 
   return(links[,order_col])
    
}
triangles<-find_triangles(links)
plot_Delaunay_triangulation(p,triangles)

FEM

We want to write a discrete version of the equation

\nabla^2 u=f

To do so we project both sides to a set of function h_i

\int (\nabla^2 u) h_i dV=\int f h_i dV \tag{1}

since our lattice is triangulated we can define write our integral as a sum over the integral on the triangles \Delta_i

\int f dV=\sum_{i\in \Delta}\int_{\Delta_i} f dV we can write f=\sum f_i h_i , if h_i where a basis then it will be exact but in our case it is an approximation. The h_i are chosen to be 1 in the point i and zero in the all the other point. They go linear from 0 to 1 thus h_i=ax+by+c more specific given a trinagle with vertices i,j,k the function h_i satify \begin{align} h_i(i)&=ax_i+by_i+c=1\\ h_i(j)&=ax_j+by_j+c=0\\ h_i(k)&=ax_k+by_k+c=0\,. \end{align} To approximate the right had side of (Equation 1) we tried:

  • writing f=\sum_i f_i h_i \begin{equation} \int f h_i dV=\sum_{ \Delta_i } \sum_{j=0}^N f_j\int_{\Delta_i} h_ih_jdV =M_{ij}f_j \end{equation} where \sum_{\Delta_i} is the summ of all triangles having i as vertex and the integral can be approximate with the midpoint rule for triangles \begin{equation} \int_{\Delta_i} h_ih_jdV =\frac{Area}{3}\left[h_ih_j\big|_{(\frac{\bar x_i+\bar x_j}{2})}+h_ih_j\big|_{(\frac{\bar x_i+\bar x_k}{2})}+h_ih_j\big|_{(\frac{\bar x_j+\bar x_k}{2})} \right] \end{equation}

    Important

    This works with regular Delanuy but not in the generic case

  • approximate f\approx f_i inside the triangle, then the integral can be done exactly and is equal to the volume of the pyramid \begin{equation} \int f h_i dV = f_i \frac{1}{3}\sum_{ \Delta_i } Area_{ \Delta_i } \end{equation} in this case M is diagonal.

    Important

    This works with regular Delanuy but still does not in the generic case, to make it work with a generic triangularization we found that we need to replace M with its average value M=\bar{M}1\!\!1

To approximate the left hand side of (Equation 1) we write u=\sum_j u_jh_j integrate by part \begin{align} \int (\nabla^2 u) h_i dV&=\sum_{i \in \Delta} \sum_{j=0}^N u_j\int_{\Delta_i}(\nabla^2h_j)h_idV \nonumber\\ &=\sum_{i \in \Delta} \sum_{j=0}^N u_j\int_{\Delta_i}-(\nabla h_j)(\nabla h_i)dV+ \sum_{i \in \Delta} \sum_{j=0}^N u_j\int_{\Delta_i}\nabla[(\nabla h_j) h_i]dV \nonumber\\ &=\sum_{i \in \Delta} \sum_{j=0}^N u_j\int_{\Delta_i}-(\nabla h_j)(\nabla h_i)dV+ \sum_{i \in \Delta} \sum_{j=0}^N u_j\int_{S_{\Delta_i}}[(\nabla h_j) h_i]\cdot n \,dS \nonumber\\ &=\sum_{i \in \Delta} \sum_{j=0}^N u_j\int_{\Delta_i}-(\nabla h_j)(\nabla h_i)dV =L_{ij}u_j \end{align} from the second to the third line we use the divergence theorem to tranform the integral over the volume as the flux on the surface, since our lattice is triangulated the flux on the surface cancel between the neighbor triangles.

Thus our discretized version of the Laplace equation become L_{ij}u_j=M_{ij}f_j and we can write a discretized version of the Laplatian as \nabla^2\to M^{-1}L

library(ggplot2)
library(plotly)
library(matlib)
library(Matrix)
compute_hi<-function(p,M,i){
  b<-c(0,0,0)
  b[i]<- 1
  a<-solve(M,b)
  A<- det(M)/2
  return(a*sqrt(abs(A)) )
}

nablah_dot_nablah<- function(h1,h2){
  return(h1[1]*h2[1]+h1[2]*h2[2] )
}


func_h<-function(h, p ){
  return(h[1]*p[1]+h[2]*p[2]+h[3])
}
midpoint_triangle<-function(hi, hj, v1,v2,v3){
 
  return(
    func_h(hi, (v1+v2)/2 )*func_h(hj, (v1+v2)/2 )+
    func_h(hi, (v1+v3)/2 )*func_h(hj, (v1+v3)/2 )+
    func_h(hi, (v2+v3)/2 )*func_h(hj, (v2+v3)/2 )  
  )
}
## type is an integer that defines how the rhs of laplace equation is computed
## 0  trapezoidal rule
## 1  trapezoidal rule + average of the diagonal of M
## 2  gauss quadrature 3 point 
compute_laplacian<- function(p,triangles, type=1){
  
  #print(triangles)
  L<-array(rep(0,length(p[,1])^2), dim = c( length(p[,1]),length(p[,1]) ))#Matrix(nrow = length(p[,1]), ncol = length(p[,1]), data = 0, sparse = TRUE)#
  M<-array(rep(0,length(p[,1])^2), dim = c( length(p[,1]),length(p[,1]) ))#Matrix(nrow = length(p[,1]), ncol = length(p[,1]), data = 0, sparse = TRUE)
  # 
  for (i in seq_along(triangles[,1]) ){
    v1<- c(triangles[i,4], triangles[i,5])
    v2<- c(triangles[i,6], triangles[i,7])
    v3<- c(triangles[i,8], triangles[i,9])
    
    Tri<-t(array( c( v1[1],v1[2],1,
                 v2[1],v2[2],1,
                 v3[1],v3[2],1),
              dim = c( 3,3 ))
      )

    Area <- abs(det(Tri))/2
    
    h<-array( dim = c( 3,3 ))
    # cat("triangle out: ", triangles[i,][[1]], triangles[i,][[2]], triangles[i,][[3]],"\n" )
    h[1,] <- solve(Tri, c(1,0,0))# compute_hi(p, Tri,1)
    h[2,] <- solve(Tri, c(0,1,0))#compute_hi(p, Tri,2)
    h[3,] <- solve(Tri, c(0,0,1))#compute_hi(p, Tri,3)

    for (j in c(1,2,3)){
      if (type==2){
        for (k in c(1,2,3)){
          M[triangles[i,j],triangles[i,k] ]<- M[triangles[i,j],triangles[i,k]] +
           (Area/3)*midpoint_triangle(h[j,], h[k,], v1, v2, v3 )
         }
      }
      else{
        M[triangles[i,j],triangles[i,j] ] <- M[triangles[i,j],triangles[i,j] ] + (Area/3)
      }
    }
    for (j in c(1,2,3)){
      for (k in c(1,2,3)){
         L[triangles[i,j], triangles[i,k] ] <- L[triangles[i,j], triangles[i,k] ]-
                                              nablah_dot_nablah(h[j,], h[k,])*Area
         
      }
    }
  }
  if(type==2 || type ==0 ){
    L  <- -inv(M) %*% L #we add the -1 since we want (-i\nabla)^2 
  }
  else if (type==1){
    L<- - (1/mean(diag(M)))*L
  }
  
  return(L)
}
 #sqN<- 5
 #r<- 1e-6
 #p <- noise_on_the_regular_lattice_2d(sqN,r/sqN)
 # f <- sin(2*pi*p[,1])+cos(2*pi*p[,2])
 # links  <- Delaunay_triangulation_fast(p)
 # links1 <- find_triangles(links)
 # L2 <- compute_laplacian(p,links1)
 # plot_Delaunay_triangulation(p,links1)

Test application on a function

Consider the function f(x,y)=\sin (2\pi x)+\cos (2\pi y) with the lapacian \nabla^2f(x,y)=-(2\pi)^2[\sin (2\pi x)+\cos (2\pi y)]=-(2\pi)^2f(x,y)

We define the deviation between exact derivative and the discrete as (we want to implemente (-i\nabla)^2=-\nabla^2: d=\frac{1}{N}\sum_i^N |\nabla^2_{latt}f-\nabla^2_{cont}f | On a regular lattice of size 5x5 we have |\nabla^2_{latt}f-\nabla^2_{cont}f|\approx\epsilon\approx\frac{1}{\sqrt{N}} thus d\sim \frac{1}{\sqrt{N}}

sqN<-5
p     <-create_regular_lattice_2d(sqN)
f<- sin(2*pi*p[,1])+cos(2*pi*p[,2])
L2<-matrix(rep(0,sqN^4), nrow=sqN^2,ncol=sqN^2)
L2r<-matrix(rep(0,sqN^4), nrow=sqN^2,ncol=sqN^2)
for(mu in c(1,2)){
      L<-compute_deriv(p,mu)
      L2<-L2+ Conj(t(L)) %*% L
      # Lr<- (L + Conj(t(L)) )/2
      # L2r<-L2r+ Lr %*% Lr
}
mean(abs(L2%*%f-f*(2*pi)^2))
[1] 4.028538

if we introduce a noise on the position of the points (the result at 1e-5 is unstable, it depends on the random number used)

sqN<-5
for (r in c(1e-4,1e-5,1e-6,1e-7)){
  p     <- noise_on_the_regular_lattice_2d(sqN,r)
  f <- sin(2*pi*p[,1])+cos(2*pi*p[,2])
  L2  <- matrix(rep(0,sqN^4), nrow=sqN^2,ncol=sqN^2)
  L2r <- matrix(rep(0,sqN^4), nrow=sqN^2,ncol=sqN^2)
  for(mu in c(1,2)){
        L<-compute_deriv(p,mu)
        L2<-L2+ Conj(t(L)) %*% L
  
  }
  cat("(r=",r,"): ",mean(abs(L2%*%f-f*(2*pi)^2)),"\n")
}
(r= 1e-04 ):  121993569 
(r= 1e-05 ):  2580665484 
(r= 1e-06 ):  4.028531 
(r= 1e-07 ):  4.028542 

with the FEM we compute the result with all the thee way we listed befor to compute the matrix M

sqN <- 5
list_r<-c(1e-2, 1e-3, 1e-4,1e-5, 1e-6, 1e-7)
df_FEM<- data.frame("r"=list_r, "Gauss3"=list_r, "diag"=list_r, "sym_diag"=list_r)
for (ir in seq_along(list_r)){
  r<-list_r[ir]
  p <- noise_on_the_regular_lattice_2d(sqN,r)
  f <- sin(2*pi*p[,1])+cos(2*pi*p[,2])
  links  <- Delaunay_triangulation_fast(p)
  links1 <- find_triangles(links)
  L2 <- compute_laplacian(p,links1,type=2)
  df_FEM[ir,"Gauss3"]<-mean(abs(L2%*%f-f*(2*pi)^2))
  L2 <- compute_laplacian(p,links1,type=0)
  df_FEM[ir,"diag"]<-mean(abs(L2%*%f-f*(2*pi)^2))
  L2 <- compute_laplacian(p,links1,type=1)
  df_FEM[ir,"sym_diag"]<-mean(abs(L2%*%f-f*(2*pi)^2))
}
df_FEM
      r   Gauss3     diag sym_diag
1 1e-02 14.63220 4.921786 3.898665
2 1e-03 15.20153 5.754429 4.023562
3 1e-04 20.46269 5.095098 4.026911
4 1e-05 22.01970 5.604196 4.028764
5 1e-06 12.77063 4.647242 4.028545
6 1e-07 15.47967 4.582857 4.028537

we now keep fix the noise on the point r to 1e-6 and we plot explicitly

sqN <- 7
N<- sqN*sqN
r<-1e-5
df_FEM_v<- data.frame("id"=c(1:N), "exact"=c(1:N), "Gauss3"=c(1:N),
                      "diag"=c(1:N), "sym_diag"=c(1:N))
df_FEM<- data.frame("r"=c(r), "Gauss3"=c(0), "diag"=c(0), "sym_diag"=c(0))
#p <- noise_on_the_regular_lattice_2d(sqN,r)
#save(p, file="./reference_lattice.RData")
load("./reference_lattice.RData")
f <- sin(2*pi*p[,1])+cos(2*pi*p[,2])
df_FEM_v["exact"]<- (f*(2*pi)^2)
links  <- Delaunay_triangulation_fast(p)
links1 <- find_triangles(links)
L2 <- compute_laplacian(p,links1,type=2)
df_FEM[1,"Gauss3"]<-mean(abs(L2%*%f-f*(2*pi)^2))
df_FEM_v["Gauss3"]<-(L2%*%f)
L2 <- compute_laplacian(p,links1,type=0)
df_FEM_v["diag"]<-(L2%*%f)
df_FEM[1,"diag"]<-mean(abs(L2%*%f-f*(2*pi)^2))
L2 <- compute_laplacian(p,links1,type=1)
df_FEM_v["sym_diag"]<-(L2%*%f)
df_FEM[1,"sym_diag"]<-mean(abs(L2%*%f-f*(2*pi)^2))
# plot
gg<-ggplot(df_FEM_v)+theme_bw()+ylab("|FEM-exact|")
# gg<- gg+ geom_line(aes(x=id,y=exact,color="   exact"))
gg<- gg+ geom_line(aes(x=id,y=abs(Gauss3-exact),color="  Gauss3"))
gg<- gg+ geom_line(aes(x=id,y=abs(diag-exact),color="  diag"))
gg<- gg+ geom_line(aes(x=id,y=abs(sym_diag-exact),color=" sym_diag"))
gg<- gg+geom_hline(yintercept = 0)
plot_Delaunay_triangulation(p,links1, TRUE)
df_FEM
      r   Gauss3     diag sym_diag
1 1e-05 13.59592 4.307231 2.100179
ggplotly(gg, dynamicTicks = TRUE)

continuum limit

df<-data.frame("type"=c(""), "sqN"=c(0), "r"=c(0), "diff"=c(0)  )
df<-df[-1,]
dfbad<-df


sqN<-5
for (sqN in c(4,5,6,7,8,9,10,11,12,14,18,20)){
#for (sqN in c(5)){  
  r<-0
  p  <- create_regular_lattice_2d(sqN)
  f<- sin(2*pi*p[,1])+cos(2*pi*p[,2])
  L2<-matrix(rep(0,sqN^4), nrow=sqN^2,ncol=sqN^2)
  L2r<-matrix(rep(0,sqN^4), nrow=sqN^2,ncol=sqN^2)
  for(mu in c(1,2)){
        L<-compute_deriv(p,mu)
        L2<-L2+ Conj(t(L)) %*% L
        Lr<- (L + Conj(t(L)) )/2
        L2r<-L2r+ Lr %*% Lr
  }
  myl<-abs(L2%*%f-f*(2*pi)^2)
  diff<-sum(abs(L2%*%f-f*(2*pi)^2))/sqN^2
  # cat("(N=",N,", r=",r,"): ",diff,"\n")
  df[nrow(df)+1,]<-list("neigh", sqN, r, diff)
}

for(r1 in c(1e-4, 1e-5, 1e-6)){
#for(r1 in c( 1e-4)){
for (sqN in c(4,5,6,7,8,9,10,11,12)){
  r<-r1/sqN
  p  <- noise_on_the_regular_lattice_2d(sqN,r)
  f<- sin(2*pi*p[,1])+cos(2*pi*p[,2])
  L2<-matrix(rep(0,sqN^4), nrow=sqN^2,ncol=sqN^2)
  L2r<-matrix(rep(0,sqN^4), nrow=sqN^2,ncol=sqN^2)
  for(mu in c(1,2)){
        L<-compute_deriv(p,mu)
        L2<-L2+ Conj(t(L)) %*% L
        Lr<- (L + Conj(t(L)) )/2
        L2r<-L2r+ Lr %*% Lr
  }
  diff<-sum(abs(L2%*%f-f*(2*pi)^2))/sqN^2
  # cat("(N=",N,", r=",r,"): ",diff,"\n")
  if (r1<0.9e-4) df[nrow(df)+1,]<-list("neigh", sqN, r1, diff)
  else   dfbad[nrow(dfbad)+1,]<-list("neigh", sqN, r1, diff)
  
}
}

r1<-1e-5
for (sqN in c(4,5,6,7,8,9, 10)){
#for (sqN in c(5)){
for(r1 in c(1e-1, 1e-2, 1e-3)){
#for(r1 in c( 1e-6)){  
# for (sqN in c(10,12,14)){
#for (sqN in c(16,18,20)){
  r<-r1/sqN
  p     <- noise_on_the_regular_lattice_2d(sqN, r)
  f<- sin(2*pi*p[,1])+cos(2*pi*p[,2])
  links <- Delaunay_triangulation_fast(p)
  links1<-find_triangles(links)
  # plot_Delaunay_triangulation(p,links1)  
 
  L2<-compute_laplacian(p, links1, type=1)
  diff<-sum(abs(L2%*%f-f*(2*pi)^2))/sqN^2
  df[nrow(df)+1,]<-list("FEM-Sdiag", sqN, r1, diff)
  if (r1==1e-3){
    L2<-compute_laplacian(p, links1, type=2)
    diff<-sum(abs(L2%*%f-f*(2*pi)^2))/sqN^2
    df[nrow(df)+1,]<-list("FEM-Gauss3", sqN, r1, diff)
    L2<-compute_laplacian(p, links1, type=0)
    diff<-sum(abs(L2%*%f-f*(2*pi)^2))/sqN^2
    df[nrow(df)+1,]<-list("FEM-diag", sqN, r1, diff)
  }
}
}
gg<-ggplot(df)+theme_bw()
gg<-gg+geom_point(aes(x=1/sqN, y=diff, color=paste(type,r), shape=paste(type,r) ))
gg<- gg+xlim(0,NA)+ylim(0,NA)
ggplotly(gg)
gg<-ggplot(dfbad)+theme_bw()
gg<-gg+geom_point(aes(x=1/sqN, y=diff, color=paste(type,r), shape=paste(type,r) ))
gg<- gg+xlim(0,NA)+ylim(0,NA)
ggplotly(gg)

Delaunay on regular lattice

On a regular lattice there is an ambiguity in defining the Delaunay triangularization since we have 4 point on the circumference. We solve the ambiguity definin it in this way

distance_wrapped<- function(p1,p2){
  d<- p2-p1
  d1<- 1-abs(d)
  l<-which( d1<abs(d))
  return(d)
}
is_wrapped<- function(ix,p2){
  d<- p2-p1
  d1<- 1-abs(d)
  l<-which( d1<abs(d))
  return(d)
}

Delaunay_triangulation_regular<-function(p){
  N<-length(p[,1])
  sqN<-as.integer(sqrt(N))
  Nt<-N*2
  triangles<-data.frame("p1"=rep(0,Nt), "p2"=rep(0,Nt), "p3"=rep(0,Nt),
                        "p1x"=rep(0,Nt), "p1y"=rep(0,Nt),
                        "p2x"=rep(0,Nt), "p2y"=rep(0,Nt),
                        "p3x"=rep(0,Nt), "p3y"=rep(0,Nt) )
  for (i in seq_along(p[,1])){
    triangles$p1[i*2-1]<-i
    
    il<-i-1
    ix<-as.integer(il%%sqN)
    iy<-as.integer(il/sqN)
    d<-c(0,0)
    ip2<- ((ix+1)%%sqN)+(iy)*sqN+1
    triangles$p2[i*2-1]<-ip2
    ip3<- (ix)+((iy+1)%%sqN)*sqN+1
    triangles$p3[i*2-1]<-ip3
    if ((ix+1)==sqN) d[1]<-1
    if ((iy+1)==sqN) d[2]<-1
    triangles$p1x[i*2-1]<- p[i,1]
    triangles$p1y[i*2-1]<- p[i,2]
    triangles$p2x[i*2-1]<- p[ip2,1]+d[1]
    triangles$p2y[i*2-1]<- p[ip2,2]
    triangles$p3x[i*2-1]<- p[ip3,1]
    triangles$p3y[i*2-1]<- p[ip3,2]+d[2]
    
    triangles$p1[i*2]<-i
    ip2<- ((ix-1)%%sqN)+(iy)*sqN+1
    triangles$p2[i*2]<-ip2
    ip3<- (ix)+((iy-1)%%sqN)*sqN+1
    triangles$p3[i*2]<-ip3
    d<-c(0,0)
    if ((ix-1)< 0) d[1]<-1
    if ((iy-1)< 0) d[2]<-1
    triangles$p1x[i*2]<- p[i,1]
    triangles$p1y[i*2]<- p[i,2]
    triangles$p2x[i*2]<- p[ip2,1]-d[1]
    triangles$p2y[i*2]<- p[ip2,2]
    triangles$p3x[i*2]<- p[ip3,1]
    triangles$p3y[i*2]<- p[ip3,2]-d[2]
    
  }
  return(triangles)
}
N<-2
p<-create_regular_lattice_2d(N)
p<-noise_on_the_regular_lattice_2d(N,0.2)
# f <- sin(2*pi*p[,1])+cos(2*pi*p[,2])
triangles  <- Delaunay_triangulation_regular(p)

plot_Delaunay_triangulation(p,triangles)

with this triangularization the approach on the continuum seems faster

continuum limit

dfr<-data.frame("type"=c(""), "sqN"=c(0), "r"=c(0), "diff"=c(0)  )
dfr<-dfr[-1,]
dfrbad<-dfr

for (sqN in c(4,6,8,10,12,14,16,18)){
  r<-0
  p  <- create_regular_lattice_2d(sqN)
  f<- sin(2*pi*p[,1])+cos(2*pi*p[,2])
  L2<-matrix(rep(0,sqN^4), nrow=sqN^2,ncol=sqN^2)
  L2r<-matrix(rep(0,sqN^4), nrow=sqN^2,ncol=sqN^2)
  for(mu in c(1,2)){
        L<-compute_deriv(p,mu)
        L2<-L2+ Conj(t(L)) %*% L
        Lr<- (L + Conj(t(L)) )/2
        L2r<-L2r+ Lr %*% Lr
  }
  diff<-sum(abs(L2%*%f-f*(2*pi)^2))/sqN^2
  # cat("(N=",N,", r=",r,"): ",diff,"\n")
  dfr[nrow(dfr)+1,]<-list("neigh", sqN, r, diff)
}


r1<-1e-3
for (r1 in c(5e-2, 1e-2,1e-3,1e-4))
for (sqN in c(4,5,6,7,8,9, 10,11, 12 , 13, 14)){
# for (sqN in c(10,12,14)){
#for (sqN in c(16,18,20)){
  r<-r1/sqN
  # p     <- create_regular_lattice_2d(sqN)
  p     <- noise_on_the_regular_lattice_2d(sqN,r)
  f<- sin(2*pi*p[,1])+cos(2*pi*p[,2])
  triangles <- Delaunay_triangulation_regular(p)
  
  L2<-compute_laplacian(p,triangles)
  diff<-sum(abs(L2%*%f-f*(2*pi)^2))/sqN^2
  dfr[nrow(dfr)+1,]<-list("FEM-Sdiag", sqN, r1, diff)
  if (r1==1e-3){
     L2<-compute_laplacian(p, triangles, type=2)
     diff<-sum(abs(L2%*%f-f*(2*pi)^2))/sqN^2
     dfr[nrow(dfr)+1,]<-list("FEM-Gauss3", sqN, r1, diff)
     L2<-compute_laplacian(p, triangles, type=0)
     diff<-sum(abs(L2%*%f-f*(2*pi)^2))/sqN^2
     dfr[nrow(dfr)+1,]<-list("FEM-diag", sqN, r1, diff)
  }
}
dfr$lab<-paste(dfr$type,dfr$r)
dfr$lab <- factor(dfr$lab, levels = rev(unique(dfr$lab)), ordered=TRUE)
gg<-ggplot(dfr)+theme_bw()
gg<-gg+geom_point(aes(x=1/sqN, y=diff, color=lab, shape=lab ))
gg<- gg+xlim(0,NA)+ylim(0,NA)
ggplotly(gg)

Spectrum

Ns<-c(8,10,12)
dfs<-data.frame("id"=c(0), "l"=c(0), "N"=c(0), "type"=c(0), "r"=c(0))
dfs<-dfs[-1,]
r<-1e-6
for (N in Ns){

  p     <- noise_on_the_regular_lattice_2d(N,r/N)
  N<-N*N
  links <- Delaunay_triangulation_fast(p)
  links1<-find_triangles(links)
  L2<-compute_laplacian(p,links1)
 
  l <- eigen(L2)$values[(N):1] # divide by N to remove the lattice spacing
  dfs<-rbind(dfs,data.frame("id"=c(1:N)-1, "l"=l, "N"=rep(N,N),
                          "type"=rep("L2",N) , "r"=rep(r,N)) )
}
gg<-ggplot()+theme_bw()
gg<- gg+ geom_point(data=dfs,aes(x=id, y=Re(l), shape=as.factor(N), color=as.factor(N) ) )+ylim(0,(4*pi)^2+(2*pi)^2+100)+xlim(0,1+4+4+4+8)

gg<- gg +geom_line(aes(x=c(1:4), y=(2*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(5:8), y=2*(2*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(9:12), y=(4*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(13:20), y=(4*pi)^2+(2*pi)^2, color="continuum", shape="continuum"  ))
ggplotly(gg, dynamicTicks = TRUE) %>% layout(yaxis = list(autorange=FALSE),
                                             xaxis = list(autorange=FALSE))

Regular Delaunay

Ns<-c(8,10,12,14)
dfrs<-data.frame("id"=c(0), "l"=c(0), "N"=c(0), "type"=c(0), "r"=c(0))
dfrs<-dfrs[-1,]
r<-1e-6
for (N in Ns){

  p     <- noise_on_the_regular_lattice_2d(N,r/N)
  N<-N*N
  links <- Delaunay_triangulation_regular(p)

  L2<-compute_laplacian(p,links)

  l <- eigen(L2)$values[(N):1] # divide by N to remove the lattice spacing
  dfrs<-rbind(dfrs,data.frame("id"=c(1:N)-1, "l"=l, "N"=rep(N,N),
                          "type"=rep("L2",N) , "r"=rep(r,N)) )
}
gg<-ggplot()+theme_bw()
gg<- gg+ geom_point(data=dfrs, aes(x=id, y=Re(l), shape=as.factor(N), color=as.factor(N) ) )+ylim(0,(4*pi)^2+(2*pi)^2+200)+xlim(0,1+4+4+4+8)

gg<- gg +geom_line(aes(x=c(1:4), y=(2*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(5:8), y=2*(2*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(9:12), y=(4*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(13:20), y=(4*pi)^2+(2*pi)^2, color="continuum", shape="continuum"  ))
ggplotly(gg, dynamicTicks = TRUE) %>% layout(yaxis = list(autorange=FALSE),
                                             xaxis = list(autorange=FALSE))

Regular Delaunay vs noise

dfrsn<-data.frame("id"=c(0), "l"=c(0), "N"=c(0), "type"=c(0), "r"=c(0))
dfrsn<-dfrsn[-1,]
gg<-list()
# for (r in c(0, 1e-4, 1e-3, 1e-2, 1e-1, 1)){
for (r in c(5e-2,1e-2,1e-3)){  
  N<-10
  p     <- noise_on_the_regular_lattice_2d(N,r)
  N<-N*N
  links <- Delaunay_triangulation_regular(p)

  L2<-compute_laplacian(p,links)

  l <- eigen(L2)$values[(N):1] # divide by N to remove the lattice spacing
  dfrsn<-rbind(dfrsn,data.frame("id"=c(1:N)-1, "l"=l, "N"=rep(N,N),
                          "type"=rep("L2",N) , "r"=rep(r,N)) )
  gg<-append(gg, plot_Delaunay_triangulation(p,links))
}
#plot_Delaunay_triangulation(p,links)
gg<-ggplot()+theme_bw()
gg<- gg+ geom_point(data=dfrsn, aes(x=id, y=Re(l), shape=as.factor(r), color=as.factor(r) ) )+ylim(0,(4*pi)^2+(2*pi)^2+200)+xlim(0,1+4+4+4+8)

gg<- gg +geom_line(aes(x=c(1:4), y=(2*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(5:8), y=2*(2*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(9:12), y=(4*pi)^2, color="continuum", shape="continuum"  ))
gg<- gg +geom_line(aes(x=c(13:20), y=(4*pi)^2+(2*pi)^2, color="continuum", shape="continuum"  ))
ggplotly(gg, dynamicTicks = TRUE) %>% layout(yaxis = list(autorange=FALSE),
                                             xaxis = list(autorange=FALSE))