I came up with an optimise process to try to find the best fit given the additional constraints, empirically. if we could fit on a value of x at a lower number than 3 it would be more protected from violation <0 for the lowest x values

x <- c(3, 5, 7, 14, 21, 28, 56, 100, 200)
y <- c(20, 30, 50, 70, 89, 95, 99.9, 99.99, 99.999)
plot(x,y,pch=19,ylim=c(0,200))
over_100_penalty = -1
under0_penalty = -1
lmfit <- function(k){
model <- lm(y~exp(-k*x))
smry <- summary(model)
base_score <- smry$adj.r.squared
rawpred <- predict(model,newdata=data.frame(x=x))
# print(rawpred)
over_penalty_score <- sum(ifelse(rawpred>100,over_100_penalty,0))
under_penalty_score <- sum(ifelse(rawpred<0,under0_penalty,0))
total_score <- over_penalty_score+under_penalty_score+base_score
cat("tested ",k, "total",total_score, " over penalty score ",over_penalty_score, " under penalty score ", under_penalty_score," fit score (adj r square) ",base_score,"\n")
total_score
}
optimise(lmfit,c(0,10),maximum = TRUE)
# $maximum
# [1] 0.1046782
#
# $objective
# [1] 0.992785
basefit <- lm(y~exp(-0.1046782*x))
xx <- seq(1,200, length=200)
lines(xx, predict(basefit, data.frame(x=xx),max=100), col="green",lwd=1)
coefficients(basefit)
# Coefficients:
# (Intercept) exp(-0.1046782 * x)
# 99.99861 -111.77309
myscorefunc <- function(x){
99.99861 -111.77309 * exp(-0.1046782*x)
}
lines(xx, myscorefunc(xx), col="red",lty="dotted",lwd=4)
myscorefunc(xx)