# Install and load package
#install.packages("rsm")   # run once
library(rsm)

# Generate Box-Behnken Design (3 factors, 3 center points)
design <- bbd(k = 3, n0 = 3)

# Convert coded variables to actual values
design$clay  <- 80 + (design$x1 * 20)
design$water <- 20 + (design$x2 * 5)
design$time  <- 4 + (design$x3 * 2)

# Add tensile strength data (example dummy values)
design$strength <- c(8.5, 6.2, 8.6, 7.1, 5.8, 7.3, 5.9,
                     9.0, 6.8, 6.5, 7.2, 8.4, 8.8, 8.7, 8.2)

# Fit second-order response surface model - Quadratic model (curved relationship)
# Quadratic terms (x^2) are included to capture curvature
# and allow identification of optimal conditions (max/min).
# Without these terms, the model is linear and cannot locate an optimum.

model <- rsm(strength ~ SO(x1, x2, x3), data = design)

# View results
summary(model)
## 
## Call:
## rsm(formula = strength ~ SO(x1, x2, x3), data = design)
## 
##             Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)  8.16667    0.38723 21.0901 4.441e-06 ***
## x1           0.25000    0.23713  1.0543  0.340009    
## x2           0.48750    0.23713  2.0559  0.094932 .  
## x3           0.03750    0.23713  0.1581  0.880533    
## x1:x2        0.35000    0.33535  1.0437  0.344438    
## x1:x3       -0.45000    0.33535 -1.3419  0.237344    
## x2:x3        0.12500    0.33535  0.3727  0.724616    
## x1^2        -1.69583    0.34904 -4.8585  0.004639 ** 
## x2^2         0.37917    0.34904  1.0863  0.326916    
## x3^2         0.12917    0.34904  0.3701  0.726495    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Multiple R-squared:  0.8736, Adjusted R-squared:  0.6461 
## F-statistic: 3.839 on 9 and 5 DF,  p-value: 0.07627
## 
## Analysis of Variance Table
## 
## Response: strength
##                 Df  Sum Sq Mean Sq F value  Pr(>F)
## FO(x1, x2, x3)   3  2.4125  0.8042  1.7877 0.26588
## TWI(x1, x2, x3)  3  1.3625  0.4542  1.0096 0.46139
## PQ(x1, x2, x3)   3 11.7692  3.9231  8.7211 0.01976
## Residuals        5  2.2492  0.4498                
## Lack of fit      3  0.8025  0.2675  0.3698 0.78687
## Pure error       2  1.4467  0.7233                
## 
## Stationary point of response surface:
##          x1          x2          x3 
## -0.01434532 -0.66090242  0.14964157 
## 
## Stationary point in original units:
##    x1.as.is    x2.as.is    x3.as.is 
## -0.01434532 -0.66090242  0.14964157 
## 
## Eigenanalysis:
## eigen() decomposition
## $values
## [1]  0.4014730  0.1497106 -1.7386836
## 
## $vectors
##          [,1]       [,2]        [,3]
## x1 0.06343108 -0.1346532  0.98886047
## x2 0.98284680 -0.1635065 -0.08531001
## x3 0.17317236  0.9773097  0.12197202
# Perform ANOVA to evaluate significance of factors
anova(model)
## Analysis of Variance Table
## 
## Response: strength
##                 Df  Sum Sq Mean Sq F value  Pr(>F)  
## FO(x1, x2, x3)   3  2.4125  0.8042  1.7877 0.26588  
## TWI(x1, x2, x3)  3  1.3625  0.4542  1.0096 0.46139  
## PQ(x1, x2, x3)   3 11.7692  3.9231  8.7211 0.01976 *
## Residuals        5  2.2492  0.4498                  
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
# Anova Interpretation:
# p > 0.05 → factors are not statistically significant
# Lack-of-fit p > 0.05 → model is adequate


# Find stationary point (optimal condition)
canonical(model)
## Near-stationary-ridge situation detected -- stationary point altered
##  Change 'threshold' if this is not what you intend
## $xs
##          x1          x2          x3 
##  0.02015815 -0.61900558 -0.10078389 
## 
## $eigen
## eigen() decomposition
## $values
## [1]  0.401473  0.000000 -1.738684
## 
## $vectors
##          [,1]       [,2]        [,3]
## x1 0.06343108 -0.1346532  0.98886047
## x2 0.98284680 -0.1635065 -0.08531001
## x3 0.17317236  0.9773097  0.12197202
# Canonical analysis results:
# xs = stationary point of the response surface in coded variables
# Eigenvalues determine whether the point is a maximum, minimum, or saddle

#Now let us do a contour plot of the model

canon <- canonical(model)
## Near-stationary-ridge situation detected -- stationary point altered
##  Change 'threshold' if this is not what you intend
contour(model, ~ x1 + x2,
        at = list(x3 = canon$xs[3]),
        image = TRUE,
        col.regions = topo.colors(20),   # ✅ correct
        xlabs = c("Clay (coded)", "Water (coded)"),
        main = "Contour Plot: Clay vs Water")
## Warning in plot.window(...): "col.regions" is not a graphical parameter
## Warning in plot.xy(xy, type, ...): "col.regions" is not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in box(...): "col.regions" is not a graphical parameter
## Warning in title(...): "col.regions" is not a graphical parameter
#to determine optimum point for clay vs water
points(canon$xs[1], canon$xs[2],
       pch = 19, col = "red", cex = 1.5)

text(canon$xs[1], canon$xs[2],
     labels = "Optimum",
     pos = 4, col = "red")

#contour plot of clay vs time

