func VocabSlice(mp Vocab, frmPool string, newPools []string, sliceOffs []int) error {
	oriTsr := mp[frmPool]
	poolY := oriTsr.Shapes()[1]
	poolX := oriTsr.Shapes()[2]
	if len(newPools)+1 != len(sliceOffs) {
		err := fmt.Errorf("sliceOffs should have one more element than newPools")  
		log.Println(err.Error())
		return err
	}
	preVal := sliceOffs[0]
	for i, curVal := range sliceOffs {
		if i > 0 {
			if preVal < curVal {
				preVal = curVal
			} else {
				err := fmt.Errorf("sliceOffs should increase progressively")  
				log.Println(err.Error())
				return err
			}
		}
	}
	frmOff := sliceOffs[0]
	for i := range newPools {
		toOff := sliceOffs[i+1]
		newPool := newPools[i]
		newTsr := etensor.NewFloat32([]int{toOff - frmOff, poolY, poolX}, nil, []string{"row", "Y", "X"})
		for off := frmOff; off < toOff; off++ {
			newTsr.SubSpace([]int{off - frmOff}).CopyFrom(oriTsr.SubSpace([]int{off}))
		}
		mp[newPool] = newTsr
		frmOff = toOff
	}
	return nil
}
VocabSlice slices a pool in the vocabulary into new ones.
SliceOffs is the cutoff points in the original pool, should have one more element than newPools.
package patgen
import (
	"fmt"
	"log"
	"math"
	"math/rand"
	"github.com/emer/etable/etensor"
	"github.com/emer/etable/tsragg"
)
type Vocab map[string]*etensor.Float32
func (vc Vocab) ByNameTry(name string) (*etensor.Float32, error) {
	tsr, ok := vc[name]
	if !ok {
		err := fmt.Errorf("Vocabulary item named: %s not found", name)
		log.Println(err)
		return nil, err
	}
	return tsr, nil
}
func NOnInTensor(trow *etensor.Float32) int {
	return int(tsragg.Sum(trow))
}
func PctActInTensor(trow *etensor.Float32) float32 {
	return float32(NOnInTensor(trow)) / float32(trow.Len())
}
func NFmPct(pct float32, n int) int {
	return int(math.Round(float64(n) * float64(pct)))
}
func AddVocabEmpty(mp Vocab, name string, rows, poolY, poolX int) (*etensor.Float32, error) {
	tsr := etensor.NewFloat32([]int{rows, poolY, poolX}, nil, []string{"row", "Y", "X"})
	mp[name] = tsr
	return tsr, nil
}
func AddVocabPermutedBinary(mp Vocab, name string, rows, poolY, poolX int, pctAct, minPctDiff float32) (*etensor.Float32, error) {
	nOn := NFmPct(pctAct, poolY*poolX)
	minDiff := NFmPct(minPctDiff, nOn)
	tsr := etensor.NewFloat32([]int{rows, poolY, poolX}, nil, []string{"row", "Y", "X"})
	err := PermutedBinaryMinDiff(tsr, nOn, 1, 0, minDiff)
	mp[name] = tsr
	return tsr, err
}
func AddVocabClone(mp Vocab, name string, copyFrom string) (*etensor.Float32, error) {
	cp, err := mp.ByNameTry(copyFrom)
	if err != nil {
		return nil, err
	}
	tsr := cp.Clone().(*etensor.Float32)
	mp[name] = tsr
	return tsr, nil
}
func AddVocabRepeat(mp Vocab, name string, rows int, copyFrom string, copyRow int) (*etensor.Float32, error) {
	origItem, err := mp.ByNameTry(copyFrom)
	if err != nil {
		return nil, err
	}
	cp := origItem.Clone()
	tsr := &etensor.Float32{}
	cpshp := cp.Shapes()
	cpshp[0] = rows
	tsr.SetShape(cpshp, nil, cp.DimNames())
	mp[name] = tsr
	cprow := cp.SubSpace([]int{copyRow})
	for i := 0; i < rows; i++ {
		trow := tsr.SubSpace([]int{i})
		trow.CopyFrom(cprow)
	}
	return tsr, nil
}
func AddVocabDrift(mp Vocab, name string, rows int, pctDrift float32, copyFrom string, copyRow int) (*etensor.Float32, error) {
	cp, err := mp.ByNameTry(copyFrom)
	if err != nil {
		return nil, err
	}
	tsr := &etensor.Float32{}
	cpshp := cp.Shapes()
	cpshp[0] = rows
	tsr.SetShape(cpshp, nil, cp.DimNames())
	mp[name] = tsr
	cprow := cp.SubSpace([]int{copyRow}).(*etensor.Float32)
	trow := tsr.SubSpace([]int{0})
	trow.CopyFrom(cprow)
	nOn := NOnInTensor(cprow)
	rmdr := 0.0                                
	drift := float64(nOn) * float64(pctDrift)  
	for i := 1; i < rows; i++ {
		srow := tsr.SubSpace([]int{i - 1})
		trow := tsr.SubSpace([]int{i})
		trow.CopyFrom(srow)
		curDrift := math.Round(drift + rmdr)  
		nDrift := int(curDrift)
		if nDrift > 0 {
			FlipBits(trow, nDrift, nDrift, 1, 0)
		}
		rmdr += drift - curDrift  
	}
	return tsr, nil
}
func VocabShuffle(mp Vocab, shufflePools []string) {
	for _, key := range shufflePools {
		tsr := mp[key]
		rows := tsr.Shapes()[0]
		poolY := tsr.Shapes()[1]
		poolX := tsr.Shapes()[2]
		sRows := rand.Perm(rows)
		sTsr := etensor.NewFloat32([]int{rows, poolY, poolX}, nil, []string{"row", "Y", "X"})
		for iRow, sRow := range sRows {
			sTsr.SubSpace([]int{iRow}).CopyFrom(tsr.SubSpace([]int{sRow}))
		}
		mp[key] = sTsr
	}
}
func VocabConcat(mp Vocab, newPool string, frmPools []string) error {
	tsr := mp[frmPools[0]].Clone().(*etensor.Float32)
	for i, key := range frmPools {
		if i > 0 {
			if !(tsr.SubSpace([]int{0}).(*etensor.Float32).Shape.IsEqual(&mp[key].SubSpace([]int{0}).(*etensor.Float32).Shape)) {
				err := fmt.Errorf("shapes of input pools must be the same")  
				log.Println(err.Error())
				return err
			}
			currows := tsr.Shapes()[0]
			approws := mp[key].Shapes()[0]
			tsr.SetShape([]int{currows + approws, tsr.Shapes()[1], tsr.Shapes()[2]}, nil, []string{"row", "Y", "X"})
			for iRow := 0; iRow < approws; iRow++ {
				subtsr := tsr.SubSpace([]int{iRow + currows})
				subtsr.CopyFrom(mp[key].SubSpace([]int{iRow}))
			}
		}
	}
	mp[newPool] = tsr
	return nil
}