diff --git a/_examples/other.go b/_examples/other.go index bf185b1..8935a32 100644 --- a/_examples/other.go +++ b/_examples/other.go @@ -18,8 +18,8 @@ func main() { mu := []float64{1.0, 2.0} // Initial mean vector sigma := []float64{0.5, 0.5} // Initial standard deviation vector - optimizedMu, optimizedSigma := GoES.DefaultOpt(myCustomFunction, mu, sigma) + res, _ := GoES.DefaultOpt(myCustomFunction, mu, sigma) - fmt.Println("Optimized mean:", optimizedMu) - fmt.Println("Optimized standard deviation:", optimizedSigma) + fmt.Println("Optimized mean:", res.Mu) + fmt.Println("Optimized standard deviation:", res.Sigma) } diff --git a/_examples/sphere.go b/_examples/sphere.go index bae2704..3a631a9 100644 --- a/_examples/sphere.go +++ b/_examples/sphere.go @@ -29,7 +29,7 @@ func main() { cfg.Verbose = false // Perform optimization - optimizedMu, _ := GoES.Opt(sphere, mu, sigma, cfg) + res, _ := GoES.Opt(sphere, mu, sigma, cfg) - fmt.Println("Optimum:", optimizedMu) // should be close to vector [0, 1, 2, ..., dim-1] + fmt.Println("Optimum:", res.Mu) // should be close to vector [0, 1, 2, ..., dim-1] } diff --git a/main.go b/main.go index d8dd22c..56fd026 100644 --- a/main.go +++ b/main.go @@ -37,9 +37,10 @@ This code implements a specific algorithm called CMA-ES (Covariance Matrix Adapt Overall, this code provides an implementation of CMA-ES for optimizing a black-box function in Go. It allows you to specify your own objective function and configure various parameters for the optimization process. */ -package goes +package GoES import ( + "fmt" "log" "math" "slices" @@ -69,12 +70,17 @@ func Defaults() Config { return cfg } +type Result struct { + Mu []float64 + Sigma []float64 +} + const const_Ez0 = 0.7978845608028661 // mean(abs(randn())) -func Opt(fn func([]float64) float64, mu []float64, sigma []float64, cfg Config) ([]float64, []float64) { +func Opt(fn func([]float64) float64, mu []float64, sigma []float64, cfg Config) (Result, error) { pop_n := cfg.PopSize n := len(mu) if len(sigma) != n { - log.Panic("mu and sigma must have the same length.") + return Result{}, fmt.Errorf("mu (len %d) and sigma (len %d) must have the same length", len(mu), len(sigma)) } for pop_n*pop_n <= 144*n { pop_n++ @@ -149,10 +155,10 @@ func Opt(fn func([]float64) float64, mu []float64, sigma []float64, cfg Config) log.Println("GoES: ", runs, mu, sigma, pop[pop_n/2].C) } } - return mu, sigma + return Result{Mu: mu, Sigma: sigma}, nil } -func DefaultOpt(fn func([]float64) float64, mu []float64, sigma []float64) ([]float64, []float64) { +func DefaultOpt(fn func([]float64) float64, mu []float64, sigma []float64) (Result, error) { cfg := Defaults() cfg.Generations = int(math.Ceil(math.Sqrt(float64(len(mu)*2+1)) * 300)) return Opt(fn, mu, sigma, cfg) @@ -174,3 +180,19 @@ func makeWeights(pop_size int) []float64 { } return W } + +func Positive(z float64) float64 { + if z < 0 { + return 1 / (1 - z) + } + return z + 1 +} + +func Probability(z float64) float64 { + p := Positive(z) + return p / (1 + p) +} + +func Bounded(x, a, b float64) float64 { + return Probability(x)*(b-a) + a +} diff --git a/main_test.go b/main_test.go index 94512dc..181dc5e 100644 --- a/main_test.go +++ b/main_test.go @@ -1,4 +1,4 @@ -package goes +package GoES import ( "bytes" @@ -22,8 +22,11 @@ func cost_test(ince float64, iva_detratta float64) float64 { pen := impo*perc_pay - ince_l return abs2(pen) } - mu, _ := DefaultOpt(cost, []float64{2 * ince}, []float64{ince / 10}) - return mu[0] + sol, err := DefaultOpt(cost, []float64{ince * 0.9}, []float64{ince / 10}) + if err != nil { + return math.NaN() + } + return sol.Mu[0] } func TestUni(t *testing.T) { @@ -42,9 +45,14 @@ func TestUni(t *testing.T) { func TestBi(t *testing.T) { muw := []float64{4, -3} - mu, sig := DefaultOpt(func(f []float64) float64 { + sol, err_opt := DefaultOpt(func(f []float64) float64 { return abs2(f[0]-muw[0]) + 100.0*abs2(f[0]+f[1]-muw[0]-muw[1]) }, []float64{0.0, 0.0}, []float64{1.0, 1.0}) + if err_opt != nil { + t.Error(err_opt) + } + mu := sol.Mu + sig := sol.Sigma err := math.Sqrt(abs2((mu[0]-muw[0])/muw[0]) + abs2((mu[1]-muw[1])/muw[1])) if err > 1e-6 { t.Error("got: ", mu, sig, " wanted:", muw, " error:", err) @@ -66,17 +74,21 @@ func TestVerbose(t *testing.T) { } } -func TestPanic(t *testing.T) { - defer func() { - if r := recover(); r == nil { - t.Errorf("The code did not panic") - } - }() - - // The following is the code under test for panicking - DefaultOpt( +func TestError(t *testing.T) { + _, err := DefaultOpt( func(f []float64) float64 { return 0.0 }, []float64{0.0, 0.0}, []float64{1.0}, // here there is a missing element ) + if err == nil { + t.Error("Expected error") + } + _, err = DefaultOpt( + func(f []float64) float64 { return 0.0 }, + []float64{0.0, 0.0}, + []float64{1.0, 1.0}, // here there is a missing element + ) + if err != nil { + t.Error("Expected success") + } } diff --git a/readme.md b/readme.md index f5d0007..9f91c81 100644 --- a/readme.md +++ b/readme.md @@ -66,11 +66,10 @@ func main() { cfg.Verbose = false // Perform optimization - optimizedMu, _ := GoES.Opt(sphere, mu, sigma, cfg) + res, _ := GoES.Opt(sphere, mu, sigma, cfg) - fmt.Println("Optimum:", optimizedMu) // should be close to vector [0, 1, 2, ..., dim-1] + fmt.Println("Optimum:", res.Mu) // should be close to vector [0, 1, 2, ..., dim-1] } - ``` **Example 2: Default Optimization Config** @@ -98,9 +97,9 @@ func main() { mu := []float64{1.0, 2.0} // Initial mean vector sigma := []float64{0.5, 0.5} // Initial standard deviation vector - optimizedMu, optimizedSigma := GoES.DefaultOpt(myCustomFunction, mu, sigma) + res, _ := GoES.DefaultOpt(myCustomFunction, mu, sigma) - fmt.Println("Optimized mean:", optimizedMu) - fmt.Println("Optimized standard deviation:", optimizedSigma) + fmt.Println("Optimized mean:", res.Mu) + fmt.Println("Optimized standard deviation:", res.Sigma) } ```