*! -spkde-: Kernel estimation for two-dimensional spatial point patterns
*! Version 1.0.0 - 3 February 2009
*! Author: Maurizio Pisati
*! Department of Sociology and Social Research
*! University of Milano Bicocca (Italy)
*! maurizio.pisati@unimib.it




*  ----------------------------------------------------------------------------
*  1. Define program
*  ----------------------------------------------------------------------------

program spkde, sortpreserve
version 10.1




*  ----------------------------------------------------------------------------
*  2. Define syntax
*  ----------------------------------------------------------------------------

syntax [varlist(numeric default=none)] [if] [in] using/,   ///
        Xcoord(varname numeric)                            ///
        Ycoord(varname numeric)                            ///
       [Kernel(string)]                                    ///
       [TRUNCated(numlist max=1 >0)]                       ///
        Bandwidth(string)                                  ///
                                                           ///
       [FBW(string)]                                       ///
       [NDP(numlist max=1 >0)]                             ///
       [NDPW(varname numeric)]                             ///
       [EDGEcorrection]                                    ///
                                                           ///
       [Dots]                                              ///
       [noVERBose]                                         ///
                                                           ///
        SAVing(string asis)




*  ----------------------------------------------------------------------------
*  3. Check syntax
*  ----------------------------------------------------------------------------

/* Check varlist */
local VARLIST "`varlist'"
if ("`VARLIST'" != "") {
   unab VARLIST: `VARLIST'
}
if ("`varlist'" == "") {
   tempvar POINTS
   qui gen `POINTS' = 1
   local varlist "`POINTS'"
}


/* Check using file */
if (substr(reverse("`using'"),1,4) != "atd.") local using "`using'.dta"
capture confirm file "`using'"
if _rc {
   di as err "{p}File {bf:`using'} not found {p_end}"
   exit 601
}
preserve
qui use "`using'", clear
cap confirm numeric variable spgrid_id spgrid_xcoord spgrid_ycoord spgrid_status
if _rc {
   di as err "{p}File {bf:`using'} is not a valid {it:gridpoints} dataset {p_end}"
   exit 198
}
qui drop if spgrid_id == .
qui drop if spgrid_xcoord == .
qui drop if spgrid_ycoord == .
qui drop if spgrid_status == 0
local VGC = `_dta[ValidGridCells]'
qui count
local G = r(N)
if (`VGC' != `G') {
   di as err "{p}File {bf:`using'} is not a valid {it:gridpoints} dataset {p_end}"
   exit 198
}
local CHECK = `_dta[CellArea]'
if ("`CHECK'" == "") {
   di as err "{p}File {bf:`using'} is not a valid {it:gridpoints} dataset {p_end}"
   exit 198
}
local CHECK "`_dta[UnitOfMeasure]'"
if ("`CHECK'" == "") {
   di as err "{p}File {bf:`using'} is not a valid {it:gridpoints} dataset {p_end}"
   exit 198
}
tempvar ID X Y
qui clonevar `ID' = spgrid_id
qui clonevar `X'  = spgrid_xcoord
qui clonevar `Y'  = spgrid_ycoord
keep `ID' `X' `Y'
sort `ID'
tempfile GRID
qui save `GRID', replace
restore


/* Check option kernel() */
if ("`kernel'" != "") {
   local KLIST "uniform normal negexp quartic triangular epanechnikov"
   local LEN = length("`kernel'")
   if (`LEN' < 2) {
      di as err "{p}The argument of option {bf:{ul:k}ernel()} cannot be "   ///
                "abbreviated to less than 2 letters {p_end}"
      exit 198
   }
   local OK = 0
   foreach K of local KLIST {
      if ("`kernel'" == substr("`K'", 1, `LEN')) {
         local OK = 1
         local kernel "`K'"
         continue, break
      }
   }
   if (!`OK') {
      di as err "{p}Option {bf:{ul:k}ernel()} accepts only "   ///
                "one of the following arguments: "             ///
                "{bf:{ul:un}iform} "                           ///
                "{bf:{ul:no}rmal} "                            ///
                "{bf:{ul:ne}gexp} "                            ///
                "{bf:{ul:qu}artic} "                           ///
                "{bf:{ul:tr}iangular} "                        ///
                "{bf:{ul:ep}anechnikov} "                      ///
                "{p_end}"
      exit 198
   }
}


/* Check option bandwidth() */
local KLIST "fbw ndp mixed"
local LEN = length("`bandwidth'")
if (`LEN' < 3) {
   di as err "{p}The argument of option {bf:{ul:b}andwidth()} cannot "   ///
             "be abbreviated to less than 3 letters {p_end}"
   exit 198
}
local OK = 0
foreach K of local KLIST {
   if ("`bandwidth'" == substr("`K'", 1, `LEN')) {
      local OK = 1
      local bandwidth "`K'"
      continue, break
   }
}
if (!`OK') {
   di as err "{p}Option {bf:{ul:b}andwidth()} accepts only "   ///
             "one of the following arguments: "                ///
             "{bf:fbw} "                                       ///
             "{bf:ndp} "                                       ///
             "{bf:{ul:mix}ed} "                                ///
             "{p_end}"
   exit 198
}


/* Check options required when bandwidth(fbw) */
if ("`bandwidth'" == "fbw") & ("`fbw'" == "") {
   di as err "{p}If you specify option {bf:{ul:b}andwidth(fbw)}, "   ///
             "you are requested to specify also option "                  ///
             "{bf:fbw(ad{it:#}|{it:#})} {p_end}"
   exit 198
}


