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

input_tab.go

Blame
  • user avatar
    Andrew E. Torda authored
    14bb4804
    History
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    input_tab.go 6.35 KiB
    // Feb 2022
    // Set up a tab for getting Monte Carlo Parameters
    // This should return an object that can be embedded in a tabbed page.
    //go:build !no_gfx
    // +build !no_gfx
    
    package ui
    
    import (
    	"fmt"
    	"os"
    	"strconv"
    	"strings"
    
    	"fyne.io/fyne/v2"
    	"fyne.io/fyne/v2/data/binding"
    	"fyne.io/fyne/v2/dialog"
    	"fyne.io/fyne/v2/storage"
    	"fyne.io/fyne/v2/widget"
    
    	"example.com/ackley_mc/mc_work"
    )
    
    // startrun is what happens when you click on the form's button to start
    // a calculation.
    func startrun(genParams genParams, form *widget.Form, mcPrm *mcwork.McPrm) {
    	form.Disable()
    	go mcWrap(genParams)
    }
    
    // fslicestrng makes a nice string from a slice of float32's
    func fslicestrng(f []float32) string {
    	if len(f) == 0 {
    		return ""
    	}
    	s := fmt.Sprintf("%g", f[0])
    	for i := 1; i < len(f); i++ {
    		s += fmt.Sprintf(", %g", f[i])
    	}
    	return s
    }
    
    // str2f32 takes a string and returns a slice of float32's
    func s2f32(s string) ([]float32, error) {
    	y := strings.Split(s, ",")
    	if len(y) < 1 {
    		return nil, fmt.Errorf("no fields found")
    	}
    	x := make([]float32, len(y))
    	for i, s := range y {
    		if r, err := strconv.ParseFloat(strings.TrimSpace(s), 32); err != nil {
    			return nil, fmt.Errorf("tried to parse %v %v", s, err)
    		} else {
    			x[i] = float32(r)
    		}
    	}
    	return x, nil
    }
    
    // validateXini checks if we can convert a string to a slice of float32's.
    // Unfortunately, fyne calls this function every time the cursor is moved
    // in the field. I think this is a little bug on their part.
    func validateXini(s string) error {
    	_, err := s2f32(s)
    	return err
    }
    
    // floatItem gives us a form item, but also a function to refresh the item.
    // This gets put on the list of things to do from the reload function.
    func floatItem(xAddr *float64, label string) (*widget.FormItem, func()) {
    	bind := binding.BindFloat(xAddr)
    	entry := widget.NewEntryWithData(binding.FloatToStringWithFormat(bind, "%.3f"))
    	fitem := widget.NewFormItem(label, entry)
    	reloadfunc := func() { _ = bind.Reload() }
    	return fitem, reloadfunc
    }
    
    // intItem does the same wrapping as floatItem, but for ints
    func intItem(xAddr *int, label string, htext string) (*widget.FormItem, func()) {
    	bind := binding.BindInt(xAddr)
    	entry := widget.NewEntryWithData(binding.IntToString(bind))
    	iItem := widget.NewFormItem(label, entry)
    	iItem.HintText = htext
    	reloadfunc := func() { _ = bind.Reload() }
    	return iItem, reloadfunc
    }
    
    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
    // a file, which means all the values should be updated.
    func paramForm(genParams genParams) *widget.Form {
    	mcPrm := genParams.mcPrm
    
    	iniTmpItem, iniTmpReload := floatItem(&mcPrm.IniTmp, "initial temperature")
    	fnlTmpItem, fnlTmpReload := floatItem(&mcPrm.FnlTmp, "final temperature")
    	xDltaItem, xDltaReload := floatItem(&mcPrm.XDlta, "X delta")
    	xDltaItem.HintText = "Maximum step size in one dimension"
    	xIniStr := binding.NewString()
    	_ = xIniStr.Set(fslicestrng(mcPrm.XIni))
    	xIniEntry := widget.NewEntryWithData(xIniStr)
    	xIniEntry.OnChanged = func(s string) {
    		x, err := s2f32(s)
    		if err != nil {
    			xIniEntry.SetValidationError(err)
    		} else {
    			mcPrm.XIni = append(mcPrm.XIni[:0], x...)
    		}
    	}
    	xIniEntry.Validator = validateXini
    	xIniItem := widget.NewFormItem("initial X", xIniEntry)
    	xIniItem.HintText = xCoordHint
    
    	nStepItem, nStepReload := intItem(&mcPrm.NStep, "N steps", "number of steps in calculation")
    	nEqulItem, nEqulReload := intItem(&mcPrm.NEquil, "N equilibration", "number of steps for equilibration")
    	seedItem, seedReload := intItem(&mcwork.Seed, "seed random numbers", "seed for random number generator")
    
    	reloadPTab := func() {
    		iniTmpReload() // The values linked to in the the various fields can
    		fnlTmpReload() // change outside of this function, so reloadPTab
    		xDltaReload()  // is called to refresh all the individual elements.
    		xIniEntry.SetText(fslicestrng(mcPrm.XIni))
    		nStepReload()
    		nEqulReload()
    		seedReload()
    	}
    
    	rdfile := func() { rdwork(genParams, reloadPTab) }
    	rdfileBtn := widget.NewButton("get file       ", rdfile)
    	rdFileItem := widget.NewFormItem("read from a file", rdfileBtn)
    	// puke-oh-rama, Can we tidy up the next three lines ?
    	r := widget.NewForm( // Lays out the items in the form
    		iniTmpItem, fnlTmpItem, xDltaItem, xIniItem,
    		nStepItem, nEqulItem, seedItem, 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
    	return r
    }
    
    // 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
    	mcPrm := genParams.mcPrm
    	if err != nil {
    		dialog.NewError(err, win).Show()
    		return
    	}
    	if rd == nil { // cancelled
    		return
    	}
    	defer rd.Close()
    	if err := mcwork.RdPrm(rd, mcPrm); err != nil {
    		dialog.NewError(err, win).Show()
    		return
    	} else {
    		dialog.NewInformation("OK", "read file, no errors", win).Show()
    		reload()
    		return
    	}
    }
    
    // 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)
    	}
    	t := dialog.NewFileOpen(getter, genParams.win)
    
    	if cwd, err := os.Getwd(); err == nil {
    		if y, err := storage.ListerForURI(storage.NewFileURI(cwd)); err == nil {
    			t.SetLocation(y) // If there was an error, we just use default location
    		}
    	}
    	t.Show() // set an error and see if anybody notices
    }
    
    // mcWrap is run in the background so the interface does not block.
    // It runs the Monte Carlo, then when it is finished, sends a message
    // down the channel.
    func mcWrap(genParams genParams) {
    	chn := genParams.chn
    	chn <- workstatus{status: calculating}
    	if result, err := mcwork.DoRun(genParams.mcPrm); err != nil {
    		dialog.NewError(err, genParams.win).Show()
    		chn <- workstatus{status: errorCalc, err: err}
    	} else {
    		chn <- workstatus{MCresult: result, status: resultsReady}
    	}
    }
    
    // inputTab sets up the input page.
    func inputTab(genParams genParams) *widget.Form {
    	return paramForm(genParams)
    }