Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 50 additions & 1 deletion engine/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ package engine
// Utilities for setting magnetic configurations.

import (
"github.com/mumax/3/data"
"math"
"math/rand"

"github.com/mumax/3/data"
)

func init() {
Expand All @@ -21,6 +22,7 @@ func init() {
DeclFunc("Conical", Conical, "Conical state for given wave vector, cone direction, and cone angle")
DeclFunc("Helical", Helical, "Helical state for given wave vector")
DeclFunc("HopfionCompactSupport", HopfionCompactSupport, "Hopfion texture from skyrmion, with compact support (smooth and magnetization exactly along z-axis outside of finite region)")
DeclFunc("CurrentMag", CurrentMag, "Returns the current magnetization as a Config. E.g. CurrentMag().Add(0.1, RandomMagSeed(123)) will return a Config with the current magnetization plus some noise.")
}

// Magnetic configuration returns m vector for position (x,y,z)
Expand Down Expand Up @@ -213,6 +215,53 @@ func HopfionCompactSupport(major_radius, minor_radius float64) Config {
}
}

func CurrentMag() Config {

d := Mesh().CellSize()
size := Mesh().Size()

Nx, Ny, Nz := size[X], size[Y], size[Z]

Lx := float64(Nx) * d[X]
Ly := float64(Ny) * d[Y]
Lz := float64(Nz) * d[Z]

mSlice := (&M).Buffer().HostCopy()

return func(x, y, z float64) data.Vector {

ix := int(math.Floor((x + 0.5*Lx) / d[X]))
iy := int(math.Floor((y + 0.5*Ly) / d[Y]))
iz := int(math.Floor((z + 0.5*Lz) / d[Z]))

if ix < 0 {
ix = 0
}
if ix >= Nx {
ix = Nx - 1
}
if iy < 0 {
iy = 0
}
if iy >= Ny {
iy = Ny - 1
}
if iz < 0 {
iz = 0
}
if iz >= Nz {
iz = Nz - 1
}

return data.Vector{
mSlice.Get(X, ix, iy, iz),
mSlice.Get(Y, ix, iy, iz),
mSlice.Get(Z, ix, iy, iz),
}
}

}

// Transl returns a translated copy of configuration c. E.g.:
//
// M = Vortex(1, 1).Transl(100e-9, 0, 0) // vortex with center at x=100nm
Expand Down
39 changes: 34 additions & 5 deletions engine/minimizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,24 @@ package engine
// Minimize follows the steepest descent method as per Exl et al., JAP 115, 17D118 (2014).

import (
"time"

"github.com/mumax/3/cuda"
"github.com/mumax/3/data"
)

var (
DmSamples int = 10 // number of dm to keep for convergence check
StopMaxDm float64 = 1e-6 // stop minimizer if sampled dm is smaller than this
DmSamples int = 10 // number of dm to keep for convergence check
StopMaxDm float64 = 1e-6 // stop minimizer if sampled dm is smaller than this
MinimizeWallClockTime float64 = -1.0 // wall-clock time limit for minimization
MinimizeConverged bool // true if minimize converged, and false if the maximum wall-clock time is reached
)

func init() {
DeclFunc("Minimize", Minimize, "Use steepest conjugate gradient method to minimize the total energy")
DeclFunc("Minimize", Minimize, "Use steepest conjugate gradient method to minimize the total energy. Returns true if convergence is reached, or false if the wall-clock time limit is exceeded. The wall-clock time limit is disabled by default.")
DeclVar("MinimizerStop", &StopMaxDm, "Stopping max dM for Minimize")
DeclVar("MinimizerSamples", &DmSamples, "Number of max dM to collect for Minimize convergence check.")
DeclVar("MinimizeWallClockTime", &MinimizeWallClockTime, "Wall-clock time limit (seconds) for Minimize that will interrupt the minimization if exceeded. Set to -1 (default) to disable. An interrupted minimization does not guarantee a correct solution.")
}

// fixed length FIFO. Items can be added but not removed
Expand Down Expand Up @@ -119,7 +124,27 @@ func (mini *Minimizer) Free() {
mini.k.Free()
}