/* Check options required when bandwidth(ndp) */
if ("`bandwidth'" == "ndp") & ("`ndp'" == "") {
   di as err "{p}If you specify option {bf:{ul:b}andwidth(ndp)}, "   ///
             "you are requested to specify also option "                  ///
             "{bf:ndp({it:#})} {p_end}"
   exit 198
}


/* Check options required when bandwidth(mixed) */
if ("`bandwidth'" == "mixed") & ("`fbw'" == "" | "`ndp'" == "") {
   di as err "{p}If you specify option {bf:{ul:b}andwidth({ul:mix}ed}), "    ///
             "you are requested to specify also options "                    ///
             "{bf:fbw(ad{it:#}|{it:#})} and {bf:ndp({it:#})} {p_end}"
   exit 198
}


/* Check option fbw() */
if ("`bandwidth'" == "ndp") local fbw = 0
else {
   local ADK = 0
   qui cap confirm number `fbw'
   if _rc {
      local BAD = 0
      if (substr("`fbw'",1,2) != "ad") local BAD = `BAD' + 1
      local K = substr("`fbw'",3,.)
      qui cap confirm integer number `K'
      local BAD = cond(_rc, `BAD' + 1, `BAD' + (`K'<=0))
      if (`BAD') {
         di as err "{p}Option {bf:{ul:fbw}()} accepts only "         ///
                   "positive numbers or argument {bf:ad{it:#}}, "    ///
                   "where {bf:{it:#}} represents a positive "        ///
                   "integer number {p_end}"
         exit 198
      }
      else {
         local ADK = `K'
      }
   }
   else {
      local BAD = (`fbw' <= 0)
      if (`BAD') {
         di as err "{p}Option {bf:{ul:fbw}()} accepts only "         ///
                   "positive numbers or argument {bf:ad{it:#}}, "    ///
                   "where {bf:{it:#}} represents a positive "        ///
                   "integer number {p_end}"
         exit 198
      }
   }
}


/* Check option ndp() */
if ("`bandwidth'" == "fbw") local ndp = 0


/* Check option ndpw() */
if ("`bandwidth'" == "fbw") local ndpw ""


/* Set and check macros FILENAME and REPLACE */
tokenize `"`saving'"', parse(", ")
local FILENAME "`1'"
local REPLACE "`3'"
if (substr(reverse("`FILENAME'"),1,4) != "atd.") {
   local FILENAME "`FILENAME'.dta"
}
cap confirm file "`FILENAME'"
if (_rc == 0) & ("`REPLACE'" != "replace") {
   di as err "{p}File {bf:`FILENAME'} specified in option "   ///
             "{bf:{ul:sav}ing()} already exists {p_end}"
   exit 602
}




*  ----------------------------------------------------------------------------
*  4. Define basic objects
*  ----------------------------------------------------------------------------

/* Set default kernel function */
if ("`kernel'" == "") local kernel "quartic"


/* Marksample */
marksample TOUSE, novarlist
markout `TOUSE' `xcoord' `ycoord' `ndpw'
qui count if `TOUSE'
if (r(N) == 0) error 2000


/* Preserve data */
preserve


/* Select relevant observations */
qui keep if `TOUSE'


/* Generate variable NDPW */
if (inlist("`bandwidth'","ndp","mixed")) {
   tempvar NDPW
   qui gen `NDPW' = 1
   if ("`ndpw'" != "") qui replace `NDPW' = `ndpw'
   su `NDPW', mean
   if (`ndp' > r(sum) ) {
      di as err "{p}Option {bf:{ul:ndp}()} accepts only positive "   ///
                "values lesser than or equal to `r(sum)' {p_end}"
      exit 198
   }
}


/* Select relevant variables */
keep `xcoord' `ycoord' `varlist' `NDPW'
tempvar IDP
qui gen `IDP' = _n
order `IDP' `xcoord' `ycoord' `varlist' `NDPW'
sort `IDP'
qui count
local N = r(N)


/* Get name of current dataset */
_getfilename "`c(filename)'"
local DATASET "`r(filename)'"


/* Set varlist */
unab varlist: `varlist'
local NV : word count `varlist'


/* Set kernel truncation parameter */
if ("`truncated'" == "") {
   local truncated = 0
}
if inlist("`kernel'", "uniform", "quartic", "triangular", "epanechnikov") {
   local truncated = 1
}


/* Set option dots */
if ("`verbose'" != "") local dots ""


/* Prepare working dataset */
local CHARLIST : char _dta[]
foreach CHAR in local `CHARLIST' {
   char _dta[`CHAR']
}
qui merge using `GRID'
drop _merge
tempvar BW NNDP
qui gen `BW' = .
qui gen `NNDP' = .
if ("`ndpw'" != "") {
   tempvar WSNDP
   qui gen `WSNDP' = .
}
if ("`edgecorrection'" != "") & (`truncated' > 0) {
   tempvar EDGE
   qui gen `EDGE' = .
}
tempvar A
qui gen `A' = .
foreach VAR of varlist `varlist' {
   tempvar `VAR'_sum
   qui gen double ``VAR'_sum' = .
   local SUM "`SUM'``VAR'_sum' "
}


