Skip to content
Snippets Groups Projects
Select Git revision
  • 7df42b8238933a8f694153a91e6efdd416f3b70b
  • master default protected
  • devel
  • adaptive_step_size
4 results

dorun.go

Blame
  • Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    dorun.go 5.37 KiB
    // Aug 2021
    package ackwork
    
    import (
    	"fmt"
    	"io"
    	"math"
    	"math/rand"
    	"os"
    	"sync"
    
    	"ackley_mc/ackwork/ackley"
    )
    
    var seedLocker sync.Mutex
    
    // getSeed returns the random number seed, but we have to put a lock
    // around it so multiple runs get consistent successive seeds
    func getSeed() int64 {
    	seedLocker.Lock()
    	r := seed
    	seed++
    	seedLocker.Unlock()
    	return r
    }
    
    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.
    func setupRun(mcPrm *mcPrm, cprm *cprm) error {
    	var err error
    	if mcPrm.dummy == true {
    		return nil
    	}
    	cprm.rand = rand.New(rand.NewSource(getSeed()))
    
    	if mcPrm.iniTmp != mcPrm.fnlTmp { // cooling or constant temperature ?
    		var coolrate float64
    		cprm.coolme = true
    		if mcPrm.fnlTmp > mcPrm.iniTmp {
    			return fmt.Errorf("Final temp %g higher than initial temp %g", mcPrm.fnlTmp, mcPrm.iniTmp)
    		}
    		nStep := float64(mcPrm.nStep)
    		fnlTmp, iniTmp := float64(mcPrm.fnlTmp), float64(mcPrm.iniTmp)
    		if fnlTmp == 0 {
    			fnlTmp = math.SmallestNonzeroFloat32
    		}
    		coolrate = -1 / nStep
    		coolrate *= math.Log(fnlTmp / iniTmp)
    		cprm.coolMult = math.Exp(-coolrate)
    	}
    
    	cprm.nEvery = uint32(math.Ceil(float64(mcPrm.nStep) / float64(mcPrm.nOutput)))
    	// change the next line to handle the case of multiple runs
    	ntmp := mcPrm.fOutName + ".csv"
    	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
    }
    
    // newx gets a candidate x slice. We have n dimensions, so a candidate
    // move is a slice of length n.
    func newx(xold []float32, xT []float32, rand *rand.Rand, xDlta float32) {
    	for i, x := range xold {
    		t := 2.*rand.Float32() - 1
    		t *= xDlta
    		xT[i] = x + t
    	}
    }
    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 {
    	var cprm cprm
    	if err := setupRun(mcPrm, &cprm); err != nil {
    		return err
    	}
    	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
    	var nAcc int                           // Counter of number of accepted moves
    	x := make([]float32, len(mcPrm.xIni))  // current position
    	xT := make([]float32, len(mcPrm.xIni)) // trial position
    	nout := uint32(1)                      // counter for printing output
    	runAcc := accRateIdeal                 // Running acceptance rate, exponentially weighted
    	copy(x, mcPrm.xIni)
    	fOld := ackley.Ackley(x) // Initial function value
    	tmprtr := float64(mcPrm.iniTmp)
    	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--
    		if nout == 0 { // Do we want to print out results on this step ?
    			fmt.Fprintf(cprm.fOut, "%d,%.4g,%.5g", n, tmprtr, fOld)
    			for _, xx := range x {
    				fmt.Fprintf(cprm.fOut, ",%.2g", xx)
    			}
    			fmt.Fprintln(cprm.fOut)
    			nout = cprm.nEvery
    		}
    		nRunAcc--
    		if nRunAcc == 0 { // Do we want to try adjusting the step size ?
    			const step = "step"
    			if runAcc < accRateIdeal-0.01 { // acceptance rate too low
    				xDlta *= 0.9
    				if mcPrm.verbose {
    					fmt.Println(step, xDlta, "decrease")
    				}
    			} else if (xDlta < maxxDlta) && (runAcc > accRateIdeal+0.01) {
    				xDlta *= 1.1
    				if mcPrm.verbose {
    					fmt.Println(step, xDlta, "increase")
    				}
    			}
    			nRunAcc = nRunAccAdj
    		}
    		newx(x, xT, cprm.rand, xDlta)
    		fTrial := ackley.Ackley(xT)
    
    		if fOld < fTrial {
    			r := cprm.rand.Float64()
    			delta := fTrial - fOld
    			t := math.Exp(-delta / tmprtr)
    			if r < t {
    				acc = true
    			}
    		} else {
    			acc = true
    		}
    		if acc == true {
    			nAcc++
    			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
    		}
    		if cprm.coolme {
    			tmprtr *= cprm.coolMult
    		}
    	}
    	err := plotWrite(cprm.plotx, cprm.ploty, cprm.plotOut, cprm.plotme)
    	fmt.Println("n accepted:", nAcc, "of", mcPrm.nStep+1)
    	return err
    }