func Minimize() {
// helper function that returns false if the wall clock time limit is exceeded. If the wall-clock time is negative, this function always returns true.
func WallclockTimer(start time.Time, WallClockTime float64) bool {
if WallClockTime < 0 {
return true
}
if WallClockTime == 0 {
return false
}
return time.Since(start) < time.Duration(WallClockTime*float64(time.Second))
}

func Minimize() bool {

// if wall-clock time is zero, skip minimization entirely (zero steps), and don't change any settings
MinimizeConverged = false
TimerStart := time.Now()
if MinimizeWallClockTime == 0 {
MinimizeConverged = false
return MinimizeConverged
}

Refer("exl2014")
SanityCheck()
// Save the settings we are changing...
Expand Down Expand Up @@ -154,9 +179,13 @@ func Minimize() {
stepper = &mini

cond := func() bool {
return (mini.lastDm.count < DmSamples || mini.lastDm.Max() > StopMaxDm)
return (mini.lastDm.count < DmSamples || mini.lastDm.Max() > StopMaxDm) && WallclockTimer(TimerStart, MinimizeWallClockTime)
}

RunWhile(cond)
pause = true
// if the loop ended because of convergence, then MinimizeConverged is true. If the loop ended because of wall-clock time, then MinimizeConverged is false.
MinimizeConverged = !(mini.lastDm.count < DmSamples || mini.lastDm.Max() > StopMaxDm)
stepper.Free()
return MinimizeConverged
}
35 changes: 27 additions & 8 deletions engine/relax.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,38 @@ package engine

import (
"math"
"time"

"github.com/mumax/3/cuda"
)

// Stopping relax Maxtorque in T. The user can check MaxTorque for sane values (e.g. 1e-3).
// If set to <=0, relax() will stop when the average torque is steady or increasing.
var RelaxTorqueThreshold float64 = -1.
var (
// Stopping relax Maxtorque in T. The user can check MaxTorque for sane values (e.g. 1e-3).
// If set to <=0, relax() will stop when the average torque is steady or increasing.
RelaxTorqueThreshold float64 = -1.
RelaxWallClockTime float64 = -1.0 // wall-clock time limit for Relax
RelaxConverged bool // true if Relax converged, and false if the maximum wall-clock time is reached
)

func init() {
DeclFunc("Relax", Relax, "Try to minimize the total energy")
DeclFunc("Relax", Relax, "Try to minimize the total energy. Returns true if convergence is reached, or false if the wall-clock time limit is exceeded. The wall-clock time limit is disabled by default.")
DeclVar("RelaxTorqueThreshold", &RelaxTorqueThreshold, "MaxTorque threshold for relax(). If set to -1 (default), relax() will stop when the average torque is steady or increasing.")
DeclVar("RelaxWallClockTime", &RelaxWallClockTime, "Wall-clock time limit (seconds) for Relax that will interrupt the relaxation if exceeded. Set to -1 (default) to disable.")
}

// are we relaxing?
var relaxing = false

func Relax() {
func Relax() bool {

// if wall-clock time is zero, skip Relaxing entirely (zero steps), and don't change any settings
RelaxConverged = false
TimerStart := time.Now()
if RelaxWallClockTime == 0 {
RelaxConverged = false
return RelaxConverged
}

SanityCheck()
pause = false

Expand Down Expand Up @@ -55,7 +70,7 @@ func Relax() {
E0 := GetTotalEnergy()
relaxSteps(N)
E1 := GetTotalEnergy()
for E1 < E0 && !pause {
for (E1 < E0 && !pause) && WallclockTimer(TimerStart, RelaxWallClockTime) {
relaxSteps(N)
E0, E1 = E1, GetTotalEnergy()
}
Expand All @@ -75,7 +90,7 @@ func Relax() {
if RelaxTorqueThreshold > 0 {
// run as long as the max torque is above threshold. Then increase the accuracy and step more.
for !pause {
for maxTorque() > RelaxTorqueThreshold && !pause {
for (maxTorque() > RelaxTorqueThreshold && !pause) && WallclockTimer(TimerStart, RelaxWallClockTime) {
relaxSteps(N)
}
MaxErr /= math.Sqrt2
Expand All @@ -88,7 +103,7 @@ func Relax() {
// if MaxErr < 1e-9, this code won't run.
var T0, T1 float32 = 0, avgTorque()
// Step as long as torque goes down. Then increase the accuracy and step more.
for MaxErr > 1e-9 && !pause {
for (MaxErr > 1e-9 && !pause) && WallclockTimer(TimerStart, RelaxWallClockTime) {
MaxErr /= math.Sqrt2
relaxSteps(N) // TODO: Play with other values
T0, T1 = T1, avgTorque()
Expand All @@ -98,7 +113,11 @@ func Relax() {
}
}
}

pause = true
RelaxConverged = (RelaxTorqueThreshold > 0 && (maxTorque() <= RelaxTorqueThreshold || MaxErr < 1e-9)) || (RelaxTorqueThreshold <= 0 && MaxErr <= 1e-9)
stepper.Free()
return RelaxConverged
}

// take n steps without setting pause when done or advancing time
Expand Down
32 changes: 32 additions & 0 deletions test/wallclocktime.mx3
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// All benchmarks mentioned in comments were run on RTX 3080 (mobile).
// ! Timings must have sufficient margin such that very weak and very strong GPUs still pass this test !

SetGridSize(256, 256, 1)
SetCellSize(5e-9, 5e-9, 3e-9)
Aex = 13e-12
Msat = 800e3
alpha = 0.02


//// Fully run relax/minimize (use a situation that relaxes very quickly)
m = Uniform(1, 0, 0)
B_ext = Vector(1, 0, 0)
ExpectB("Relax converged", Relax(), true) // Takes 0.25s to converge
ExpectB("Minimize converged", Minimize(), true) // Takes an additional 0.003s to converge


//// Interrupt relax/minimize
B_ext = Vector(0, 0, 0)
t_range := 0.01

m = RandomMag()
RelaxWallclockTime = 0.1 // Takes 49s to converge
t0 := now()
ExpectB("Relax not converged", Relax(), false)
Expect("Relax stopped in time", since(t0).Seconds(), RelaxWallclockTime + t_range/2, t_range/2)

m = RandomMag()
MinimizeWallclockTime = 0.1 // Takes 5.5s to converge
t0 = now()
ExpectB("Minimize not converged", Minimize(), false)
Expect("Minimize stopped in time", since(t0).Seconds(), MinimizeWallclockTime + t_range/2, t_range/2)