/* Compute bandwidth when fbw(ad#) */
if ("`bandwidth'" != "ndp") & ("`ADK'" != "") {
   if (`ADK') {
      if ("`verbose'" == "") {
         di ""
         di as txt "-> Computing average distance between each data "   ///
                   "point and its `ADK' nearest neighbors..."
      }
      mata: spkdeADk(`N', "`xcoord'", "`ycoord'", `ADK')
   }
}




*  ----------------------------------------------------------------------------
*  5. Execute task
*  ----------------------------------------------------------------------------

/* Compute kernel estimates */
mata: spkde(`N', "`IDP'", "`xcoord'", "`ycoord'", "`varlist'",       ///
            "`kernel'", `truncated', "`bandwidth'", `fbw', `ndp',    ///
            "`NDPW'", `G', "`X'", "`Y'", "`BW'", "`NNDP'",           ///
            "`WSNDP'", "`EDGE'", "`A'", "`SUM'", `_dta[CellArea]',   ///
            "`verbose'", "`dots'")


/* Compute kernel density and intensity estimates */
if ("`verbose'" == "") {
   di as txt "-> Estimating kernel intensities and densities..."
}
local i = 1
foreach VAR of varlist `varlist' {
   if ("`VARLIST'" != "") local V "`VAR'_"
   su `VAR', mean
   local VSUM = r(sum)
   qui gen double `V'lambda = ``VAR'_sum' / `A'
   qui recode `V'lambda (. = 0)
   tempvar TEMP
   qui gen `TEMP' = `V'lambda * `_dta[CellArea]'
   su `TEMP', mean
   local TSUM = r(sum)
   qui gen `V'c = `VSUM' / `TSUM'
   qui replace `V'lambda = `V'lambda * `V'c
   qui gen `V'p = ((`TEMP' * `V'c) / `VSUM') * 100
   qui move `V'c `V'lambda
   if (`"`VARLIST'"' != "") lab var `V'c `"`VAR' - Constant of proportionality"'
   else lab var `V'c "Constant of proportionality"
   if (`"`VARLIST'"' != "") lab var `V'lambda `"`VAR' - Objects per unit area"'
   else lab var `V'lambda "Objects per unit area"
   if (`"`VARLIST'"' != "") lab var `V'p `"`VAR' - Relative frequency of objects (%)"'
   else lab var `V'p "Relative frequency of objects (%)"
   drop `TEMP'
   local i = `i' + 1
}


/* Arrange results dataset */
if ("`verbose'" == "") {
   di as txt "-> Saving results dataset..."
}
drop `IDP' `xcoord' `ycoord' `varlist' `NDPW'
qui drop if `ID' == .
clonevar spgrid_id = `ID'
clonevar spgrid_xcoord = `X'
clonevar spgrid_ycoord = `Y'
qui gen bandwidth = `BW'
lab var bandwidth "Kernel bandwidth"
qui gen ndp = `NNDP'
lab var ndp "Number of data points used for kernel estimation"
if ("`ndpw'" != "") {
   qui gen wndp = `WSNDP'
   lab var wndp "Weighted number of data points used for kernel estimation"
   local KEEPVAR "`KEEPVAR'wndp "
}
if ("`edgecorrection'" != "") & (`truncated' > 0) {
   qui gen edgecorrect = `EDGE'
   lab var edgecorrect "Edge correction factor"
   local KEEPVAR "`KEEPVAR'edgecorrect "
}
qui gen A = `A'
lab var A "Area over which the kernel function has been evaluated"
drop `ID' `X' `Y' `BW' `NNDP' `WSNDP' `EDGE' `A' `SUM'
order spgrid_id spgrid_xcoord spgrid_ycoord bandwidth ndp `KEEPVAR' A
sort spgrid_id


