math-optim/algo/plot.go

251 lines
6.1 KiB
Go
Raw Normal View History

2022-06-18 05:27:10 +02:00
// Copyright 2022 wanderer <a_mirre at utb dot cz>
// SPDX-License-Identifier: GPL-3.0-or-later
package algo
import (
"fmt"
"log"
"strings"
"time"
"git.dotya.ml/wanderer/math-optim/report"
"git.dotya.ml/wanderer/math-optim/stats"
"git.dotya.ml/wanderer/math-optim/util"
"gonum.org/v1/gonum/floats"
"gonum.org/v1/plot"
"gonum.org/v1/plot/plotter"
"gonum.org/v1/plot/plotutil"
"gonum.org/v1/plot/vg"
)
const (
preferredFont = "Mono"
titlePreferredFont = "Sans"
yAxisLabel = "CF value"
xAxisLabel = "Generations"
)
func plotMeanVals(meanVals []float64, title string, fes int) *plot.Plot {
plotter.DefaultFont.Typeface = preferredFont
2022-07-18 22:36:47 +02:00
plotter.DefaultLineStyle.Width = vg.Points(2.0)
2022-07-18 10:08:12 +02:00
2022-08-03 17:20:49 +02:00
if fes != len(meanVals) {
log.Fatalf("meanVals - FES mismatch: %d vs %d , bailing\n", fes, len(meanVals))
}
2022-07-18 10:08:12 +02:00
p := plot.New()
2022-07-18 22:36:47 +02:00
p.Title.Text = "Mean - " + title
2022-07-18 10:08:12 +02:00
p.X.Label.Text = xAxisLabel
p.X.Label.TextStyle.Font.Variant = preferredFont
2022-07-18 10:08:12 +02:00
p.X.Label.TextStyle.Font.Weight = 1 // Medium
p.X.Tick.Label.Font.Variant = preferredFont
p.Y.Label.Text = yAxisLabel
p.Y.Label.TextStyle.Font.Variant = preferredFont
2022-07-18 10:08:12 +02:00
p.Y.Label.TextStyle.Font.Weight = 1 // Medium
p.Y.Tick.Label.Font.Variant = preferredFont
2022-07-18 10:08:12 +02:00
2022-07-18 22:36:47 +02:00
p.Title.TextStyle.Font.Size = 14.5
p.Title.TextStyle.Font.Variant = titlePreferredFont
2022-07-18 10:08:12 +02:00
p.Title.TextStyle.Font.Weight = 2 // SemiBold
2022-07-18 22:36:47 +02:00
p.Title.Padding = 3 * vg.Millimeter
2022-07-18 10:08:12 +02:00
// mark the end of the X axis with len(meanVals).
p.X.Max = float64(len(meanVals))
p.Y.Min = floats.Min(meanVals)
p.Y.Max = floats.Max(meanVals)
pts := make(plotter.XYs, len(meanVals))
// fill the plotter with datapoints.
for k, res := range meanVals {
pts[k].X = float64(k)
pts[k].Y = res
}
lines := make([]interface{}, 0)
lines = append(lines, pts)
err := plotutil.AddLines(
p,
lines...,
)
if err != nil {
log.Panic(err)
}
return p
}
// violating gocognit 30, TODO(me): still split this up.
// nolint: gocognit
2022-07-18 22:36:47 +02:00
func plotAllDims(algoStats []stats.Stats, fPrefix, fExt string, ch chan report.PicList, chMean chan report.PicList) {
start := time.Now()
picsDir := report.GetPicsDir()
// create picsDir (if not exists), fail on error, no point in going on
// parsing algoStats and computing images if we cannot save them.
if err := util.CreatePath(picsDir); err != nil {
log.Fatalln("went to create picsDir, there was an issue: ", err)
}
pL := report.NewPicList()
pics := make([]report.Pic, 0)
2022-07-18 22:36:47 +02:00
pLMean := report.NewPicList()
picsMean := make([]report.Pic, 0)
// since the algoStats only contains results of a single algo, it's safe to
// set the value like this.
pL.Algo = algoStats[0].Algo
2022-07-18 22:36:47 +02:00
pLMean.Algo = algoStats[0].Algo
pWidth := 13 * vg.Centimeter
pHeight := 13 * vg.Centimeter
plotter.DefaultFont.Typeface = preferredFont
plotter.DefaultLineStyle.Width = vg.Points(1.5)
2022-07-08 18:16:51 +02:00
for _, s := range algoStats {
p := plot.New()
pic := report.NewPic()
p.Title.Text = fmt.Sprintf("D: %d, G: %d, I: %d",
s.Dimens,
s.Generations,
s.Iterations,
)
// this is latex-rendered.
pic.Caption = strings.ReplaceAll(p.Title.Text, " ", "~")
// since a single stat slice of algoStats only contains results of a
// single bench func, it's safe to set the value like this.
pL.Bench = s.BenchFuncStats[0].BenchName
2022-07-18 22:36:47 +02:00
pLMean.Bench = s.BenchFuncStats[0].BenchName
p.X.Label.Text = xAxisLabel
p.X.Label.TextStyle.Font.Variant = preferredFont
p.X.Label.TextStyle.Font.Weight = 1 // Medium
p.X.Tick.Label.Font.Variant = preferredFont
p.Y.Label.Text = yAxisLabel
p.Y.Label.TextStyle.Font.Variant = preferredFont
p.Y.Label.TextStyle.Font.Weight = 1 // Medium
p.Y.Tick.Label.Font.Variant = preferredFont
2022-07-18 22:36:47 +02:00
p.Title.TextStyle.Font.Size = 14.5
p.Title.TextStyle.Font.Variant = titlePreferredFont
p.Title.TextStyle.Font.Weight = 2 // SemiBold
2022-07-18 22:36:47 +02:00
p.Title.Padding = 3 * vg.Millimeter
for _, dim := range s.BenchFuncStats {
// infinite thanks to this SO comment for the interface "hack":
// https://stackoverflow.com/a/44872993
lines := make([]interface{}, 0)
for _, iter := range dim.BenchResults {
// mark the end of the X axis with len(iter.Results).
p.X.Max = float64(len(iter.Results))
if floats.Min(iter.Results) < p.Y.Min {
p.Y.Min = floats.Min(iter.Results)
}
if floats.Max(iter.Results) > p.Y.Max {
p.Y.Max = floats.Max(iter.Results)
}
pts := make(plotter.XYs, len(iter.Results))
// fill the plotter with datapoints.
for k, res := range iter.Results {
pts[k].X = float64(k)
pts[k].Y = res
}
lines = append(lines, pts)
}
err := plotutil.AddLines(
p,
lines...,
)
if err != nil {
2022-07-08 22:51:31 +02:00
// panic (don't panic, I know) instead of a hard exit.
log.Panic(err)
}
filename := fmt.Sprintf("%s%s-%s-%s-%dD-%dG-%dI",
picsDir,
fPrefix,
util.SanitiseFName(s.Algo),
util.SanitiseFName(dim.BenchName),
s.Dimens,
s.Generations,
len(dim.BenchResults),
)
2022-07-18 10:08:12 +02:00
filenameMean := filename + util.SanitiseFName(" Mean")
// NEVER EVER ATTEMPT TO INITIALISE THIS WITH `pic`!
picMean := report.NewPic()
meanTitle := fmt.Sprintf("D: %d, G: %d, I: %d",
s.Dimens,
s.Generations,
s.Iterations,
)
// this is latex-rendered.
picMean.Caption = "Mean - " + strings.ReplaceAll(meanTitle, " ", "~")
// set pic file path (later used in tmpl generation)
pic.FilePath = filename
2022-07-18 10:08:12 +02:00
picMean.FilePath = filenameMean
2022-07-18 22:36:47 +02:00
// get the *mean* plot.
pMean := plotMeanVals(dim.MeanVals, meanTitle, s.Generations)
elapsed := time.Since(start)
info := fmt.Sprintf("saving img to file: %s(-Mean)%s [generated in %s]",
filename, fExt, elapsed,
)
if s.Algo == "Random Search" {
printRandomSearch(info)
} else {
printSHC(info)
}
// Save the plot to a file using the above-constructed 'filename'.
if err := p.Save(
pWidth,
pHeight,
filename+fExt,
); err != nil {
panic(err)
}
// save pic.
pics = append(pics, *pic)
2022-07-18 10:08:12 +02:00
// save plot of pMean.
if err := pMean.Save(
pWidth,
pHeight,
filenameMean+fExt,
); err != nil {
panic(err)
}
2022-07-18 22:36:47 +02:00
// save mean pic.
picsMean = append(picsMean, *picMean)
}
}
pL.Pics = pics
2022-07-18 22:36:47 +02:00
pLMean.Pics = picsMean
ch <- *pL
2022-07-18 22:36:47 +02:00
chMean <- *pLMean
}