From 68b14f9960ae7c45ce1eb97de5348d0953781c4b Mon Sep 17 00:00:00 2001 From: leo Date: Sat, 24 Dec 2022 11:30:22 +0100 Subject: [PATCH] chore: add all updates, sort out later --- .drone.star | 2 +- .envrc | 2 + .golangci.yml | 2 +- algo/algo.go | 68 ++++++++++++++++++++++++++++++++++ algo/algo_test.go | 10 +++++ algo/plot.go | 19 ++++++++++ algo/stochasticHillClimbing.go | 13 ++++++- bench/functions.go | 2 + flake.nix | 25 ++++++++++++- report/alltables.tmpl | 2 +- report/pic.go | 2 +- report/pics.tmpl | 3 ++ report/report.go | 4 ++ report/report_base.tmpl | 4 ++ report/table.tmpl | 1 + run.go | 2 + 16 files changed, 155 insertions(+), 6 deletions(-) diff --git a/.drone.star b/.drone.star index a118c2a..1b0a994 100644 --- a/.drone.star +++ b/.drone.star @@ -316,4 +316,4 @@ def main(ctx): } ] -# vim: ft=bzl.starlark syntax=bzl.starlark noexpandtab ts=4 foldmethod=manual +# vim:ft=bzl.starlark:syntax=bzl.starlark:noexpandtab:ts=4:sts=4:sw=4:foldmethod=manual diff --git a/.envrc b/.envrc index 63555e8..5970342 100644 --- a/.envrc +++ b/.envrc @@ -4,4 +4,6 @@ use flake # comment out if not planning to use this add_extra_vimrc +# alias gor='go run' + # vim: ff=unix ft=sh diff --git a/.golangci.yml b/.golangci.yml index 7f02539..01c029e 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -13,7 +13,7 @@ issues: linters: enable: - bidichk - - dupl + # - dupl # The linter 'deadcode' is deprecated (since v1.49.0) due to: The owner # seems to have abandoned the linter. Replaced by unused. # - deadcode diff --git a/algo/algo.go b/algo/algo.go index 0c1ad8e..d1cadfd 100644 --- a/algo/algo.go +++ b/algo/algo.go @@ -14,6 +14,8 @@ "git.dotya.ml/wanderer/math-optim/stats" ) +// var Algos = []string{"Random Search", "Stochastic Hill Climbing"} + // mu protects access to meanStats. var mu sync.Mutex @@ -157,6 +159,11 @@ funcCount := len(bench.Functions) algoStats[i] = s } + // save stats to json. + // stats.SaveStats(schw, "schwefel") + // stats.SaveStats(djg1, "djg1") + // stats.SaveStats(djg2, "djg2") + pCh := make(chan report.PicList, funcCount*len(bench.Dimensions)) pMeanCh := make(chan report.PicList, funcCount*len(bench.Dimensions)) @@ -188,6 +195,14 @@ funcCount := len(bench.Functions) m.Unlock() } +// TODO(me): split this package to multiple - package per algo, common code here. +// TODO(me): implement Simulated Annaeling. +// TODO(me): implement a variant of Stochastic Hill Climber that tweaks its +// Neighbourhood size or MaxNeighbourVariancePercent based on the +// latest 5 values, if they don't change, params get tweaked to +// broaden the search space to make sure it's not stuck in a local +// extreme. + // DoStochasticHillClimbing performs a search using the 'Stochastic Hill // Climbing' method. func DoStochasticHillClimbing(wg *sync.WaitGroup, m *sync.Mutex) { @@ -250,3 +265,56 @@ funcCount := len(bench.Functions) stats.SaveTable(algoName, algoStats) m.Unlock() } + +func DoStochasticHillClimbing100Neigh(wg *sync.WaitGroup, m *sync.Mutex) { + defer wg.Done() + + printSHC("starting...") + + // funcCount is the number of bench functions available. + funcCount := len(bench.Functions) + // stats for the current algo (StochasticHillClimber). + algoStats := make([][]stats.Stats, funcCount) + // ch serves as a way to get the actual computed output. + ch := make(chan []stats.Stats, funcCount) + + for i := range algoStats { + go HillClimb(10000, 30, 100, bench.Dimensions, bench.FuncNames[i], ch) + } + + // get results. + for i := range algoStats { + s := <-ch + + algoStats[i] = s + } + + pCh := make(chan report.PicList, funcCount*len(bench.Dimensions)) + pMeanCh := make(chan report.PicList, funcCount*len(bench.Dimensions)) + + for _, algoStat := range algoStats { + go plotAllDims(algoStat, "plot", ".pdf", pCh, pMeanCh) + } + + pLs := []report.PicList{} + pLsMean := []report.PicList{} + + for range algoStats { + pL := <-pCh + pLMean := <-pMeanCh + + pLs = append(pLs, pL) + pLsMean = append(pLsMean, pLMean) + } + + algoName := "Stochastic Hill Climbing 100 Neighbours" + + // protect access to shared data. + m.Lock() + report.SavePicsToFile(pLs, pLsMean, algoName) + // report.SavePicsToFile(pLsMean, pLs, algoName) + + // stats.PrintStatisticTable(algoStats) + stats.SaveTable(algoName, algoStats) + m.Unlock() +} diff --git a/algo/algo_test.go b/algo/algo_test.go index 31d0f0b..476c58f 100644 --- a/algo/algo_test.go +++ b/algo/algo_test.go @@ -4,6 +4,7 @@ package algo import ( + "log" "os" "sync" "testing" @@ -16,14 +17,18 @@ var m sync.Mutex func TestDoRandomSearchExec(t *testing.T) { + t.Parallel() + wg.Add(1) + // use t.tmpdir go DoRandomSearch(&wg, &m) wg.Wait() picsDir := report.GetPicsDir() + "-test-rs" + // attempt to clean up. if err := os.RemoveAll(picsDir); err != nil { t.Error(err) } @@ -34,6 +39,7 @@ func TestDoRandomSearchExec(t *testing.T) { t.Error("picsDir should have already been cleaned up") } + log.Println("pwd:", os.Getenv("PWD")) // clean up outdir. if err := os.RemoveAll("out"); err != nil { t.Error(err) @@ -41,6 +47,8 @@ func TestDoRandomSearchExec(t *testing.T) { } func TestDoSHCExec(t *testing.T) { + t.Parallel() + wg.Add(1) go DoStochasticHillClimbing(&wg, &m) @@ -49,6 +57,7 @@ func TestDoSHCExec(t *testing.T) { picsDir := report.GetPicsDir() + "-test-shc" + // attempt to clean up. if err := os.RemoveAll(picsDir); err != nil { t.Error(err) } @@ -59,6 +68,7 @@ func TestDoSHCExec(t *testing.T) { t.Error("picsDir should have already been cleaned up") } + log.Println("pwd:", os.Getenv("PWD")) // clean up outdir. if err := os.RemoveAll("out"); err != nil { t.Error(err) diff --git a/algo/plot.go b/algo/plot.go index 16baf69..4b4f099 100644 --- a/algo/plot.go +++ b/algo/plot.go @@ -121,6 +121,7 @@ func PlotMeanValsMulti( // set pic file path and caption. pic.FilePath = filename + // pic.Caption = strings.ReplaceAll(filename, " ", "~") pic.Caption = strings.ReplaceAll( fmt.Sprintf("Comparison of Means (%dI) - %s (%dD)", iterations, bench, dimens, @@ -157,21 +158,27 @@ func plotMeanVals(meanVals []float64, title string, fes int) *plot.Plot { } p := plot.New() + // pic := report.NewPic() p.Title.Text = "Mean - " + title p.X.Label.Text = xAxisLabel + // p.X.Label.Padding = 8 * vg.Millimeter p.X.Label.TextStyle.Font.Variant = preferredFont p.X.Label.TextStyle.Font.Weight = 1 // Medium p.X.Tick.Label.Font.Variant = preferredFont + // p.X.Padding = 2 * vg.Millimeter p.Y.Label.Text = yAxisLabel + // p.Y.Label.Padding = 2 * vg.Millimeter p.Y.Label.TextStyle.Font.Variant = preferredFont p.Y.Label.TextStyle.Font.Weight = 1 // Medium p.Y.Tick.Label.Font.Variant = preferredFont + // p.Y.Padding = 1 * vg.Millimeter p.Title.TextStyle.Font.Size = 14.5 p.Title.TextStyle.Font.Variant = titlePreferredFont p.Title.TextStyle.Font.Weight = 2 // SemiBold + // p.Title.Padding = 5 * vg.Millimeter p.Title.Padding = 3 * vg.Millimeter // mark the end of the X axis with len(meanVals). @@ -255,12 +262,20 @@ func plotAllDims(algoStats []stats.Stats, fPrefix, fExt string, ch chan report.P p.Y.Label.TextStyle.Font.Variant = preferredFont p.Y.Label.TextStyle.Font.Weight = 1 // Medium p.Y.Tick.Label.Font.Variant = preferredFont + // p.Y.Padding = 1 * vg.Millimeter p.Title.TextStyle.Font.Size = 14.5 p.Title.TextStyle.Font.Variant = titlePreferredFont p.Title.TextStyle.Font.Weight = 2 // SemiBold + // p.Title.Padding = 5 * vg.Millimeter p.Title.Padding = 3 * vg.Millimeter + // p.Legend.TextStyle.Font.Variant = preferredFontStyle + // p.Legend.TextStyle.Font.Size = 8 + // p.Legend.Top = true + // p.Legend.Padding = 0 * vg.Centimeter + // p.Add(plotter.NewGrid()) + for _, dim := range s.BenchFuncStats { // infinite thanks to this SO comment for the interface "hack": // https://stackoverflow.com/a/44872993 @@ -286,6 +301,7 @@ func plotAllDims(algoStats []stats.Stats, fPrefix, fExt string, ch chan report.P pts[k].Y = res } + // lines = append(lines, "#"+fmt.Sprint(j), pts) lines = append(lines, pts) } @@ -299,6 +315,7 @@ func plotAllDims(algoStats []stats.Stats, fPrefix, fExt string, ch chan report.P } // TODO(me): add Neighbourhood param + // TODO(me): add search space percent param filename := fmt.Sprintf("%s%s-%s-%s-%dD-%dG-%dI", picsDir, fPrefix, @@ -333,6 +350,7 @@ func plotAllDims(algoStats []stats.Stats, fPrefix, fExt string, ch chan report.P filename, fExt, elapsed, ) + // TODO(me): rework this. if s.Algo == "Random Search" { printRandomSearch(info) } else { @@ -347,6 +365,7 @@ func plotAllDims(algoStats []stats.Stats, fPrefix, fExt string, ch chan report.P ); err != nil { panic(err) } + // log.Println(filename + fExt) // save pic. pics = append(pics, *pic) diff --git a/algo/stochasticHillClimbing.go b/algo/stochasticHillClimbing.go index e553a2f..5548b43 100644 --- a/algo/stochasticHillClimbing.go +++ b/algo/stochasticHillClimbing.go @@ -59,6 +59,8 @@ func selectBestNeighbour(neighbours []neighbour, benchFuncName string) ([]float6 // f is the actual bench function, based on the name. f := bench.Functions[benchFuncName] + // fmt.Println("select best\nlen(neighbours)", len(neighbours)) + for i, v := range neighbours { switch i { case 0: @@ -92,6 +94,8 @@ func getBenchSearchSpaceSize(benchName string) float64 { // the allowedTweak value should therefore the amount to at most 10% of the // searchSpaceSize. TODO(me): floor this down. func getAllowedTweak(searchSpaceSize float64) float64 { + // TODO(me): have this passed to HillClimb and from there into this func. + // return (bench.MaxNeighbourVariancePercent * 0.5) * (searchSpaceSize * 0.01) return bench.MaxNeighbourVariancePercent * (searchSpaceSize * 0.01) } @@ -103,7 +107,9 @@ func genNeighbours(n, dimens int, benchName string, origin []float64, neighbVals // create a new representation of the uniform distribution with the bounds // unset for the moment, since we need to find out what exactly those ought // to be (and we will - a couple of lines later). + // uniform := distuv.Uniform{Src: time.Now().UnixMicro()} uniform := distuv.Uniform{} + // uniform.Src.Seed(uint64(time.Now().UnixNano())) params := bench.FunctionParams[benchName] // get bench function bounds. benchMin := params.Min() @@ -116,6 +122,7 @@ func genNeighbours(n, dimens int, benchName string, origin []float64, neighbVals uniform.Src = rand.NewSource(uint64(time.Now().UnixNano())) for _, v := range neighbVals { + // for _, v := range neighbVals { for i := 0; i < dimens; i++ { newMin := origin[i] - allowedTweak @@ -130,7 +137,9 @@ func genNeighbours(n, dimens int, benchName string, origin []float64, neighbVals if newMin > benchMax { uniform.Max = newMax } + // fmt.Println("newMin:", newMin, "newMax", newMax) + // v = append(v, uniform.Rand()) v[i] = uniform.Rand() } } @@ -236,7 +245,7 @@ funcStats := &stats.FuncStats{BenchName: benchFunc} funcStats.BenchResults = make([]stats.BenchRound, minIters) - // create a source of preudo-randomness. + // create and seed a source of preudo-randomness src := rand.NewSource(uint64(rand.Int63())) // src := rand.NewSource(uint64(time.Now().UnixNano())) @@ -249,6 +258,7 @@ funcStats.BenchResults[iter].Iteration = iter // create and stochastically populate the vals slice. initVals := make([]float64, dimens) + // reseed using current time. uniDist.Src = src var bestResult float64 @@ -337,6 +347,7 @@ funcStats.MeanVals = dimXMean.MeanVals shcMeans.BenchMeans = append(shcMeans.BenchMeans, *dimXMean) } + // log.Printf("%+v\n", shcMeans) sort.Sort(shcMeans) // export AlgoMeans. diff --git a/bench/functions.go b/bench/functions.go index fae3d16..a1be7ae 100644 --- a/bench/functions.go +++ b/bench/functions.go @@ -29,6 +29,8 @@ // Schwefel computes the value of the Schwefel function for x. func Schwefel(x []float64) float64 { + // - Domain is | x_i | < 500 + // - Global minimum at fmin = -122569.5 at x_i = 420.9687 var res float64 for _, val := range x { diff --git a/flake.nix b/flake.nix index 3976c7d..dd6c8ec 100644 --- a/flake.nix +++ b/flake.nix @@ -20,9 +20,15 @@ }: let projname = "math-optim"; + system.configurationRevision = + self.rev + or throw "Refusing to build from a dirty Git tree!"; + nix.registry.nixpkgs.flake = nixpkgs; + # to work with older version of flakes lastModifiedDate = self.lastModifiedDate or self.lastModified or "19700101"; + # lastModifiedDate = "19700101"; # Generate a user-friendly version number. version = "v0.0.0"; @@ -57,8 +63,12 @@ pname = "${projname}"; buildInputs = [ go + # gcc + # glibc + # glibc.static ]; nativeBuildInputs = [pkgconfig]; + # nativeBuildInputs = [go glibc.static]; overrideModAttrs = _: { # GOPROXY = "direct"; @@ -189,11 +199,22 @@ { name = "${projname}-" + version; + dontAutoPatchelf = ""; GOFLAGS = "-buildmode=pie -trimpath -mod=readonly -modcacherw"; GOLDFLAGS = "-s -w -X main.version=${version}"; - CGO_CFLAGS = "-g0 -mtune=native"; + CGO_CFLAGS = "-g0 -Ofast -mtune=native -flto"; CGO_LDFLAGS = "-Wl,-O1,-sort-common,-as-needed,-z,relro,-z,now,-flto -pthread"; + # GOLDFLAGS = "-s -w -X main.version=${version} -linkmode external -extldflags -static"; + # CGO_CFLAGS = "-g0 -mtune=native + # -I${pkgs.glibc.dev}/include + # "; + # LDFLAGS = "-L${pkgs.glibc}/lib"; + # CGO_LDFLAGS = " + # -Wl,-O1,-sort-common,-as-needed,-z,relro,-z,now,-flto -pthread + # -L${pkgs.glibc}/lib + # "; GOPROXY = "direct"; + # GOMEMLIMIT = "10GiB"; shellHook = '' echo " -- in math-optim dev shell..." @@ -212,6 +233,8 @@ statix alejandra + # glibc.static + ## ad-hoc cmds gob gota diff --git a/report/alltables.tmpl b/report/alltables.tmpl index f07a9f5..6b9ae20 100644 --- a/report/alltables.tmpl +++ b/report/alltables.tmpl @@ -10,7 +10,7 @@ \section{Per-algo benchmark comparison statistics} {{ range $i, $v := .AllTables.TexFiles }} {{- range $j, $u := $v.FilePaths }} -\input{ {{- $u -}} } +\input{ {{- printf "{%s}" $u -}} } \newpage {{- end -}} {{ end }} diff --git a/report/pic.go b/report/pic.go index 582a9f1..d2c7135 100644 --- a/report/pic.go +++ b/report/pic.go @@ -64,7 +64,7 @@ func NewPicList() *PicList { // SavePicsToFile saves each pic list for all bench funcs of a specified algo // to a file. func SavePicsToFile(pls, plsMean []PicList, algoName string) { - var paths []string + paths := make([]string, 0, len(pls)) ptf := picTexFiles{Algo: algoName} diff --git a/report/pics.tmpl b/report/pics.tmpl index ba70d40..4a650e9 100644 --- a/report/pics.tmpl +++ b/report/pics.tmpl @@ -19,8 +19,10 @@ {\includegraphics[scale=0.45]{ {{- printf "%s" $v.FilePath -}} }} \caption{ {{- printf "\\scriptsize{%s}" $v.Caption -}} } \end{subfigure} + % \hspace{1.3em} \hfill {{- end -}} +% \newline {{ range $k, $w := .PicsMean }} \begin{subfigure}{0.30\textwidth} \vspace{2em} @@ -29,6 +31,7 @@ {\includegraphics[scale=0.45]{ {{- printf "%s" $w.FilePath -}} }} \caption{ {{- printf "\\scriptsize{%s}" $w.Caption -}} } \end{subfigure} + % \hspace{1.3em} \hfill {{- end }} \caption{ {{- printf "%s - %s" .Algo .Bench -}} } diff --git a/report/report.go b/report/report.go index 250baa7..043294c 100644 --- a/report/report.go +++ b/report/report.go @@ -28,6 +28,10 @@ tmplReportFile []byte ) +func GetOutPrefix() string { + return outPrefix +} + // GetPicsDirPrefix returns the path to the folder meant for tex files. func GetTexDir() string { return outPrefix + texDir diff --git a/report/report_base.tmpl b/report/report_base.tmpl index 6676172..d40327b 100644 --- a/report/report_base.tmpl +++ b/report/report_base.tmpl @@ -7,11 +7,14 @@ left=12mm, right=12mm, } +% \usepackage{lmodern} \usepackage[utf8]{inputenc} \usepackage[T1]{fontenc} \usepackage[fleqn]{amsmath} \usepackage{amssymb} \usepackage{amsfonts} +% \usepackage{fontspec} +% \usefonttheme[onlymath]{serif} \usepackage{multirow} \usepackage{graphicx} \usepackage{textcomp} @@ -24,6 +27,7 @@ % inkscapelatex=false is important to not have jumbled plot labels as a result of latex trying to render plot/axis labels with the default latex fonts. \svgsetup{inkscapelatex=false,clean=true,inkscapepath=svgsubdir} \pdfsuppresswarningpagegroup=1 % pdflatex complains svg-turned-pdf files +\pdfinclusioncopyfonts=1 \usepackage{meta} \usepackage[affil-it]{authblk} diff --git a/report/table.tmpl b/report/table.tmpl index 49588d7..aaf27c5 100644 --- a/report/table.tmpl +++ b/report/table.tmpl @@ -10,6 +10,7 @@ \subsection{ {{- .Algo -}} } \begin{table}[!htb] + % \begin{tabular}[t]{ |l|{{- range $i, $v := .ColLayout }}{{$v}}| {{- end}} } \resizebox{\columnwidth}{!}{\begin{tabular}[t]{r||{{- range $i, $v := .ColLayout }}{{$v}}| {{- end -}} } \textbf{params} & {{ range $i, $v := .ColNames }}{{ printf "\\textbf{%s}" $v }} & {{ end}}\\ {{- range $j, $v := .Rs }} diff --git a/run.go b/run.go index fb54f7d..6d29c98 100644 --- a/run.go +++ b/run.go @@ -19,6 +19,7 @@ func run() { doPrint := flag.Bool("printreport", true, "print report.tex to console") generate := flag.Bool("generate", true, "run algos and generate plot pics/statistical tables (anew)") + // TODO(me): add flag for plot output format: -plotout=(svg,eps,pdf) flag.Parse() if *generate { @@ -33,6 +34,7 @@ func run() { go algo.DoRandomSearch(&wg, &m) go algo.DoStochasticHillClimbing(&wg, &m) + // go algo.DoStochasticHillClimbing100Neigh(&wg, &m) wg.Wait()