Skip to content

Commit

Permalink
Improve optimizer handling
Browse files Browse the repository at this point in the history
- Show when the optimizer is running
- Make it possible to stop the optimizing process
- Increase min. number of optimizer iterations
  • Loading branch information
Dadido3 committed Sep 4, 2021
1 parent a8647cb commit 88c1208
Show file tree
Hide file tree
Showing 8 changed files with 227 additions and 67 deletions.
51 changes: 51 additions & 0 deletions optimizer-component.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Copyright (C) 2021 David Vogel
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package main

import (
"github.com/vugu/vugu"
)

type OptimizerComponent struct {
AttrMap vugu.AttrMap

OptimizerState *OptimizerState
fontAwesomeClass string
}

func (c *OptimizerComponent) Compute(ctx vugu.ComputeCtx) {
if c.OptimizerState.Running() {
// Step through hourglass icons to show optimizer is running.
switch c.fontAwesomeClass {
case "fas fa-hourglass-start":
c.fontAwesomeClass = "fas fa-hourglass-half"
case "fas fa-hourglass-half":
c.fontAwesomeClass = "fas fa-hourglass-end"
default:
c.fontAwesomeClass = "fas fa-hourglass-start"
}
} else {
c.fontAwesomeClass = "fas fa-sync"
}
}

func (c *OptimizerComponent) handleClick(event vugu.DOMEvent) {
if c.OptimizerState.Running() { // Side not: This is not perfectly race condition free, but it doesn't matter here.
c.OptimizerState.Stop()
} else {
c.OptimizerState.Start(event)
}
}
3 changes: 3 additions & 0 deletions optimizer-component.vugu
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<button vg-attr="c.AttrMap" @click="c.handleClick(event)">
<i :class="c.fontAwesomeClass"></i>
</button>
122 changes: 122 additions & 0 deletions optimizer-state.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// Copyright (C) 2021 David Vogel
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package main

import (
"log"
"sync"
"time"

"github.com/vugu/vugu"
)

// OptimizerState stores the state of the optimizer and handles start/stop queries.
type OptimizerState struct {
sync.RWMutex

site *Site
running bool // Optimizer is running.
stopFlag bool // Flag signalling that the optimizer should stop.
}

// Running returns whether the optimizer is running or not.
func (os *OptimizerState) Running() bool {
os.RLock()
defer os.RUnlock()

return os.running
}

func (os *OptimizerState) Start(event vugu.DOMEvent) {
os.Lock()
defer os.Unlock()

// Check if there is already an optimizer running for this site.
if os.running {
return
}
os.running, os.stopFlag = true, false

// Create clone of site to optimize. Also create a pair of tweakables lists.
os.site.RLock()
defer os.site.RUnlock()
siteClone := os.site.Copy()
OriginalTweakables, _ := os.site.GetTweakablesAndResiduals()
CloneTweakables, _ := siteClone.GetTweakablesAndResiduals()

done := make(chan struct{})

// Copy the parameters from siteClone to the original site.
uiSync := func() {
// Lock clone.
siteClone.RLock()
defer siteClone.RUnlock()

// Lock original site/event environment.
event.EventEnv().Lock()
defer event.EventEnv().UnlockRender()

// Overwrite the value of all globalSite tweakables with siteClone tweakables.
for i, tweakable := range CloneTweakables {
OriginalTweakables[i].SetTweakableValue(tweakable.TweakableValue())
}
}

checkStop := func() bool {
os.RLock()
defer os.RUnlock()

return os.stopFlag
}

// Call uiSync every now and then until the optimization is done.
go func() {
ticker := time.NewTicker(250 * time.Millisecond)
defer ticker.Stop()

for {
select {
case <-done:
uiSync()
return
case <-ticker.C:
uiSync()
}
}
}()

// Optimize.
go func() {
err := Optimize(siteClone, checkStop)
if err != nil {
log.Printf("Optimize failed: %v", err)
}

os.Lock()
defer os.Unlock()
os.running = false

// Stop UI updates.
done <- struct{}{}
}()
}

func (os *OptimizerState) Stop() {
os.Lock()
defer os.Unlock()

os.stopFlag = true
}
37 changes: 25 additions & 12 deletions optimizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
package main

