From ac9a0c8cf2f637c1c2900600706e7b8bd49b81b2 Mon Sep 17 00:00:00 2001
From: "Andrew E. Torda" <torda@zbh.uni-hamburg.de>
Date: Fri, 25 Feb 2022 16:48:19 +0100
Subject: [PATCH] Added a switch to turn off adaptive move size.

---
 TODO               |  8 +++-----
 doc.go             |  1 +
 mc_work/dorun.go   | 47 +++++++++++++++++++++++++++-------------------
 mc_work/mc_work.go |  3 ++-
 mc_work/rdprm.go   |  4 ++++
 ui/input_tab.go    | 23 +++++++++++++++--------
 ui/output_tab.go   | 12 ++++++------
 ui/ui_run.go       | 20 ++++++++++----------
 8 files changed, 69 insertions(+), 49 deletions(-)

diff --git a/TODO b/TODO
index 81a4483..c7e1ba1 100644
--- a/TODO
+++ b/TODO
@@ -1,7 +1,5 @@
-- in main MC, add a boolean to make move size adjustment optional. Otherwise the method works too well for an Uebung.
-
-- Add radio button to input tab for adaptive move sizes
-
 - Change the adaptive scheme so it looks more like weak coupling. Scale the adjustment by current rate / desired rate
 
-- add testing to the ui directory
\ No newline at end of file
+- add testing to the ui directory
+
+- Save the best found configuration, print out at end
\ No newline at end of file
diff --git a/doc.go b/doc.go
index a3912c0..2b7977e 100644
--- a/doc.go
+++ b/doc.go
@@ -7,6 +7,7 @@
 // where input_file is a set of name-value pairs. These are defined with defaults
 // in rdprm.go. At the moment, we have
 package main