contour(model, ~ x1 + x3,
        at = list(x2 = canon$xs[2]),       # fix water
        image = TRUE,
        col.regions = topo.colors(20),     # ✅ correct color argument
        xlabs = c("Clay (coded)", "Time (coded)"),
        main = "Contour Plot: Clay vs Time")
## Warning in plot.window(...): "col.regions" is not a graphical parameter
## Warning in plot.xy(xy, type, ...): "col.regions" is not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in box(...): "col.regions" is not a graphical parameter
## Warning in title(...): "col.regions" is not a graphical parameter
# Add optimum point
points(canon$xs[1], canon$xs[3],
       pch = 19, col = "red", cex = 1.5)

text(canon$xs[1], canon$xs[3],
     labels = "Optimum",
     pos = 4, col = "red")

#contour plot of water vs time

contour(model, ~ x2 + x3,
        at = list(x1 = canon$xs[1]),       # fix clay
        image = TRUE,
        col.regions = topo.colors(20),
        xlabs = c("Water (coded)", "Time (coded)"),
        main = "Contour Plot: Water vs Time")
## Warning in plot.window(...): "col.regions" is not a graphical parameter
## Warning in plot.xy(xy, type, ...): "col.regions" is not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in box(...): "col.regions" is not a graphical parameter
## Warning in title(...): "col.regions" is not a graphical parameter
# Add optimum point
points(canon$xs[2], canon$xs[3],
       pch = 19, col = "red", cex = 1.5)

text(canon$xs[2], canon$xs[3],
     labels = "Optimum",
     pos = 4, col = "red")

#if you want to visualize the above in one screen

canon <- canonical(model)
## Near-stationary-ridge situation detected -- stationary point altered
##  Change 'threshold' if this is not what you intend
# Arrange 3 plots in one row
par(mfrow = c(1, 3))

# 1. Clay vs Water
contour(model, ~ x1 + x2,
        at = list(x3 = canon$xs[3]),
        image = TRUE,
        col.regions = topo.colors(20),
        xlabs = c("Clay", "Water"),
        main = "Clay vs Water")
## Warning in plot.window(...): "col.regions" is not a graphical parameter
## Warning in plot.xy(xy, type, ...): "col.regions" is not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in box(...): "col.regions" is not a graphical parameter
## Warning in title(...): "col.regions" is not a graphical parameter
points(canon$xs[1], canon$xs[2], pch = 19, col = "red")

# 2. Clay vs Time
contour(model, ~ x1 + x3,
        at = list(x2 = canon$xs[2]),
        image = TRUE,
        col.regions = topo.colors(20),
        xlabs = c("Clay", "Time"),
        main = "Clay vs Time")
## Warning in plot.window(...): "col.regions" is not a graphical parameter
## Warning in plot.xy(xy, type, ...): "col.regions" is not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in box(...): "col.regions" is not a graphical parameter
## Warning in title(...): "col.regions" is not a graphical parameter
points(canon$xs[1], canon$xs[3], pch = 19, col = "red")

# 3. Water vs Time
contour(model, ~ x2 + x3,
        at = list(x1 = canon$xs[1]),
        image = TRUE,
        col.regions = topo.colors(20),
        xlabs = c("Water", "Time"),
        main = "Water vs Time")
## Warning in plot.window(...): "col.regions" is not a graphical parameter
## Warning in plot.xy(xy, type, ...): "col.regions" is not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in axis(side = side, at = at, labels = labels, ...): "col.regions" is
## not a graphical parameter
## Warning in box(...): "col.regions" is not a graphical parameter
## Warning in title(...): "col.regions" is not a graphical parameter
points(canon$xs[2], canon$xs[3], pch = 19, col = "red")

# Reset layout
par(mfrow = c(1,1))

#3D surface RSM plots

#Clay vs water (fix time) RSM 3D plot

canon <- canonical(model)
## Near-stationary-ridge situation detected -- stationary point altered
##  Change 'threshold' if this is not what you intend
persp(model, ~ x1 + x2,
      at = list(x3 = canon$xs[3]),   # fix time
      zlab = "Strength",
      col = "lightblue",
      theta = 30,
      phi = 25,
      main = "3D Surface: Clay vs Water",
      ticktype = "detailed")

# Clay vs Time (fix Water) 3D RSM plot

persp(model, ~ x1 + x3,
      at = list(x2 = canon$xs[2]),   # fix water
      zlab = "Strength",
      col = "lightgreen",
      theta = 30,
      phi = 25,
      main = "3D Surface: Clay vs Time",
      ticktype = "detailed")

# Water vs Time (fix clay) 3D RSM plot
persp(model, ~ x2 + x3,
      at = list(x1 = canon$xs[1]),   # fix clay
      zlab = "Strength",
      col = "lightpink",
      theta = 30,
      phi = 25,
      main = "3D Surface: Water vs Time",
      ticktype = "detailed")

#All 3D contour plot for RSM in a single plot
par(mfrow = c(1, 3))

# Clay vs Water
persp(model, ~ x1 + x2,
      at = list(x3 = canon$xs[3]),
      zlab = "Strength", col = "lightblue",
      theta = 30, phi = 25, main = "Clay vs Water")

# Clay vs Time
persp(model, ~ x1 + x3,
      at = list(x2 = canon$xs[2]),
      zlab = "Strength", col = "lightgreen",
      theta = 30, phi = 25, main = "Clay vs Time")

# Water vs Time
persp(model, ~ x2 + x3,
      at = list(x1 = canon$xs[1]),
      zlab = "Strength", col = "lightpink",
      theta = 30, phi = 25, main = "Water vs Time")

par(mfrow = c(1,1))