import (
"fmt"
"log"
"time"

Expand All @@ -33,14 +34,21 @@ type Residualer interface {
ResidualSqr() float64 // Returns the sum of squared residuals. (Each residual is divided by the accuracy of the measurement device).
}

func Optimize(site *Site) {
func Optimize(site *Site, stopFunc func() bool) error {
tweakables, residuals := site.GetTweakablesAndResiduals()

if len(tweakables) == 0 {
return fmt.Errorf("there are no tweakable variables")
}
if len(tweakables) == 0 {
return fmt.Errorf("there are no residuals to be determined")
}

ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()

// Function to optimize.
f := func(x []float64) float64 {
optimizeFunc := func(x []float64) float64 {
// Do some silly sleep every now and then to prevent the UI from locking up.
// TODO: Remove optimizer sleep once WASM threads are fully supported
select {
Expand All @@ -66,8 +74,18 @@ func Optimize(site *Site) {
return ssr
}

// Function to end the optimization prematurely.
statusFunc := func() (optimize.Status, error) {
if stopFunc() {
return optimize.Success, nil
}

return optimize.NotTerminated, nil
}

p := optimize.Problem{
Func: f,
Func: optimizeFunc,
Status: statusFunc,
}

// Get the initial tweakable variables/parameters.
Expand All @@ -77,15 +95,8 @@ func Optimize(site *Site) {
//init = append(init, rand.Float64())
}

/*res, err := optimize.Minimize(p, init, nil, &optimize.CmaEsChol{InitStepSize: 0.01})
if err != nil {
log.Printf("Optimization failed: %v", err)
}
if err = res.Status.Err(); err != nil {
log.Printf("Optimization status error: %v", err)
}*/

res, err := optimize.Minimize(p, init, &optimize.Settings{Converger: &optimize.FunctionConverge{Absolute: 1e-10, Iterations: 1000}}, &optimize.NelderMead{})
//res, err := optimize.Minimize(p, init, nil, &optimize.CmaEsChol{InitStepSize: 0.01})
res, err := optimize.Minimize(p, init, &optimize.Settings{Converger: &optimize.FunctionConverge{Absolute: 1e-10, Iterations: 100000}}, &optimize.NelderMead{})
if err != nil {
log.Printf("Optimization failed: %v", err)
}
Expand All @@ -99,4 +110,6 @@ func Optimize(site *Site) {
for i, tweakable := range tweakables {
tweakable.SetTweakableValue(res.X[i])
}

return nil
}
52 changes: 0 additions & 52 deletions root.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import (
"encoding/json"
"fmt"
"log"
"time"

"github.com/vugu/vgrouter"
"github.com/vugu/vugu"
Expand All @@ -42,57 +41,6 @@ func (r *Root) handleSidebarClose(event vugu.DOMEvent) {
r.sidebarDisplay = "none"
}

func (r *Root) handleRecalculate(event vugu.DOMEvent) {
// Create clone of site to optimize. Also create a pair of tweakables lists.
globalSite.RLock()
siteClone := globalSite.Copy()
OriginalTweakables, _ := globalSite.GetTweakablesAndResiduals()
CloneTweakables, _ := siteClone.GetTweakablesAndResiduals()
globalSite.RUnlock()

done := make(chan struct{})

// Copy the parameters from siteClone to globalSite.
uiSync := func() {
// Lock clone.
siteClone.RLock()
defer siteClone.RUnlock()

// Lock original site/event environment.
event.EventEnv().Lock()
defer event.EventEnv().UnlockRender()

// Overwrite the value of all globalSite tweakables with siteClone tweakables.
for i, tweakable := range CloneTweakables {
OriginalTweakables[i].SetTweakableValue(tweakable.TweakableValue())
}
}

// Call uiSync every now and then until the optimization is done.
go func() {
ticker := time.NewTicker(250 * time.Millisecond)
defer ticker.Stop()

for {
select {
case <-done:
uiSync()
return
case <-ticker.C:
uiSync()
}
}
}()

// Optimize.
go func() {
Optimize(siteClone)

// Stop UI updates.
done <- struct{}{}
}()
}

func (r *Root) handleDownload(event vugu.DOMEvent) {
data, err := json.MarshalIndent(globalSite, "", "\t")
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions root.vugu
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,10 @@
<button class="w3-bar-item w3-button" @click="c.handleDownload(event)"><i class="far fa-save"></i></button>
<button class="w3-bar-item w3-button" @click="c.handleUploadClick(event)"><i class="far fa-folder-open"></i></button>
<input class="w3-hide" type="file" id="site-upload" @change="c.handleUpload(event)" accept=".D3survey">
<button class="w3-bar-item w3-button" @click="c.handleRecalculate(event)"><i class="fas fa-sync"></i></button>
<main:OptimizerComponent class="w3-bar-item w3-button" :OptimizerState="&globalSite.optimizerState"></main:OptimizerComponent>
<button class="w3-bar-item w3-button" @click="c.handleExport(event)"><i class="fas fa-file-export"></i></button>
</div>

<div class="w3-bar-block">
<button class="w3-bar-item w3-button" @click='c.Navigate("/", nil)'>Overview</button>
<button class="w3-bar-item w3-button" @click='c.Navigate("/points", nil)'>Points</button>
Expand Down
10 changes: 9 additions & 1 deletion site.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ type Site struct {

shortIDGen *shortid.Shortid

optimizerState OptimizerState

Name string

// Geometry data and measurements.
Expand All @@ -57,6 +59,9 @@ func NewSite(name string) (*Site, error) {
Tripods: map[string]*Tripod{},
}

// Restore keys and references.
site.RestoreChildrenRefs()

return site, nil
}

Expand All @@ -82,7 +87,8 @@ func NewSiteFromJSON(data []byte) (*Site, error) {
// Expensive data like images will not be copied, but referenced.
func (s *Site) Copy() *Site {
copy := &Site{
Name: s.Name,
Name: s.Name,
//optimizerState: s.optimizerState.Copy(), // Don't copy optimizer state for now.
Points: map[string]*Point{},
Lines: map[string]*Line{},
Cameras: map[string]*Camera{},
Expand Down Expand Up @@ -115,6 +121,8 @@ func (s *Site) Copy() *Site {

// RestoreChildrenRefs updates the key of the children and any reference to this object.
func (s *Site) RestoreChildrenRefs() {
s.optimizerState.site = s

for k, v := range s.Points {
v.key, v.site = k, s
}
Expand Down
15 changes: 15 additions & 0 deletions toggle-input-component.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,18 @@
// Copyright (C) 2021 David Vogel
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.

package main

import "github.com/vugu/vugu"
Expand Down

0 comments on commit 88c1208

Please sign in to comment.