+
 // BUGS
 // A chart in png format from go-chart is no problem, but a file in svg format causes a parse error in fyne.
 /*
diff --git a/mc_work/dorun.go b/mc_work/dorun.go
index 042d982..0ad6d92 100644
--- a/mc_work/dorun.go
+++ b/mc_work/dorun.go
@@ -189,10 +189,11 @@ func nRunAdj(mcPrm *McPrm, nstep int, runAcc float64, xDlta float64) float64 {
 	return xDlta
 }
 
-// doRun does a Monte Carlo run. Although single precision is fine for the
-// coordinates and function, we use double precision for the temperature.
-// Unfortunately, the plotting functions want double precision, so we store
-// an awful lot of stuff as float64.
+// doRun does a Monte Carlo run.
+// We only need single precision for most things, except for function
+// values, but the interface to the plot and interface packages mostly
+// want double precision, so we have a lot of float64 that shoudl be
+// float32.
 func DoRun(mcPrm *McPrm) (MCresult, error) {
 	var cprm cprm
 	broken := MCresult{}
@@ -204,8 +205,11 @@ func DoRun(mcPrm *McPrm) (MCresult, error) {
 		defer cprm.fOut.Flush()
 	}
 
-	if c, ok := cprm.fplotWrt.(io.Closer); ok { // will also have to do for X values
-		defer c.Close()
+	if c, ok := cprm.fplotWrt.(io.Closer); ok {
+		defer c.Close() // file handle for function values
+	}
+	if c, ok := cprm.xplotWrt.(io.Closer); ok {
+		defer c.Close() // file handle for X trajectories
 	}
 
 	var nAcc int                           // Counter, number of accepted moves
@@ -213,32 +217,33 @@ func DoRun(mcPrm *McPrm) (MCresult, error) {
 	xT := make([]float32, len(mcPrm.XIni)) // trial position
 	runAcc := accRateIdeal                 // Running acceptance rate, exponentially weighted
 
-	for i := range mcPrm.XIni {
-		x[i] = float32(mcPrm.XIni[i])
+	for i := range mcPrm.XIni { // Initial coordinates from
+		x[i] = float32(mcPrm.XIni[i]) // the control structure
 	}
 
-	//	copy(x, mcPrm.XIni) I had to replace this with the loop above
 	fOld := ackley.Ackley(x) // Initial function value
 	fTrial := fOld
 	tmprtr := float64(mcPrm.IniTmp)
 	nRunAcc := nRunAccAdj // Every nRunAccAdj, try adjusting the step size.
 	const runMult = 0.99
-	xDlta := mcPrm.XDlta // Adaptable step size
+	xDlta := mcPrm.XDlta // Step size which might be adjusted on the fly
 	saveStep(&cprm, 0, tmprtr, x, fOld)
 
 	for n := 0; n < mcPrm.NStep; n++ {
 		var accept bool
-		nRunAcc--
-		if nRunAcc == 0 { // Do we want to try adjusting the step size ?
-			xDlta = nRunAdj(mcPrm, n, runAcc, xDlta)
-			nRunAcc = nRunAccAdj // Reset the counter
+		if mcPrm.AdaptStep {
+			nRunAcc--
+			if nRunAcc == 0 { // Do we want to try adjusting the step size ?
+				xDlta = nRunAdj(mcPrm, n, runAcc, xDlta)
+				nRunAcc = nRunAccAdj // Reset the counter
+			}
 		}
 		newx(x, xT, cprm.rand, xDlta)
 		fTrial = ackley.Ackley(xT)
 		if fTrial <= fOld {
 			accept = true
 		} else {
-			delta := fTrial - fOld         // Make the decision as to whether to
+			delta := fTrial - fOld         // Decision as to whether to
 			t := math.Exp(-delta / tmprtr) // accept or reject the trial
 			if cprm.rand.Float64() < t {
 				accept = true
@@ -246,12 +251,16 @@ func DoRun(mcPrm *McPrm) (MCresult, error) {
 		}
 		if accept {
 			nAcc++
-			runAcc = runMult*runAcc + (1.0 - runMult)
 			copy(x, xT)
 			fOld = fTrial
 			saveStep(&cprm, n+1, tmprtr, x, fTrial)
-		} else { // update the running estimate of acceptance
-			runAcc = runMult * runAcc
+			if mcPrm.AdaptStep {
+				runAcc = runMult*runAcc + (1.0 - runMult)
+			}
+		} else { // else don't really have to do anything, but update statistics
+			if mcPrm.AdaptStep {
+				runAcc = runMult * runAcc // update the running estimate of acceptance
+			}
 		}
 		if cprm.coolme {
 			tmprtr *= cprm.coolMult
@@ -279,5 +288,5 @@ func DoRun(mcPrm *McPrm) (MCresult, error) {
 		xdata = xBuf.Bytes()
 	}
 
-	return MCresult{ Fdata: fdata, Xdata: xdata, NStep: mcPrm.NStep, NAcc: nAcc}, nil
+	return MCresult{Fdata: fdata, Xdata: xdata, NStep: mcPrm.NStep, NAcc: nAcc}, nil
 }
diff --git a/mc_work/mc_work.go b/mc_work/mc_work.go
index c3dada8..2942105 100644
--- a/mc_work/mc_work.go
+++ b/mc_work/mc_work.go
@@ -15,6 +15,7 @@ type McPrm struct {
 	fOutName       string    // where we write output to
 	fPltName       string    // where we plot to (function values)
 	xPltName       string    // where we plot x trajectories to
+	AdaptStep      bool      // Adaptive step sizes
 	verbose        bool
 	dummy          bool // for testing. If set, do not do a run
 }
@@ -26,5 +27,5 @@ var Seed int
 // For returning results
 type MCresult struct {
 	Fdata, Xdata []byte // png images of function and X data
-	NStep, NAcc int     // number of steps and accepted steps
+	NStep, NAcc  int    // number of steps and accepted steps
 }
diff --git a/mc_work/rdprm.go b/mc_work/rdprm.go
index d6cac58..738cd21 100644
--- a/mc_work/rdprm.go
+++ b/mc_work/rdprm.go
@@ -29,6 +29,7 @@ var cmdDflt = []struct {
 	{"foutname", ""},
 	{"fpltname", ""}, // empty means no plots of function
 	{"xpltname", ""},
+	{"adaptstep", ""},
 	{"verbose", ""},
 	{"dummy", ""},
 }
@@ -88,6 +89,9 @@ func digest(prmMap map[string]string, mcPrm *McPrm) error {
 	if prmMap["verbose"] != "" {
 		mcPrm.verbose = true
 	}
+	if prmMap["adaptstep"] != "" {
+		mcPrm.AdaptStep = true
+	}
 	if prmMap["dummy"] != "" {
 		mcPrm.dummy = true
 	}
diff --git a/ui/input_tab.go b/ui/input_tab.go
index bd934ce..8625f90 100644
--- a/ui/input_tab.go
+++ b/ui/input_tab.go
@@ -11,7 +11,7 @@ import (
 	"os"
 	"strconv"
 	"strings"
-	
+
 	"fyne.io/fyne/v2"
 	"fyne.io/fyne/v2/data/binding"
 	"fyne.io/fyne/v2/dialog"
@@ -86,6 +86,7 @@ func intItem(xAddr *int, label string, htext string) (*widget.FormItem, func())
 }
 
 var xCoordHint = "The starting point. A comma-separated list of floats. It is used to set the dimensionality"
+
 // paramBox sets up a screen full of parameters we can adjust.
 // It returns the screen and a function to be called to refresh it.
 // This is necessary, since there is a button to read parameters from
@@ -114,7 +115,12 @@ func paramForm(genParams genParams) *widget.Form {
 
 	nStepItem, nStepReload := intItem(&mcPrm.NStep, "N steps", "number of steps in calculation")
 	seedItem, seedReload := intItem(&mcwork.Seed, "seed random numbers", "seed for random number generator")
-	
+
+	adaptbind := binding.BindBool(&mcPrm.AdaptStep)
+	adaptEntry := widget.NewCheckWithData("adaptive step size", adaptbind)
+	adaptItem := widget.NewFormItem("fI string", adaptEntry)
+	adaptItem.HintText = "Turn on adaptive moves to try to keep acceptance near 5%"
+	adaptReload := func() { _ = adaptbind.Reload() }
 	reloadPTab := func() {
 		iniTmpReload()
 		fnlTmpReload()
@@ -122,6 +128,7 @@ func paramForm(genParams genParams) *widget.Form {
 		xIniEntry.SetText(fslicestrng(mcPrm.XIni))
 		nStepReload()
 		seedReload()
+		adaptReload()
 	}
 
 	rdfile := func() { rdwork(genParams, reloadPTab) }
@@ -130,7 +137,7 @@ func paramForm(genParams genParams) *widget.Form {
 
 	r := widget.NewForm( // Lays out the items in the form
 		iniTmpItem, fnlTmpItem, xDltaItem, xIniItem,
-		nStepItem, seedItem, rdFileItem)
+		nStepItem, seedItem, adaptItem, rdFileItem)
 	r.SubmitText = "start calculation"
 	r.OnSubmit = func() { startrun(genParams, r, mcPrm) }
 	reloadPTab() // does this help ? sometimes the form pops up with entries not ready
@@ -139,8 +146,8 @@ func paramForm(genParams genParams) *widget.Form {
 
 // getmcprm gets MC parameters from a file. It is called by the file open
 // dialog and is given an already open file handle.
-func getmcprm  (rd fyne.URIReadCloser, err error, genParams genParams, reload func()) {
-	win :=genParams.win
+func getmcprm(rd fyne.URIReadCloser, err error, genParams genParams, reload func()) {
+	win := genParams.win
 	mcPrm := genParams.mcPrm
 	if err != nil {
 		dialog.NewError(err, win).Show()
@@ -163,8 +170,8 @@ func getmcprm  (rd fyne.URIReadCloser, err error, genParams genParams, reload fu
 // rdwork is called after the file open dialog gets "OK"
 //func rdwork(mcPrm *mcwork.McPrm, parent fyne.Window, reload func()) {
 func rdwork(genParams genParams, reload func()) {
-	getter := func (rd fyne.URIReadCloser, err error) {
-		getmcprm (rd, err, genParams, reload)
+	getter := func(rd fyne.URIReadCloser, err error) {
+		getmcprm(rd, err, genParams, reload)
 	}
 	t := dialog.NewFileOpen(getter, genParams.win)
 
@@ -186,7 +193,7 @@ func mcWrap(genParams genParams) {
 		dialog.NewError(err, genParams.win).Show()
 		chn <- workstatus{status: errorCalc, err: err}
 	} else {
-		chn <- workstatus{ MCresult: result, status: resultsReady}
+		chn <- workstatus{MCresult: result, status: resultsReady}
 	}
 }
 
diff --git a/ui/output_tab.go b/ui/output_tab.go
index ddf1a4b..a0eaf74 100644
--- a/ui/output_tab.go
+++ b/ui/output_tab.go
@@ -27,10 +27,10 @@ import (
 )
 
 // runStatTxt should summarise some of the statistics
-func runStatTxt (rslt * mcwork.MCresult) fyne.Widget {
-	txt := fmt.Sprintf ("Num steps %d\nNum accepted %d\nacceptance rate %.1f %%",
-		rslt.NStep, rslt.NAcc, (float32(rslt.NAcc)/float32(rslt.NStep)) * 100.)
-	r := widget.NewLabel( txt)
+func runStatTxt(rslt *mcwork.MCresult) fyne.Widget {
+	txt := fmt.Sprintf("Num steps %d\nNum accepted %d\nacceptance rate %.1f %%",
+		rslt.NStep, rslt.NAcc, (float32(rslt.NAcc)/float32(rslt.NStep))*100.)
+	r := widget.NewLabel(txt)
 	return r
 }
 
@@ -82,7 +82,7 @@ actual X values given to the function`
 
 // This spacer is the simplest way to stick a bit of room in the stucture
 // below. We cheat a bit in that the Hide() is just a noop.
-type spacer struct{widget.Separator }
+type spacer struct{ widget.Separator }
 
 func (spacer) Hide()              {}
 func (spacer) MinSize() fyne.Size { return fyne.Size{Width: 1, Height: 2} }
@@ -141,7 +141,7 @@ func showResultsTab(cntr *fyne.Container, win fyne.Window, rslt mcwork.MCresult)
 	fImage := png2image(rslt.Fdata, "function.png")
 	xImage := png2image(rslt.Xdata, "xdata.png")
 	rbox := container.New(&rbox{}, fImage, xImage)
-	right := container.NewVSplit (rbox, runStatTxt(&rslt))
+	right := container.NewVSplit(rbox, runStatTxt(&rslt))
 	right.SetOffset(0.99)
 	left := leftbar(win, rslt.Fdata, rslt.Xdata)
 
diff --git a/ui/ui_run.go b/ui/ui_run.go
index dfa4be5..1360bd2 100644
--- a/ui/ui_run.go
+++ b/ui/ui_run.go
@@ -16,10 +16,10 @@ import (
 )
 
 // Convenience struct so we do not have perverse long parameter lists
-type genParams struct{
-	mcPrm *mcwork.McPrm  // The MC parameters shared with the main simulation
-	win fyne.Window      // Parent window. Needed for all dialog boxes
-	chn chan workstatus  // To tell the output tab current status
+type genParams struct {
+	mcPrm *mcwork.McPrm   // The MC parameters shared with the main simulation
+	win   fyne.Window     // Parent window. Needed for all dialog boxes
+	chn   chan workstatus // To tell the output tab current status
 }
 
 // We have a channel that sends the status to the output tab.
@@ -33,9 +33,9 @@ const (
 
 type workstatus struct {
 	mcwork.MCresult
-//	fdata, xdata []byte // the data in a function or X value plot
-	err          error
-	status       status // Tells us what we should do now
+	//	fdata, xdata []byte // the data in a function or X value plot
+	err    error
+	status status // Tells us what we should do now
 }
 
 // UiDoRun sets up the screen to do a run. It is effectively a big
@@ -45,7 +45,7 @@ func UiDoRun(mcPrm *mcwork.McPrm) error {
 	win := a.NewWindow("Monte Carlo")
 	chn := make(chan workstatus)
 	defer close(chn)
-	var genParams = genParams {mcPrm, win, chn}
+	var genParams = genParams{mcPrm, win, chn}
 	inputForm := inputTab(genParams)
 	quitbutton := widget.NewButton("click somewhere in here to confirm", a.Quit)
 	cntrOut := container.NewMax()
@@ -59,5 +59,5 @@ func UiDoRun(mcPrm *mcwork.McPrm) error {
 	return nil
 }
 
-func nothing(...interface{}){}
-func breaker(...interface{}){}
+func nothing(...interface{}) {}
+func breaker(...interface{}) {}
-- 
GitLab