/* Set relevant characteristics */
char _dta[gridpointsfile] "`using'"
char _dta[dataset] "`DATASET'"
char _dta[varlist] "`VARLIST'"
local SEL = "`if' `in'"
if ("`SEL'" == "") local SEL "all"
char _dta[selobs] "`SEL'"
char _dta[DataPoints] "`N'"
char _dta[xcoord] "`xcoord'"
char _dta[ycoord] "`ycoord'"
char _dta[kernel] "`kernel'"
if inlist("`kernel'","normal","negexp") {
   if (`truncated' == 0) {
      char _dta[truncated] "no"
   }
   else {
      char _dta[truncated] `truncated'
   }
}
char _dta[bandwidth] "`bandwidth'"
if (`fbw' > 0) char _dta[fbw] "`fbw'"
if (`ndp' > 0) char _dta[ndp] "`ndp'"
char _dta[ndpw] "`ndpw'"
if ("`edgecorrection'" != "") & (`truncated' > 0) {
   char _dta[edgecorrection] "yes"
}
else {
   char _dta[edgecorrection] "no"
}


/* Save results dataset */
qui compress
qui save "`FILENAME'", `REPLACE'




*  ----------------------------------------------------------------------------
*  6. End program
*  ----------------------------------------------------------------------------

restore
end








*  ----------------------------------------------------------------------------
*  Mata functions
*
*  : spkde()
*  : spkdeADk()
*  : spkdeBandwidth()
*  : sp_*()
*  ----------------------------------------------------------------------------

version 10.1
mata:
mata clear
mata set matastrict on




//*****************************************************************************
//*  spkde()                                                                  *
//*  --> spkdeBandwidth                                                       *
//*  --> sp_distance                                                          *
//*  --> sp_dots1                                                             *
//*  --> sp_dots2                                                             *
//*  --> sp_proximity                                                         *
//*  --> sp_wsum                                                              *
//*****************************************************************************

void spkde(real scalar N, string scalar idp, string scalar xp,
           string scalar yp, string scalar varlist, string scalar kernel,
           real scalar truncated, string scalar bandwidth,
           real scalar fbw, real scalar ndp, string scalar ndpw,
           real scalar G, string scalar xg, string scalar yg,
           string scalar bw, string scalar nndp, string scalar wsndp,
           string scalar edge, string scalar areak, string scalar sum,
           real scalar areac, string scalar verbose, string scalar dots)
{


/* Setup */
real scalar        g, x, y, h
real colvector     ID, NNDP, A, D, BW
string colvector   spwmNEIGHBORS, spwmWEIGHTS
real matrix        XY, VL, XYG, W, EC, SUM

/* Generate working objects */
ID = st_data(1::N, idp)
XY = st_data(1::N, (xp,yp))
VL = st_data(1::N, tokens(varlist))
st_view(XYG, 1::G, (xg,yg))
NNDP = J(G, 1, .)
A = J(G, 1, .)

/* Generate neighbors and weights matrices */
spwmNEIGHBORS = J(G, 1, "")
spwmWEIGHTS = J(G, 1, "")

/* Compute proximity weights and edge correction */
if (verbose == "") {
   printf("\n")
   if (edge != "") printf("{txt}-> Computing proximity weights and edge correction...\n")
   else printf("{txt}-> Computing proximity weights...\n")
   if (dots != "") {
      printf("\n")
      sp_dots1("Grid points", G)
   }
}
for (g=1; g<=G; g++) {
   if (dots != "") sp_dots2(g, G)
   x = _st_data(g, st_varindex(xg))
   y = _st_data(g, st_varindex(yg))
   D = sp_distance(x, y, XY)
   BW = spkdeBandwidth(bandwidth, fbw, ndp, ndpw, N, D)
   h = BW[1]
   W = sp_proximity(D, h, kernel, truncated)
   W = ID , W
   W = select(W, W[.,2] :> 0)
   NNDP[g] = rows(W)
   st_store(g, bw, h)
   st_store(g, nndp, NNDP[g])
   if (wsndp != "") st_store(g, wsndp, BW[2])
   if (NNDP[g] > 0) {
      spwmNEIGHBORS[g] = invtokens(strofreal(W[.,1]'))
      spwmWEIGHTS[g] = invtokens(strofreal(W[.,2]', "%10.0g"))
      if (truncated == 0) A[g] = areac * G
      else {
         h = h*truncated
         A[g] = h * h * pi()
         if (edge != "") {
            EC = select(XYG, (XYG[.,1]:>=(x-h)) :& (XYG[.,1]:<=(x+h)) :&
                             (XYG[.,2]:>=(y-h)) :& (XYG[.,2]:<=(y+h)) )
            EC = sp_distance(x, y, EC)
            EC = sum(EC :<= h)
            EC = EC / ((h * h * pi()) / areac)
            if (EC > 1) EC = 1
            st_store(g, edge, EC)
            A[g] = A[g] * EC
         }
      }
      st_store(g, areak, A[g])
   }
}

/* Compute weighted sums */
SUM = sp_wsum(VL, NNDP, spwmNEIGHBORS, spwmWEIGHTS)
st_store(1::G, tokens(sum), SUM)


}




//*****************************************************************************
//*  spkdeADk()                                                               *
//*  --> sp_distance                                                          *
//*****************************************************************************

void spkdeADk(real scalar N, string scalar xp, string scalar yp,
              real scalar k)
{


/* Setup */
real scalar      i, x, y, h
real colvector   D
real matrix      XY, AD

/* Generate working objects */
XY = st_data(1::N, (xp,yp))
AD = J(N, k, .)

/* Compute distances */
for (i=1; i<=N; i++) {
   x = XY[i,1]
   y = XY[i,2]
   D = sp_distance(x, y, XY)
   D[i] = .
   AD[i,.] = sort(D,1)[1::k]'
}

/* Return bandwidth */
st_local("fbw", strofreal(mean(mean(AD)')))


}




//*****************************************************************************
//*  spkdeBandwidth()                                                         *
//*****************************************************************************

real colvector spkdeBandwidth(string scalar bandwidth, real scalar fbw,
                              real scalar ndp, string scalar ndpw,
                              real scalar N, real colvector D)
{


/* Setup */
real scalar      nsel
real colvector   BW, NDPW
real matrix      SEL

/* Generate working objects */
BW = J(2, 1, .)

/* Fixed bandwidth method */
if (bandwidth == "fbw") {
   BW[1] = fbw
}

/* Minimum (weighted) number of data points method */
if (bandwidth == "ndp") {
   NDPW = st_data(1::N, ndpw)
   NDPW = D , NDPW
   _sort(NDPW, 1)
   NDPW = NDPW[.,1] , runningsum(NDPW[.,2])
   if (NDPW[1,2] > ndp) {
      BW[1] = NDPW[1,1] * 1.000001
      BW[2] = NDPW[1,2]
   }
   else {
      SEL = select(NDPW, NDPW[.,2] :<= ndp)
      if (SEL[rows(SEL),2] < ndp) {
         BW[1] = NDPW[rows(SEL)+1,1] * 1.000001
         BW[2] = NDPW[rows(SEL)+1,2]
      }
      else {
         BW[1] = SEL[rows(SEL),1] * 1.000001
         BW[2] = SEL[rows(SEL),2]
      }
   }
}

/* Mixed method */
if (bandwidth == "mixed") {
   NDPW = st_data(1::N, ndpw)
   NDPW = D , NDPW
   _sort(NDPW, 1)
   NDPW = NDPW[.,1] , runningsum(NDPW[.,2])
   if (NDPW[1,2] > ndp) {
      BW[1] = NDPW[1,1] * 1.000001
      BW[2] = NDPW[1,2]
   }
   else {
      SEL = select(NDPW, NDPW[.,2] :<= ndp)
      if (SEL[rows(SEL),2] < ndp) {
         BW[1] = NDPW[rows(SEL)+1,1] * 1.000001
         BW[2] = NDPW[rows(SEL)+1,2]
      }
      else {
         BW[1] = SEL[rows(SEL),1] * 1.000001
         BW[2] = SEL[rows(SEL),2]
      }
   }
   nsel = rows(select(NDPW, NDPW[.,1] :<= fbw))
   if (nsel > 0) {
      if (NDPW[nsel,2] >= ndp) {
         BW[1] = fbw
         BW[2] = NDPW[nsel,2]
      }
   }
}

/* Return results */
return(BW)


}




//*****************************************************************************
//*  sp_*() - Library of Mata functions for spatial data analysis             *
//*                                                                           *
//*  : sp_distance                                                            *
//*  : sp_dots1                                                               *
//*  : sp_dots2                                                               *
//*  : sp_proximity                                                           *
//*  : sp_wsum                                                                *
//*****************************************************************************




/** 200808 *******************************************************************/
/*                                                                           */
/*  sp_distance                                                              */
/*                                                                           */
/*  Returns a R-by-1 colvector containing the distances between the          */
/*  point defined by the coordinate pair (x,y) and each point in the         */
/*  R-by-2 coordinate matrix POINTS.                                         */
/*  Returned distances can be of two kinds:                                  */
/*  (a) Euclidean distances, when point coordinates are expressed            */
/*      in Cartesian units. These distances are computed using the           */
/*      Pythagorean formula                                                  */
/*  (b) Great-circle distances (in meters, kilometers, feet, yards,          */
/*      miles, or nautical miles), when point coordinates are expressed      */
/*      in latitude/longitude units (decimal degrees). These distances       */
/*      are computed using the Haversine formula and the Earth's             */
/*      quadratic mean radius (6372795.478 m)                                */
/*                                                                           */
/*****************************************************************************/

real colvector sp_distance(real scalar x, real scalar y, real matrix POINTS,
                          |string scalar latlon)
{


/* Setup */
real scalar      base, radius
real colvector   X, Y, A, B, D, DLON, DLAT

/* Generate working objects */
X = POINTS[., 1]
Y = POINTS[., 2]

/* Compute Euclidean distances */
if (latlon == "") {
   A = (X[.] :- x) :* (X[.] :- x)
   B = (Y[.] :- y) :* (Y[.] :- y)
   D = sqrt(A + B)
}

/* Compute great circle distances */
if (latlon != "") {
   base = 6372795.478
   if (latlon == "m")  radius = base
   if (latlon == "km") radius = base * 0.001
   if (latlon == "ft") radius = base * 3.280839895
   if (latlon == "yd") radius = base * 1.093613298
   if (latlon == "mi") radius = base * 0.000621371
   if (latlon == "nm") radius = base * 0.000539956
   x = x * (pi() / 180)
   y = y * (pi() / 180)
   X = X :* (pi() / 180)
   Y = Y :* (pi() / 180)
   DLON = (X :- x) :/ 2
   DLAT = (Y :- y) :/ 2
   D = ((sin(DLAT)) :^ 2) + (cos(y) :* cos(Y) :* ((sin(DLON)) :^ 2))
   D = 2 :* atan2(sqrt(1 :- D), sqrt(D))
   D = D :* radius
}

/* Return results */
return(D)


}




/** 200808 *******************************************************************/
/*                                                                           */
/*  sp_dots1                                                                 */
/*                                                                           */
/*  Displays header & ruler of dots-type verbose output                      */
/*                                                                           */
/*****************************************************************************/

void sp_dots1(string scalar header, real scalar n)
{


printf("{txt}%s (", header)
printf("{res}%g{txt})\n", n)
printf("{txt}{hline 4}{c +}{hline 3} 1 {hline 3}{c +}{hline 3} 2 ")
printf("{txt}{hline 3}{c +}{hline 3} 3 {hline 3}{c +}{hline 3} 4 ")
printf("{txt}{hline 3}{c +}{hline 3} 5\n")


}




/** 200808 *******************************************************************/
/*                                                                           */
/*  sp_dots2                                                                 */
/*                                                                           */
/*  Displays iterations of dots-type verbose output                          */
/*                                                                           */
/*****************************************************************************/

void sp_dots2(real scalar i, real scalar n)
{


/* Setup */
real scalar   linenum

/* Display */
linenum = mod(i,50)
if (linenum != 0  &  i < n) {
   printf("{txt}.")
}
if (linenum == 0  &  i < n) {
   printf("{txt}. %5.0f\n", i)
}
if (i == n) {
   printf("{txt}.\n")
   printf("\n")
}


}




/** 020209 *******************************************************************/
/*                                                                           */
/*  sp_proximity                                                             */
/*                                                                           */
/*  Returns a R-by-1 colvector containing the raw proximity weights          */
/*  corresponding to each distance in an R-by-1 colvector D, computed        */
/*  as a function of kernel bandwidth h, kernel function kf, and             */
/*  kernel truncation parameter tr                                           */
/*                                                                           */
/*****************************************************************************/

real colvector sp_proximity(real colvector D, real scalar h,
                            string scalar kf, real scalar tr)
{


/* Setup */
real colvector   W, OK

/* Uniform function */
if (kf == "uniform") {
   W = J(rows(D), 1, 1)
   OK = D :< h
}

/* Normal function */
if (kf == "normal") {
   W = D :* D
   W = W :/ (h * h)
   W = W :* 0.5
   W = exp(-W)
   if (tr > 0) OK = D :< (h * tr)
   else OK = J(rows(D), 1, 1)
}

/* Negative exponential function */
if (kf == "negexp") {
   W = D :/ h
   W = W :* 3
   W = exp(-W)
   if (tr > 0) OK = D :< (h * tr)
   else OK = J(rows(D), 1, 1)
}

/* Quartic function */
if (kf == "quartic") {
   W = D :* D
   W = W :/ (h * h)
   W = 1 :- W
   W = W :* W
   OK = D :< h
}

/* Triangular function */
if (kf == "triangular") {
   W = D :/ h
   W = 1 :- W
   OK = D :< h
}

/* Epanechnikov function */
if (kf == "epanechnikov") {
   W = D :* D
   W = W :/ (h * h)
   W = 1 :- W
   OK = D :< h
}

/* Return results */
_editmissing(W, 0)
return(W :* OK)


}




/** 200808 *******************************************************************/
/*                                                                           */
/*  sp_wsum                                                                  */
/*                                                                           */
/*  Returns a R1-by-C matrix containing the weighted sum of the R2-by-C      */
/*  matrix X, defined in terms of the R1-by-1 neighbors matrix NEIGHBORS     */
/*  and the R1-by-1 spatial weights matrix WEIGHTS                           */
/*                                                                           */
/*****************************************************************************/

real matrix sp_wsum(real matrix X, real colvector NN,
                    string colvector NEIGHBORS, string colvector WEIGHTS)
{


/* Setup */
real scalar      n, v, i
real colvector   Ni
real rowvector   Wi
real matrix      Xi, SUM

/* Generate working objects */
n = rows(NN)
v = cols(X)
SUM = J(n, v, 0)

/* Compute weighted sum */
for (i=1; i<=n; i++) {
   if (NN[i] > 0) {
      Ni = strtoreal(tokens(NEIGHBORS[i]))'
      Wi = strtoreal(tokens(WEIGHTS[i]))
      Xi = X[Ni,.]
      SUM[i, .] = Wi * Xi
   }
}

/* Return results */
return(SUM)


}




//*****************************************************************************
//*  Exit Mata                                                                *
//*****************************************************************************

end