# 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$yeast <- 5 + (design$x1 * 3)
design$time  <- 2.5 + (design$x2 * 0.5)
design$temp  <- 32.5 + (design$x3 * 2.5)

# Add tensile strength data (example dummy values)
design$ethanol <- 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(ethanol ~ SO(x1, x2, x3), data = design)

# View results
summary(model)
## 
## Call:
## rsm(formula = ethanol ~ SO(x1, x2, x3), data = design)
## 
##             Estimate Std. Error t value  Pr(>|t|)    
## (Intercept)  6.96667    0.73265  9.5088 0.0002175 ***
## x1          -0.57500    0.44866 -1.2816 0.2561931    
## x2           0.20000    0.44866  0.4458 0.6743988    
## x3           0.07500    0.44866  0.1672 0.8737898    
## x1:x2       -1.07500    0.63450 -1.6943 0.1509896    
## x1:x3        0.17500    0.63450  0.2758 0.7937300    
## x2:x3       -0.02500    0.63450 -0.0394 0.9700952    
## x1^2         0.10417    0.66040  0.1577 0.8808401    
## x2^2         0.70417    0.66040  1.0663 0.3350589    
## x3^2         0.25417    0.66040  0.3849 0.7161634    
## ---
## Signif. codes:  0 '***' 0.001 '**' 0.01 '*' 0.05 '.' 0.1 ' ' 1
## 
## Multiple R-squared:  0.5475, Adjusted R-squared:  -0.267 
## F-statistic: 0.6722 on 9 and 5 DF,  p-value: 0.7155
## 
## Analysis of Variance Table
## 
## Response: ethanol
##                 Df Sum Sq Mean Sq F value Pr(>F)
## FO(x1, x2, x3)   3 3.0100 1.00333  0.6231 0.6301
## TWI(x1, x2, x3)  3 4.7475 1.58250  0.9827 0.4712
## PQ(x1, x2, x3)   3 1.9842 0.66139  0.4107 0.7527
## Residuals        5 8.0517 1.61033               
## Lack of fit      3 3.8050 1.26833  0.5973 0.6751
## Pure error       2 4.2467 2.12333               
## 
## Stationary point of response surface:
##          x1          x2          x3 
## -0.67634543 -0.65733518  0.05297128 
## 
## Stationary point in original units:
##    x1.as.is    x2.as.is    x3.as.is 
## -0.67634543 -0.65733518  0.05297128 
## 
## Eigenanalysis:
## eigen() decomposition
## $values
## [1]  1.0236825  0.2603032 -0.2214857
## 
## $vectors
##           [,1]       [,2]       [,3]
## x1  0.50843184 0.08849098  0.8565433
## x2 -0.85810765 0.13495153  0.4954183
## x3  0.07175178 0.98689282 -0.1445484
# Perform ANOVA to evaluate significance of factors
anova(model)
## Analysis of Variance Table
## 
## Response: ethanol
##                 Df Sum Sq Mean Sq F value Pr(>F)
## FO(x1, x2, x3)   3 3.0100 1.00333  0.6231 0.6301
## TWI(x1, x2, x3)  3 4.7475 1.58250  0.9827 0.4712
## PQ(x1, x2, x3)   3 1.9842 0.66139  0.4107 0.7527
## Residuals        5 8.0517 1.61033
# 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)
## $xs
##          x1          x2          x3 
## -0.67634543 -0.65733518  0.05297128 
## 
## $eigen
## eigen() decomposition
## $values
## [1]  1.0236825  0.2603032 -0.2214857
## 
## $vectors
##           [,1]       [,2]       [,3]
## x1  0.50843184 0.08849098  0.8565433
## x2 -0.85810765 0.13495153  0.4954183
## x3  0.07175178 0.98689282 -0.1445484
# 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)
contour(model, ~ x1 + x2,
        at = list(x3 = canon$xs[3]),
        image = TRUE,
        col.regions = topo.colors(20),   # ✅ correct
        xlabs = c("Yeast (coded)", "Time (coded)"),
        main = "Contour Plot: Yeast 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
#to determine optimum point for yeast vs time
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 yeast vs temperature

contour(model, ~ x1 + x3,
        at = list(x2 = canon$xs[2]),       # fix time
        image = TRUE,
        col.regions = topo.colors(20),     # ✅ correct color argument
        xlabs = c("Yeast (coded)", "Temperature (coded)"),
        main = "Contour Plot: Yeast vs Temperature")
## 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 time vs temp

contour(model, ~ x2 + x3,
        at = list(x1 = canon$xs[1]),       # fix Yeast
        image = TRUE,
        col.regions = topo.colors(20),
        xlabs = c("Time (coded)", "Temperature (coded)"),
        main = "Contour Plot: Time vs Temperature")
## 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)

# Arrange 3 plots in one row
par(mfrow = c(1, 3))

# 1. Yeast vs Time
contour(model, ~ x1 + x2,
        at = list(x3 = canon$xs[3]),
        image = TRUE,
        col.regions = topo.colors(20),
        xlabs = c("Yeast", "Time"),
        main = "Yeast 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[2], pch = 19, col = "red")

# 2. Yeast vs Temperature
contour(model, ~ x1 + x3,
        at = list(x2 = canon$xs[2]),
        image = TRUE,
        col.regions = topo.colors(20),
        xlabs = c("Yeast", "Temperature"),
        main = "Yeast vs Temperature")
## 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. Time vs Temperature
contour(model, ~ x2 + x3,
        at = list(x1 = canon$xs[1]),
        image = TRUE,
        col.regions = topo.colors(20),
        xlabs = c("Time", "Temperature"),
        main = "Time vs Temperature")
## 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

#Yeast vs time (fix temp) RSM 3D plot

canon <- canonical(model)

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

# Yeast vs Temp (fix Time) 3D RSM plot

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

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

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

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

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

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

par(mfrow = c(1,1))