Skip to content

Instantly share code, notes, and snippets.

@nanzi
Last active April 22, 2020 06:26
Show Gist options
  • Save nanzi/b8da07df1f8832153cc6bd401d3266d4 to your computer and use it in GitHub Desktop.
Save nanzi/b8da07df1f8832153cc6bd401d3266d4 to your computer and use it in GitHub Desktop.
FFT analysis tool for Go AI sai's autogtp comments
package main
// Search a sai sgf directory for comments FFT analysis
import (
"bytes"
"fmt"
"github.com/fohristiwhirl/sgf"
"github.com/mjibson/go-dsp/fft"
"io/ioutil"
"math/cmplx"
"os"
"path/filepath"
"strconv"
"strings"
)
func main() {
if len(os.Args) < 2 {
fmt.Printf("Usage: %s dirname \n", filepath.Base(os.Args[0]))
return
}
files, err := ioutil.ReadDir(os.Args[1])
if err != nil {
panic(err.Error())
}
fmt.Println("0_ComposedPart(SortMe),FFTParts,Result,Black,White,Match,Black_View_In_Frequncy ")
var matches []string
for _, f := range files {
fullpath := filepath.Join(os.Args[1], f.Name())
matchHash := f.Name()
matchHash6 := "[" + matchHash[:7] + "]" //protect for numbers
node, err := sgf.Load(fullpath)
if err != nil {
fmt.Printf("%v\n", err)
}
root := node.GetRoot()
pb, okRb := root.GetValue("PB")
if !okRb {
fmt.Printf("Can't find black player info in %s ! Set as \"none\" ", matchHash6)
pb = "none"
}
pw, okRw := root.GetValue("PW")
if !okRw {
fmt.Printf("Can't find black player info in %s ! Set as \"none\" ", matchHash6)
pw = "none"
}
res, okRr := root.GetValue("RE")
if !okRr {
fmt.Printf("Can't find result info in %s ! Set as \"none\" ", matchHash6)
res = "none"
}
comts := node.SaiComt()
totalMoves := len(comts)
// read all comments and save as float64 slice in parts
// https://github.com/sai-dev/sai/issues/13
//
var aomSeries []float64 // alpkt_online_median, approximate median of alpkt over the move explored subtree
var alpktSeries []float64 // alpkt is more or less the points in favor of black, including komi. Raw network estimate of alpkt (more noisy)
var betaSeries []float64 // beta is a measure of certainty of the score. Raw network estimate of beta, if above 1 the score is almost decided
var piSeries []float64 // pi, raw network win-rate estimate at current komi, for black
var aeaSeries []float64 // agent_eval_avg, average of agent win-rate over the move explored subtree
var axlSeries []float64 // agent_x_lambda, size of the integration interval (useful only if lambda>0)
var isBSeries []float64 // isBluder, flag, useful only for self-plays, a random move may be a blunder (if it loses at least 5% win-rate probability) in that case training info is saved only after last blunder
for mv := 0; mv < totalMoves; mv++ {
comment := strings.Split(comts[mv], ", ")
alpkt_online_median, _ := strconv.ParseFloat(comment[0], 64)
alpkt, _ := strconv.ParseFloat(comment[1], 64)
beta, _ := strconv.ParseFloat(comment[2], 64)
pi, _ := strconv.ParseFloat(comment[3], 64)
aea, _ := strconv.ParseFloat(comment[4], 64)
axl, _ := strconv.ParseFloat(comment[5], 64)
isBluder, _ := strconv.ParseFloat(comment[6], 64)
aomSeries = append(aomSeries, alpkt_online_median)
alpktSeries = append(alpktSeries, alpkt)
betaSeries = append(betaSeries, beta)
piSeries = append(piSeries, pi)
aeaSeries = append(aeaSeries, aea)
axlSeries = append(axlSeries, axl)
isBSeries = append(isBSeries, isBluder)
//debug
//fmt.Print(comment)
}
aomFFT := fft.FFTReal(aomSeries)
alpktFFT := fft.FFTReal(alpktSeries)
betaFFT := fft.FFTReal(betaSeries)
piFFT := fft.FFTReal(piSeries)
aeaFFT := fft.FFTReal(aeaSeries)
axlFFT := fft.FFTReal(axlSeries)
isBFFT := fft.FFTReal(isBSeries)
// debug
//fmt.Println(alpktSeries)
//fmt.Println(betaSeries)
// Handle the fft complex array : Amplitude & Phase
var aomAmp, alpktAmp, betaAmp, piAmp, aeaAmp, axlAmp, isBAmp, aomPhase, alpktPhase, betaPhase, piPhase, aeaPhase, axlPhase, isBPhase []float64
var fftHead = [14]string{"0aom-Amp", "1alpkt-Amp", "2beta-Amp", "3pi-Amp", "4aea-Amp", "5axl-Amp", "6isB-Amp", "0aom-Phase", "1alpkt-Phase", "2beta-Phase", "3pi-Phase", "4aea-Phase", "5axl-Phase", "6isB-Phase"}
var fftInfo [14][]float64
for i := 0; i < totalMoves; i++ {
m0 := cmplx.Abs(aomFFT[i])
m1 := cmplx.Abs(alpktFFT[i])
m2 := cmplx.Abs(betaFFT[i])
m3 := cmplx.Abs(piFFT[i])
m4 := cmplx.Abs(aeaFFT[i])
m5 := cmplx.Abs(axlFFT[i])
m6 := cmplx.Abs(isBFFT[i])
aomAmp = append(aomAmp, m0)
alpktAmp = append(alpktAmp, m1)
betaAmp = append(betaAmp, m2)
piAmp = append(piAmp, m3)
aeaAmp = append(aeaAmp, m4)
axlAmp = append(axlAmp, m5)
isBAmp = append(isBAmp, m6)
p0 := cmplx.Phase(aomFFT[i])
p1 := cmplx.Phase(alpktFFT[i])
p2 := cmplx.Phase(betaFFT[i])
p3 := cmplx.Phase(piFFT[i])
p4 := cmplx.Phase(aeaFFT[i])
p5 := cmplx.Phase(axlFFT[i])
p6 := cmplx.Phase(isBFFT[i])
aomPhase = append(aomPhase, p0)
alpktPhase = append(alpktPhase, p1)
betaPhase = append(betaPhase, p2)
piPhase = append(piPhase, p3)
aeaPhase = append(aeaPhase, p4)
axlPhase = append(axlPhase, p5)
isBPhase = append(isBPhase, p6)
}
fftInfo[0] = aomAmp
fftInfo[1] = alpktAmp
fftInfo[2] = betaAmp
fftInfo[3] = piAmp
fftInfo[4] = aeaAmp
fftInfo[5] = axlAmp
fftInfo[6] = isBAmp
fftInfo[7] = aomPhase
fftInfo[8] = alpktPhase
fftInfo[9] = betaPhase
fftInfo[10] = piPhase
fftInfo[11] = aeaPhase
fftInfo[12] = axlPhase
fftInfo[13] = isBPhase
// arrange SortableHeader:winner/color/mod/phase into matches
var (
winner string
loser string
res0 string = res[:1]
playerB string = pb[len(pb)-8:]
playerW string = pw[len(pw)-8:]
)
if res0 == "B" {
winner = playerB
loser = playerW
}
if res0 == "W" {
winner = playerW
loser = playerB
}
if res0 == "0" {
winner = "Jigo" + playerB
loser = playerW
}
for m := 0; m < 14; m++ {
var matchInfo bytes.Buffer
matchInfo.WriteString(fftHead[m]) // Amp/Phase
matchInfo.WriteString("-")
matchInfo.WriteString(winner)
matchInfo.WriteString("-")
matchInfo.WriteString(res0)
matchInfo.WriteString("-")
matchInfo.WriteString(loser)
matchInfo.WriteString(",")
matchInfo.WriteString(fftHead[m])
matchInfo.WriteString(",")
matchInfo.WriteString(res0)
matchInfo.WriteString(",")
matchInfo.WriteString(playerB)
matchInfo.WriteString(",")
matchInfo.WriteString(playerW)
matchInfo.WriteString(",")
matchInfo.WriteString(matchHash6)
for _, f := range fftInfo[m] {
f := strconv.FormatFloat(f, 'f', 7, 64)
matchInfo.WriteString(",")
matchInfo.WriteString(f)
}
matches = append(matches, matchInfo.String())
}
}
for _, m := range matches {
fmt.Println(m)
}
return
}
// SaiComt returns all Black move comments in sgf.
func (self *Node) SaiComt() map[int]string {
vals := make(map[int]string)
move_count := 0
node := self.GetRoot()
node = node.MainChild()
// size := node.RootBoardSize()
for {
comt, ok := node.GetValue("C") // Assuming just 1, as per SGF specs.
if ok {
vals[move_count] = comt
} else {
vals[move_count] = "0"
}
move_count++
node = node.MainChild()
if node == nil {
//fmt.Println("no nodes")
break
}
node = node.MainChild()//Jump across white move, if white comment is copied from black
if node == nil {
//fmt.Println("no nodes")
break
}
}
return vals
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment