Skip to content
Snippets Groups Projects
Commit f3190b0f authored by Andrew E. Torda's avatar Andrew E. Torda
Browse files

Plotting of function value works well enough. Should add plotting of

temperature and x-values.
parent 0bf01ed8
No related branches found
No related tags found
No related merge requests found
......@@ -20,6 +20,7 @@ type mcPrm struct {
nRun uint32 // number of separate runs
nOutput uint32 // write at least this many lines of output
fOutName string // where we write output to
pOutName string // where we plot to
verbose bool
dummy bool // for testing. If set, do not do a run
}
......
......@@ -58,6 +58,7 @@ ini_temp 0.1
final_temp 0.02
x_ini 7,7,7,7,7,7,7,7,7,7
n_step 10000000
n_step 10
n_output 10000
f_outName testanneal10d`
......@@ -105,13 +106,52 @@ n_step 100000
n_output 50000
f_outName testphase`
var plottable_test = []string{
var csv_test = []string{
s1d, s3d, sHot, sCold, sRealAnneal, sPhaseTrans, s5d, s10d}
func TestPlottable(t *testing.T) {
for _, s := range plottable_test {
func TestCsv(t *testing.T) {
for _, s := range csv_test {
if err := realmain(strings.NewReader(s)); err != nil {
t.Fatal("plottable failed with\n", err, "\nInput", s)
t.Fatal("csv failed with\n", err, "\nInput", s)
}
}
}
var s1dplot = `
ini_temp 1
final_temp 0.05
x_ini 15
n_step 10000
n_output 5000
f_outName testanneal1d
plot_outName testplot.svg`
var plotTest = []string{
s1dplot,
}
func TestPlot(t *testing.T) {
for _, s := range plotTest {
if err := realmain(strings.NewReader(s)); err != nil {
t.Fatal("plot test failed with\n", err, "\nInput", s)
}
}
}
func TestMakepngName(t *testing.T) {
var tdata = []struct {
in string
out string
}{
{"boo", "boo.png"},
{"boo.", "boo.png"},
{"a.png", "a.png"},
{"/boo/foo/a.PNG", "/boo/foo/a.PNG"},
{"a/b/c/d.svg", "a/b/c/d.png"},
}
for _, s := range tdata {
if tmp := MakepngName (s.in); tmp != s.out {
t.Fatal ("Wanted", s.out, "got", tmp)
}
}
}
......@@ -28,8 +28,12 @@ type cprm struct { // parameters calculated from input
rand *rand.Rand
coolMult float64 // multiplier for temperature
coolme bool // Are we cooling or just doing constant temperature ?
plotme bool // Are we making output for plotting
nEvery uint32 // Output every nEvery steps
fOut io.WriteCloser
plotOut io.WriteCloser
plotx []float64 // Why float 64 ? Because the plotting libraries
ploty []float64 // generally want this
}
// setupRun does things like get the cooling rate, seed the random numbers.
......@@ -62,7 +66,16 @@ func setupRun(mcPrm *mcPrm, cprm *cprm) error {
if cprm.fOut, err = os.Create(ntmp); err != nil {
return err
}
if mcPrm.pOutName != "" {
mcPrm.pOutName = makepngName(mcPrm.pOutName)
if cprm.plotOut, err = os.Create(mcPrm.pOutName); err != nil {
return err
}
cprm.plotme = true
n_alloc := mcPrm.nStep / 5 // About a fifth of the number of steps
cprm.plotx = make([]float64, 0, n_alloc)
cprm.ploty = make([]float64, 0, n_alloc)
}
return nil
}
......@@ -76,6 +89,18 @@ func newx(xold []float32, xT []float32, rand *rand.Rand, xDlta float32) {
}
}
func breaker() {}
// plotPnt adds a point to the data to be plotted. At the start, we check
// if we have to plot at all. This is done here, to make the main loop
// below a bit shorter.
func plotPnt(n uint32, fTrial float64, plotx, ploty *[]float64, plotme bool) {
if !plotme {
return
}
*plotx = append(*plotx, float64(n))
*ploty = append(*ploty, float64(fTrial))
}
// doRun does a Monte Carlo run. Although single precision is fine for the
// coordinates and function, we use double precision for the temperature.
func doRun(mcPrm *mcPrm) error {
......@@ -86,6 +111,9 @@ func doRun(mcPrm *mcPrm) error {
if cprm.fOut != nil {
defer cprm.fOut.Close()
}
if cprm.plotOut != nil {
defer cprm.plotOut.Close()
}
const accRateIdeal = 0.05 // Arbitrary target of 5% acceptance
const nRunAccAdj = 200 // every n steps, check acceptance rate
const maxxDlta = 2. // max step size
......@@ -100,6 +128,7 @@ func doRun(mcPrm *mcPrm) error {
nRunAcc := nRunAccAdj // Every nRunAccAdj, try adjusting the step size.
const runMult = 0.99
xDlta := mcPrm.xDlta // Adaptable step size
plotPnt(0, fOld, &cprm.plotx, &cprm.ploty, cprm.plotme)
for n := uint32(0); n < mcPrm.nStep; n++ {
var acc bool
nout--
......@@ -145,6 +174,7 @@ func doRun(mcPrm *mcPrm) error {
runAcc = runMult*runAcc + (1.0 - runMult)
copy(x, xT)
fOld = fTrial
plotPnt(n+1, fTrial, &cprm.plotx, &cprm.ploty, cprm.plotme)
} else { // update the running estimate of acceptance
runAcc = runMult * runAcc
}
......@@ -152,6 +182,7 @@ func doRun(mcPrm *mcPrm) error {
tmprtr *= cprm.coolMult
}
}
err := plotWrite(cprm.plotx, cprm.ploty, cprm.plotOut, cprm.plotme)
fmt.Println("n accepted:", nAcc, "of", mcPrm.nStep+1)
return nil
return err
}
package ackwork
var MakepngName = makepngName
// 31 Dec 2021
// This is for doing some plotting. For the moment, this is in the ackwork package,
// but maybe it should be in its own. It definitely goes in its own file, since
// it is tied to one of the chart packages I have tried out.
package ackwork
import (
"fmt"
"io"
"math"
"path/filepath"
"github.com/wcharczuk/go-chart/v2"
"github.com/wcharczuk/go-chart/v2/drawing"
"gitlab.rrz.uni-hamburg.de/Bae5157/axticks"
)
// makepngName takes a name. If it does not end in png, append .png to the name.
// If it ends in something else, replace the ending with png.
// We have stalinistically decided that we will only write png output.
// svg is nicer, but the files can become silly big.
func makepngName(fname string) string {
const dotpng = ".png"
ext := filepath.Ext(fname)
switch ext {
case "":
return fname + dotpng
case ".png":
fallthrough
case ".PNG":
return fname
default:
return fname[0:len(fname)-len(ext)] + dotpng
}
return "hello"
}
// maketicks gives reasonable default tick locations
func maketicks(axisDscrpt axticks.AxisDscrpt, prcsn int) []chart.Tick {
xmin, delta, prcsn := axisDscrpt.Xmin, axisDscrpt.Delta, axisDscrpt.Prcsn
rnge := axisDscrpt.Xmax - axisDscrpt.Xmin
ntick := int(math.Round((rnge / delta) + 1))
t := make([]chart.Tick, ntick)
const fstr = "%.*f"
t[0].Value, t[0].Label = xmin, fmt.Sprintf(fstr, prcsn, xmin)
for i := 1; i < ntick; i++ {
t[i].Value = t[i-1].Value + delta
t[i].Label = fmt.Sprintf("%.*f", prcsn, t[i].Value)
}
return t
}
// plotWrt writes out a plot to the given io.writercloser and
// closes the file when finished.
func plotWrite(xdata, ydata []float64, plotOut io.WriteCloser, plotme bool) error {
if !plotme {
return nil
}
var xAxisDscrpt, yAxisDscrpt axticks.AxisDscrpt
var err error
if yAxisDscrpt, err = axticks.Tickpos(ydata); err != nil {
return fmt.Errorf("plot making y axis: %w", err)
}
if xAxisDscrpt, err = axticks.Tickpos(xdata); err != nil {
return fmt.Errorf("plot making x axis: %w", err)
}
xaxis := chart.XAxis{
Name: "step",
Range: &chart.ContinuousRange{
Min: xAxisDscrpt.Xmin,
Max: xAxisDscrpt.Xmax,
},
Ticks: maketicks(xAxisDscrpt, yAxisDscrpt.Prcsn),
}
yaxis := chart.YAxis{
Name: "cost (arb units)",
NameStyle: chart.Style{
TextRotationDegrees: 360}, // Zero does not work
TickStyle: chart.Style{
TextRotationDegrees: 0,
},
AxisType: chart.YAxisSecondary,
Range: &chart.ContinuousRange{
Min: yAxisDscrpt.Xmin,
Max: yAxisDscrpt.Xmax,
},
Ticks: maketicks(yAxisDscrpt, yAxisDscrpt.Prcsn),
}
graph := chart.Chart{
Width: 800,
Series: []chart.Series{
chart.ContinuousSeries{
XValues: xdata,
YValues: ydata,
Style: chart.Style{
StrokeWidth: 5,
DotWidth: 0,
},
},
},
YAxis: yaxis,
XAxis: xaxis,
Background: chart.Style{
Padding: chart.Box{
Left: 75,
Right: 25,
},
FillColor: drawing.ColorTransparent,
},
}
if err := graph.Render(chart.PNG, plotOut); err != nil {
return fmt.Errorf("While plotting %w", err)
}
return nil
}
......@@ -30,6 +30,7 @@ var cmdDflt = []struct {
{"x_delta", "0.5"},
{"seed", "1637"},
{"f_outname", "mcrun"},
{"plot_outname", ""}, // empty means no plots
{"verbose", ""},
{"dummy", ""},
}
......@@ -81,6 +82,7 @@ func digest(prmMap map[string]string, mcPrm *mcPrm) error {
mcPrm.nOutput = getu(prmMap["n_output"])
mcPrm.xIni = getx(prmMap["x_ini"])
mcPrm.fOutName = prmMap["f_outname"]
mcPrm.pOutName = prmMap["plot_outname"]
seed = int64(getu(prmMap["seed"]))
if err != nil { // Only returns the first error encountered
return err
......
doc.go 0 → 100644
// Dec 2021
// ackley is for teaching. We use Monte Carlo to optimise Ackley's function
// in N dimensions.
// To run it
// Ackley input_file
// where input_file is a set of name-value pairs. These are defined with defaults
// in rdprm.go. At the moment, we have
/*
{"ini_temp", "20"},
{"final_temp", "1"},
{"n_step", "1000"},
{"n_run", "1"},
{"n_output", "500"},
{"x_ini", "3,4,5"},
{"x_delta", "0.5"},
{"seed", "1637"},
{"f_outname", "mcrun"},
{"verbose", ""},
{"dummy", ""},
*/
module ackley_mc
go 1.16
require (
github.com/wcharczuk/go-chart/v2 v2.1.0
gitlab.rrz.uni-hamburg.de/Bae5157/axticks v0.0.0-20211227145643-dc5ef95d7dad
)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment