# FILE: solver.tcl
#
# Class for controlling micromagnetic solver
#

Oc_Class mms_solver {
    private variable mif
    private variable grid
    private variable fieldrange
    private variable logname
    private variable hashstring = "##################################################################"  ;# For log file section demarcation

    public variable default_control_spec = {}
    ## Dummy value, set to real value when mif file is read in.

    public variable control_point_proc = "$this ControlPointCheck"
    public variable stop_request_flag = 0

    private variable solver_state = Init
    ## Should be one of Pause, OneStep, Relax, Run, or Init.
    ## Set by calls to ChangeSolverState.  The 'after idle'
    ## method BackgroundStep checks the value of this variable at
    ## the start and end of processing.  Init the initial value
    ## set during initialization and reset operations, and should
    ## not be otherwise used.

    private variable BackgroundId = {}
    ## If not {}, then the 'after id' of the BackgroundStep script.
    ## If = {}, then no BackgroundStep is currently scheduled.

    private variable ode_step_count = 0  ;# Total number of ODE
    ## steps taken.  This is incremented until a reset, restart,
    ## or the loading of a new problem resets the value.

    private variable ode_lcp_step_count = 0  ;# ODE step count
    ## at last control point.  Used to prevent spurious control
    ## point events, and to implement a safeguard that ensures
    ## that at least 10 iterations are taken at each field value,
    ## unless the user overrides this by setting MaxTimeStep in
    ## the MIF file.

    private variable lcp_field_index = -1 ;# Field index
    ## at last control point.  Makes sure a control point is
    ## raised in the case where the applied field is changed
    ## and a control point is reached without any ODE steps.
    ## Added mjd on 16-Aug-1999.

    private variable simulation_time = 0  ;# Total simulation time in
    ## "simulation" seconds.  This is model time, not CPU process time.
    ## Like ode_step_count, this is only reset upon a reset, restart,
    ## or the loading of a new problem.

    private variable maxangle     ;#  Largest angle between any
    ## two neighboring spins in current magnetization state.  Valid
    ## for current iteration if set (i.e., if [info exists maxangle])

    private variable lcp_maxangle  ;#  Largest angle between any
    ## two neighboring spins since the last control point.  There is
    ## some fragileness here when we are at a control point step.  In
    ## that case, as currently coded (1999-04-08), lcp_maxangle holds
    ## the value _since_ the previous control point, and doesn't get
    ## reset until the next step.  OTOH, ode_lcp_step_count gets
    ## updated immediately, i.e., if we are at a control point, then
    ## ode_lcp_step_count == ode_step_count.
    ##   MJD NOTE 16-Aug-1999: It is not clear how to interpret
    ## lcp_maxangle in the case where multiple control points are
    ## raised at the same iteration (e.g., due to applied field
    ## steps).  I would prefer to change lcp_maxangle at the same
    ## time as ode_lcp_step_count, but I some external code might
    ## depend on the current behavior...

    private variable run_maxangle  ;# Largest angle between any
    ## two neighboring spins throughout run.

    private variable angle_threshold = 30	;# Angle (in degrees)
    ## above which a maxangle value should report a warning.

    private variable angle_warning = 1		;# Whether or not
    ## we warn when maxangle exceeds angle_threshold.  Determined
    ## by whether or not run_maxangle starts exceeding it.

    private variable restart = 0  ;# If true, then current grid was
    ## initialized using (or at least attempting to use) a log file
    ## from an earlier run to extract restart information.

    private variable hUpdateCountOffset = 0  ;# Used to offset
    ## hUpdateCount across restarts.

    # Relax stop criteria; unset => no limit
    private variable stop_torque
    private variable stop_step
    private variable stop_time

    public variable controlpoint_callback = Oc_Nop
    public variable fieldstep_callback = Oc_Nop

    # Events generated by this class:
    #     Iteration    --- ODE step complete
    #     ChangeState  --- Solver changing state (%state is new state)
    #     ControlPoint --- Control point reached (at current field)
    #     FieldStep    --- Applied field nominal value stepped  
    #     FieldDone    --- FieldStep attempted, but no more steps in list
    #     RunDone      --- New problem, reset, solver closing, or FieldStep
    #                      attempted with no more steps in list
    #     BigAngle     --- When maxangle exceeds angle_threshold
    #     delete       --- Instance being deleted
    # Note: A given ODE iteration my raise multiple events, but
    #       CheckPoint events are not raised more than once on the same
    #       iteration.

    private variable run_done_iteration = -1
    ## Iteration count of last RunDone event.  Used to protect
    ## against multiple firings of RunDone on the same iteration.

    private common rescheduleDelay

    ClassConstructor {
        global tcl_platform
        set rescheduleDelay 0
        # Socket reading on Windows needs an opening in the
        # event scheduling to work properly.  On other platforms
        # reschedule as fast as possible for maximum compute
        # performance.
        if {[string match windows $tcl_platform(platform)]} {
            set rescheduleDelay 1
        }
    }
    Constructor { _mif args } {
        # NOTE: First elt of args may be an (optional) value for
        #  "do_restart".
        if {[string match {} $_mif]} {
            error "Invalid mif object passed to mms_solver constructor"
        }
        # If first element of args doesn't start with "-", then
        # interpret it as a value for "do_restart"
        set do_restart [lindex $args 0]
        if {[string match {} $do_restart] || \
                [string match "-*" $do_restart]} {
            # Whoops, first element not a value for do_restart
            set do_restart 0  ;# Default value
        } else {
            # First arg meant for do_restart, so shift args down 1
            set args [lreplace $args 0 0]
        }
        mms_fieldrange New fieldrange
        eval $this Configure $args
        $this Reset $_mif $do_restart
    }
    private method LogStamp {} {
        return "\[<[pid]> [clock format [clock seconds]\
                 -format %H:%M:%S\ %Y-%m-%d]\]"
    }
    private method InitLogFile {} {
        # Sets up a log file derived from [$mif GetOutBaseName]
        # to be used for both log and error messages.  Also writes
        # run header info to the file.

        set logname "[$mif GetOutBaseName]"
        if {[string match {} $logname]} {
            set logname "mmsolve2d"   ;# Fallback
        }
        append logname ".log"
        if {[catch {open $logname a+} logchan]} {
            Oc_Log Log "Error in Reset: $logchan" warning
            set logname [[Oc_Config RunPlatform] GetValue path_device_null]
            set logchan [open $logname a+]
        }

        # Direct error messages to the log file.  This assumes that
        # an outer controlling script has called Oc_Log SetLogHandler
        # to redirect errors to FileLogger.  (Otherwise this is
        # effectively a nop.)
        FileLogger SetFile $logname

        # Dump run header info to log file
        regsub -all "\n(.)" "    [$mif Write]" "\n    \\1" mifspec
        puts -nonewline $logchan "$hashstring
[$this LogStamp] Run start on [info hostname]
  MIF Specification ---
$mifspec"
        close $logchan
    }

    private method RestartLogParse { checkpt_name {oldlog {}} } {
        # Parses log file to allow restart from last saved state.
        # Requires logname and mif be already initialized.
        upvar $checkpt_name checkpt
        if {[string match {} $oldlog]} {
            set oldlog $logname
        }
        if {[catch {open $oldlog r} logchan]} {
            Oc_Log Log "$logchan" error
        }

        set last_ctrlpt(ode\ step) -1
        set last_magfile(ode\ step) -2
        set checkpt(ode\ step) -3
        set run_maxangle 0
        set block unknown
	set blockpat {^\[[^]]+\] *(.*)$}  ;# Regexp matching start
	## of block in log file
        while {1} {
            set readcount [gets $logchan line]
            if {$readcount<0 || \
		    [regexp -- {^#########*$} $line] || \
                    [regexp -- $blockpat $line] } {
                # End of block
                set angindex "cp maxang (deg)"
                if {[info exists last_ctrlpt($angindex)] && \
                        $last_ctrlpt($angindex)>$run_maxangle} {
                    set run_maxangle $last_ctrlpt($angindex)
                }
                # ... then check to see if last blocks aligned
                if {$last_magfile(ode\ step) == $last_ctrlpt(ode\ step) && \
                        [info exists last_magfile(magfile)] && \
                        [file readable $last_magfile(magfile)]} {
                    unset checkpt
                    array set checkpt [array get last_magfile]
                    array set checkpt [array get last_ctrlpt]
                    set checkpt(run_maxangle) $run_maxangle
                } elseif {[string match ctrlpt $block]} {
                    # Closing ctrlpt block;  Try to guess magfile name
                    set magname [format "%s.field%04d-pass%05d.omf" \
                            [$mif GetOutBaseName] \
			    $last_ctrlpt(field\ index) \
                            $last_ctrlpt(ode\ step)]        ;# Default
                    if {![file readable $magname] && \
			    [info exists last_ctrlpt(field\ index)]} {
                        # Default didn't work; Try batchsolver format
                        set magname [format "%s.field%04d.omf" \
                                [$mif GetOutBaseName] \
                                $last_ctrlpt(field\ index)]
                        if {![file readable $magname]} {
                            set magname {}
                        }
                    }
                    if {![string match {} $magname]} {
                        # Success!
                        unset checkpt
                        array set checkpt [array get last_ctrlpt]
                        set checkpt(magfile) $magname
                        set checkpt(run_maxangle) $run_maxangle
                    }
                }
            }
            if {$readcount<0} {
                break  ;# End of input
            }
            if {[regexp -- {^#########*$} $line]} {
                # New run
                unset last_magfile
                unset last_ctrlpt
                set last_ctrlpt(ode\ step) -1
                set last_magfile(ode\ step) -2
                set run_maxangle 0
                set block unknown
	    } elseif {[regexp -- $blockpat \
		    $line dummy blockinfo]} {
		# New block
                if {[regexp -- {^([^:]+): *(.*)$} $blockinfo \
                        dummy blocktype blockarg]} {
                    set blocktype [string trim $blocktype]
                    set blockarg [string trim $blockarg]
                } else {
                    set blocktype [string trim $blockinfo]
                    set blockarg {}
                }
                switch -exact -- $blocktype {
                    ControlPoint {
                        set block ctrlpt
                        unset last_ctrlpt
                        set last_ctrlpt(ctrltype) $blockarg
                        set last_ctrlpt(ode\ step) -1
                    }
                    WriteMagFile {
                        set block magfile
                        unset last_magfile
                        set last_magfile(magfile) $blockarg
                        set last_magfile(ode\ step) -2
                    }
                    Restart {
                        set block restart
                        unset last_magfile
                        unset last_ctrlpt
                        set last_ctrlpt(ode\ step) -1
                        set last_magfile(ode\ step) -2
                        set run_maxangle 0
                    }
                    default { set block unknown }
                }
            } else {
                # Continue processing inside last block
                switch -exact -- $block {
                    ctrlpt {
                        if {[regexp -- {^ ([^:]+):(.*)$} \
                                $line dummy name value]} {
                            set name [string tolower [string trim $name]]
                            set value [string trim $value]
                            set last_ctrlpt($name) $value
                        }
                    }
                    magfile {
                        if {[regexp -- {^ ([^:]+):(.*)$} \
                                $line dummy name value]} {
                            set name [string tolower [string trim $name]]
                            set value [string trim $value]
                            set last_magfile($name) $value
                        }
                    }
                    restart {
                        if {[regexp -- {^ ([^:]+):(.*)$} \
                                $line dummy name value]} {
                            set name [string tolower [string trim $name]]
                            if {[string match "run max angle (deg)" $name]} {
                                set run_maxangle [string trim $value]
                            }
                        }
                    }
                }
            }
        }
    }
    

    method Reset { {_mif {}} {do_restart 0}} {
        $this ChangeSolverState Pause
        $this CloseLog
	set solver_state Init
        set restart 0 ;# Default
        set restart_active 0
	if {$ode_step_count != $run_done_iteration} {
	    set run_done_iteration $ode_step_count
	    if {[catch {Oc_EventHandler Generate $this RunDone} \
		    msg]} {
		bgerror $msg
	    }
	}
        if {![string match {} $_mif]} {
            mms_mif New safety_mif $_mif ;# In case $_mif is actually $mif
            if {[info exists mif]} {
                $mif Delete
                unset mif
            }
            if {[info exists grid]} {
                $grid Delete
                unset grid
            }
            if {![info exists fieldrange]} {
                mms_fieldrange New fieldrange  ;# Safety
            }
            mms_mif New mif $safety_mif  ;# Copy constructor
            $safety_mif Delete
            $this InitLogFile
            $fieldrange SetFields [$mif GetFieldRangeList]
            set default_control_spec [$mif GetDefaultControlPointSpec]
            if {$do_restart} {
                if {[catch {$this RestartLogParse checkpt $logname} errmsg]} {
                    Oc_Log Log "$errmsg" warning
                } elseif {![info exists checkpt(magfile)]} {
                    Oc_Log Log "No checkpoint info in $logname" warning
                } else {
                    $mif SetMagInitArgs [list avfFile $checkpt(magfile)]
                    set restart_active 1
                }
            }
            mms_grid2D New grid $mif
        } else {
            $this InitLogFile
            $fieldrange Reset
            if {![info exists grid]} {
                error "Variable `grid' unset in mms_solver::Reset"
            }
            $grid Reset
        }
        set ode_step_count 0
        set ode_lcp_step_count 0
	set lcp_field_index -1
        set maxangle [expr [$grid GetMaxNeighborAngle]*180./3.14159265358979]
        set lcp_maxangle $maxangle
        set run_maxangle $maxangle
        set simulation_time 0
	set run_done_iteration -1
        $this UpdateField
        $this SetControlPoints
        if {$restart_active} {
            catch {$this IncrementFieldStep $checkpt(field\ index)}
            catch {set simulation_time $checkpt(sim\ time\ \(s\))}
            catch {set run_maxangle $checkpt(run_maxangle)}
            catch {set ode_step_count $checkpt(ode\ step)}
            catch {set hUpdateCountOffset $checkpt(update\ count)}
            set restart 1

            # Note restart event in log
            set logchan [open $logname a+]
            puts $logchan "[$this LogStamp] Restart: $logname"
            puts $logchan "  Field Index        : [$this GetFieldStepCount]"
            puts $logchan "  App Field (T)      : [$this GetNomAppliedField]"
            puts $logchan "  Sim Time (s)       : $simulation_time"
            puts $logchan "  ODE Step           : $ode_step_count"
            puts $logchan "  Update count       : [$this GetFieldUpdateCount]"
            puts $logchan "  Run Max Angle (deg): $run_maxangle"
            close $logchan
        }
	set angle_warning [expr {$run_maxangle <= $angle_threshold}]
	$this ChangeSolverState Pause
    }
    private method UpdateField {} {
        $grid SetAppliedField \
                [$fieldrange GetField] [$fieldrange GetFieldIndex]
    }
    private method SetControlPoints {} {
        set ctrlspec [$fieldrange GetControlSpec]
        if {[string match {} $ctrlspec]} {
            set ctrlspec $default_control_spec
        }
        catch { unset stop_torque }
        catch { unset stop_time }
        catch { unset stop_step }
        foreach {spec value} $ctrlspec {
            switch -exact -- $spec {
                -torque { set stop_torque [expr $value] }
                -time { set stop_time [expr $value] }
                -iteration { set stop_step [expr $value] }
                default {
                    error "Invalid control point spec: $spec (value: $value)"
                }
            }
        }
    }
    method GetNumericFormat {} {
	return [$mif GetDataTableFormat]
    }
    method GetIterationValueLabels {} {
        return \
"  ODE     Max       Max                               ENERGIES
 Step     m x h     Angle     Total       Exchange    Anisotropy      Demag        Zeeman"
    }
    method GetMaxMxH {} {
        return [$grid GetMaxMxH]
    }
    method GetFieldUpdateCount {} {
        return [expr [$grid GethUpdateCount] + $hUpdateCountOffset]
    }
    method GetStepStats {} {
        return [$grid GetStepStats]
    }
    method GetIterationValues {} {
        set energies [join [$this GetEnergyDensities]]
	set dtfmt [$mif GetDataTableOutputFormat]
        return [format \
                "%5d $dtfmt  $dtfmt  $dtfmt $dtfmt $dtfmt $dtfmt $dtfmt" \
                [$this GetODEStepCount] [$grid GetMaxMxH] \
                [$this GetMaxAngle] \
                [lindex $energies 1] [lindex $energies 3] \
                [lindex $energies 5] [lindex $energies 7] \
                [lindex $energies 9]]
    }
    private method LogControlPointCheck { \
	    step_check time_check torque_check mxh } {
	# Make note in log and return true
	set logchan [open $logname a+]
	puts -nonewline $logchan "[$this LogStamp] ControlPoint:"
	if {$step_check}   { puts -nonewline $logchan " step"   }
	if {$time_check}   { puts -nonewline $logchan " time"   }
	if {$torque_check} { puts -nonewline $logchan " torque" }
	puts $logchan {}
	puts $logchan "  Field Index     : [$this GetFieldStepCount]"
	puts $logchan "  App Field (T)   : [$this GetNomAppliedField]"
	puts $logchan "  Sim Time (s)    : $simulation_time"
	puts $logchan "  CP MaxAng (deg) : $lcp_maxangle"
	puts $logchan "  |m x h|         : $mxh"
	puts $logchan "  ODE Step        : $ode_step_count"
	puts $logchan "  Update count    : [$this GetFieldUpdateCount]"
	close $logchan
    }
    method ControlPointCheck { {mxh unknown} } { \
	    stop_torque stop_step stop_time grid } {
        # Returns 1 if stopping criteria are met, 0 otherwise.
        # Call with mxh set to "unknown" (the default)
        # to query $grid for max m x h.
        set torque_check 0
        set time_check 0
        set step_check 0
        if { [info exists stop_torque] } {
            if { [string match "unknown" $mxh] } {
                # Query grid for max m x h
                set mxh [$grid GetMaxMxH]
            }
            if { $mxh < $stop_torque } {
                set torque_check 1      ;# Stop
            }
        }
        if { [info exists stop_time] } {
            if  { $stop_time <= 0. } {
                set time_check 1      ;# Stop
            }
        }
        if { [info exists stop_step] } {
            if { $stop_step <= 0 } {
                set step_check 1      ;# Stop
            }
        }
        if { [expr $step_check || $time_check || $torque_check] } {
            # Make note in log and return true
	    $this LogControlPointCheck \
		    $step_check $time_check $torque_check $mxh
            return 1
        }
        return 0 ;# Control point not reached
    }

    ;### NOTE: The following method should be private, but is run via
    ;###   the update stack so must be technically public.
    method BackgroundStep { {stop_check 0} } {
        # This method should be called via 'after idle'.  At
        # the start of processing, this method first checks the
        # value of solver_state, and returns immediately if
        # solver_state==Pause.  Otherwise, one step is made
        # in the ODE, w/o any update calls.  After this, the
        # method may reschedule itself, depending on the value
        # of solver_state and ControlPointCheck.

        # If stop_check is true, then the code checks the
        # stop conditions *instead* of taking an ODE step.
        # Rescheduling occurs the same either way, with
        # stop_check set to 0 on the rescheduled event, so
        # a Relax or a Run sequence can be initiated by calling
        # this routine with stop_check set to 1 to disable
        # unnecessary ODE stepping at the current field.
        if {[string match {} $BackgroundId]} {
            # Safety check
            error "solver method BackgroundStep call scheduling error"
        }

        set BackgroundId {}
        if {[string match Pause $solver_state]} { return }
        if {$stop_check} {
            set mxh [$grid GetMaxMxH]
        } else {
            set combined_stop_time [$mif GetMaxTimeStep]
            if {![info exists stop_time]} {
                # No time contraint from applied field schedule
                set mxh [$grid StepODE [$mif GetMinTimeStep] \
                                       $combined_stop_time]
            } else {
                # Obey field schedule stop time constraint
                if { $combined_stop_time<=0. } {
                    # MaxTimeStep not specified.  As a safeguard,
                    # adjust step limit to ensure at least 10
                    # iterations are taken at this field value.
                    set ensure_count \
                            [expr 10 - ($ode_step_count-$ode_lcp_step_count)]
                    if { $ensure_count>0 } {
                        set combined_stop_time \
                                [expr $stop_time/$ensure_count]
                    }
                }
                if { $combined_stop_time<=0. \
                        || $stop_time<$combined_stop_time } {
                    set combined_stop_time $stop_time
                }
                set mxh [$grid StepODE [$mif GetMinTimeStep] \
                                       $combined_stop_time]
                set stop_time [expr $stop_time - [$grid GetTimeStep]]
                ## Keep stop_time separate from simulation_time to
                ## better handle round-off
            }
            set maxangle [expr \
                    [$grid GetMaxNeighborAngle]*57.295779513082320877]
	    ## (Note: 180./Pi = 57.295...)
            ## If it is too expensive to calculate maxangle each
            ## iteration, then "unset maxangle" instead, so method
            ## GetMaxAngle works properly.
            if {$ode_step_count == $ode_lcp_step_count \
                    || $lcp_maxangle<$maxangle} {
                set lcp_maxangle $maxangle ;# Reset on first step
                ## after control point, or if maxangle is bigger.
            }
            if {$run_maxangle<$maxangle} {
		set run_maxangle $maxangle
		if {$angle_warning && ($run_maxangle > $angle_threshold)} {
		    Oc_EventHandler Generate $this BigAngle -angle $run_maxangle
		    set angle_warning 0
		}
	    }

            incr ode_step_count

            if {[info exists stop_step]} { incr stop_step -1 }
            set simulation_time [expr $simulation_time + [$grid GetTimeStep]]
            if {[catch {Oc_EventHandler Generate $this Iteration} msg]} {
                bgerror $msg
            }
        }

        # Check control_point and field_step requirements, and generate
        # events and callbacks as appropriate.
        set control_point_event 0
        set fieldstep_attempt 0
        if {[eval $control_point_proc $mxh]} {
            set control_point_event 1
            if {[string match Run $solver_state]} {
                set fieldstep_attempt 1
            }
        }
        if {$control_point_event &&  \
                ($ode_step_count != $ode_lcp_step_count || \
		[$fieldrange GetFieldIndex] != $lcp_field_index) } {
            # Relax events get called with old field values still
            # in place.  (Hmm, maybe the ode_step_count check is
	    # not needed? -mjd, 16-Aug-1999.)
            eval $controlpoint_callback
            if {[catch {Oc_EventHandler Generate $this ControlPoint} \
                    msg]} {
                bgerror $msg
            }
            set ode_lcp_step_count $ode_step_count
	    set lcp_field_index [$fieldrange GetFieldIndex]
        }
        set fieldstep_event 0
        if {$fieldstep_attempt} {
            if {[$fieldrange GetFieldIndex]+1<[$fieldrange GetFieldCount]} {
                $this IncrementFieldStep
                eval $fieldstep_callback
                if {[catch {Oc_EventHandler Generate $this FieldStep} \
                        msg]} {
                    bgerror $msg
                }
                set fieldstep_event 1
            } else {
                if {[catch {Oc_EventHandler Generate $this FieldDone} \
                        msg]} {
                    bgerror $msg
                }
		if {$ode_step_count != $run_done_iteration} {
		    set run_done_iteration $ode_step_count
		    if {[catch {Oc_EventHandler Generate $this RunDone} \
			    msg]} {
			bgerror $msg
		    }
		}
            }
        }

        # Reschedule if necessary, otherwise set solver_state to Pause.
        # (Also do safety check on $solver_state.)
        # NB: 'Pause' is a possible state, because the Oc_EventHandlers
        #     may modify the solver state.
        set reschedule 0
        switch -exact -- $solver_state {
            Run     {
                if {!$control_point_event || $fieldstep_event} {
                    set reschedule 1
                }
            }
            Relax   {
                if {!$control_point_event} {
                    set reschedule 1
                }
            }
            Pause  {}
            OneStep {}
            default {
                error "Invalid solver_state detected in\
                        BackgroundStep: ->${solver_state}<-"
            }
        }
        if {$reschedule} {
	    set new_stop_check $fieldstep_event
            set BackgroundId [after $rescheduleDelay \
                    set [$this GlobalName BackgroundId] \
                    \[after idle \
		    [list $this BackgroundStep $new_stop_check]\]]
            ## We need to schedule this from the non-idletask stack,
            ## because if Tcl_DoOneEvent processes *any* idletasks, it
            ## processes *all*, including apparently ones that go into
            ## the list after idletask processing has commenced.  The
            ## above seems to work, but Dean Brettle <brettle@his.com>
            ## claims that there is a race condition in this as well
            ## (see his comp.lang.tcl posting on 1998/02/15).  I haven't
            ## looked at the Tcl core source code, but it may be that
            ## a Tcl 'update' call with the above in place may be evil
            ## as well.  If a problem is observed with the above, it is
            ## likely that changing the 'after 0' to, say, 'after 20' will
            ## better hide the problem.  -mjd, 1998/04/07.
            ##
            ## See also the ClassConstructor - DGP 1998/11/04.
        } else {
            set solver_state "Pause"
	    if {[catch {
		    Oc_EventHandler Generate $this ChangeState -state Pause
	    } msg]} {
		bgerror $msg
	    }
        }
    }

    method ChangeSolverState { newstate } {
        # 'newstate' should be one of OneStep, Relax, Run
        #  or Pause.
        if {[string match $solver_state $newstate]} {return} ;# No change

        # Rather than relying on the state of BackgroundId, play
        # it safe and always de-schedule BackgroundId, and
        # re-schedule if necessary.  (Presumably ChangeSolverState
        # won't be called inside a tight loop!)
        if {![string match {} $BackgroundId]} {
            after cancel $BackgroundId
            set BackgroundId {}
        }

        switch -exact -- $newstate {
            OneStep {
                set BackgroundId [after idle [list $this BackgroundStep 0]]
            }
            Relax   -
            Run    {
                set BackgroundId [after idle [list $this BackgroundStep 1]]
            }
            Pause {}
            default {
                error "Invalid solver_state request to\
                        ChangeSolverState: $newstate"
            }
        }
        set solver_state $newstate
	if {[catch {
		Oc_EventHandler Generate $this ChangeState -state $solver_state
	} msg]} {
	    bgerror $msg
	}
    }
    method IncrementFieldStep { { amount 1 }} {
        set newindex [$fieldrange StepField $amount]
        $this UpdateField
        $this SetControlPoints
        return $newindex
    }
    method GetFieldStepCount {} {
        return [$fieldrange GetFieldIndex]
    }
    method GetODEStepCount {} { return $ode_step_count; }
    method GetMaxAngle {} {
        # Returns max neighbor angle in degrees.
        if {![info exists maxangle]} {
            set maxangle [expr \
                    [$grid GetMaxNeighborAngle]*180./3.14159265358979]
        }
        return $maxangle
    }
    method GetEnergyDensities {} {
        # Returns a list of list pairs, energy name + value
        # The first is the total energy"
        set energy_list [$grid GetEnergyDensities]
        set rtn_list {}
        lappend rtn_list [list "Total" [lindex $energy_list 4]]
        lappend rtn_list [list "Exchange" [lindex $energy_list 0]]
        lappend rtn_list [list "Anisotropy" [lindex $energy_list 1]]
        lappend rtn_list [list "Demag" [lindex $energy_list 2]]
        lappend rtn_list [list "Zeeman" [lindex $energy_list 3]]
        return $rtn_list
    }
    method GetNomAppliedField { {mult 1.} {fmt {}}} {
	if {[string match {} $fmt]} {
	    set fmt [$mif GetDataTableOutputFormat]
	    set fmt "$fmt $fmt $fmt"
	}
        set field [$fieldrange GetField]
        return [format $fmt \
                [expr [lindex $field 0]*$mult] \
                [expr [lindex $field 1]*$mult] \
                [expr [lindex $field 2]*$mult]]
    }
    method GuessAppliedFieldAxis {} {
        #  Queries the mif file and tries to guess the component
        # of BApplied that is most likely intended for M-B loop.
        #  Returns a unit 3 vector.
        # NB: This method is a serious hack.  Its use is discouraged.
        set B0 [$fieldrange GetField 0]
        set B1 [$fieldrange GetField 1]
        set dBx [expr abs([lindex $B1 0] - [lindex $B0 0])]
        set dBy [expr abs([lindex $B1 1] - [lindex $B0 1])]
        set dBz [expr abs([lindex $B1 2] - [lindex $B0 2])]
        if { $dBz > $dBy && $dBz > $dBx } { return "0 0 1" }
        if { $dBy > $dBx }                { return "0 1 0" }
        return "1 0 0"
    }
    method GetAveMag {} {
        # Returns average magnetization M/Ms, as a string with
        # 3 entries.
        set mag [$grid GetAveMag]
        return "[lindex $mag 0] [lindex $mag 1] \
                [lindex $mag 2]"
    }
    method GetCellSize {} {
	return [$grid GetCellSize]
    }
    method GetStepSize {} {
        return [$grid GetStepSize]
    }
    method GetTimeStep {} {
        return [$grid GetTimeStep]
    }
    method GetSimulationTime {} {
        return $simulation_time
    }
    method GetOutputBasename {} {
        return [$mif GetOutBaseName]
    }
    method GetSolverState {} {
        return $solver_state
    }
    method GetFieldComponentNorm { component thick } {
        return [$grid GetFieldComponentNorm $component $thick]
    }
    method GetComponentEnergyDensity { component } {
        set energy_list [$grid GetEnergyDensities]
	set value {foo}
	set component [string tolower $component]
	switch -exact $component {
	    exchange   { set value [lindex $energy_list 0] }
	    anis       -
	    anisotropy { set value [lindex $energy_list 1] }
	    demag      { set value [lindex $energy_list 2] }
	    zeeman     { set value [lindex $energy_list 3] }
	    total      { set value [lindex $energy_list 4] }
	}
        return $value
    }
    method WriteFieldComponentFile { component {filename {}} {note " "} } {
        # Writes reduced h component field to a ovf file
        if {[string match {} $filename]} {
            # Use data in mif to construct "real" filename
            set filename [format "%s.%s-field%04d-pass%05d.ohf" \
                    [$mif GetOutBaseName] $component \
		    [$this GetFieldStepCount] [$this GetODEStepCount]]
            set filename [Oc_DirectPathname $filename] 
            # Get arb. temp name
	    set tempname [Oc_TempName solveh .ohf]
        } else {
            set tempname [Oc_DirectPathname $filename]
            set filename {}
        }

        # If $note is a single blank (default), then fill in Happ and
        # iteration information.
        if {[string match " " $note]} {
            set note      "Field Index: [$this GetFieldStepCount]"
	    append note "\nApplied field (T): [$this GetNomAppliedField]"
	    append note "\nIteration: [$this GetODEStepCount]"
	    append note "\nTime (s): $simulation_time"
	    append note "\n|m x h|: [$grid GetMaxMxH]"
	    set comment [$mif GetUserComment]
	    if {![string match {} $comment]} {
		append note "\nUser Comment: $comment"
	    }
        }

        # Write file
	foreach {type precision} [$mif GetTotalFieldOutputFormat] {break}
        $grid WriteFieldComponent $component $type $precision \
		$tempname $filename $note
        return [list $tempname $filename]
    }
    method WriteFieldFile { {filename {}} {note " "} } {
        # Writes reduced h field to a ovf file
        if {[string match {} $filename]} {
            # Use data in mif to construct "real" filename
            set filename [format "%s.field%04d-pass%05d.ohf" \
                    [$mif GetOutBaseName] \
		    [$this GetFieldStepCount] [$this GetODEStepCount]]
            set filename [Oc_DirectPathname $filename] 
            # Get arb. temp name
	    set tempname [Oc_TempName solveh .ohf]
        } else {
            set tempname [Oc_DirectPathname $filename]
            set filename {}
        }

        # If $note is a single blank (default), then fill in Happ and
        # iteration information.
        if {[string match " " $note]} {
            set note      "Field Index: [$this GetFieldStepCount]"
	    append note "\nApplied field (T): [$this GetNomAppliedField]"
	    append note "\nIteration: [$this GetODEStepCount]"
	    append note "\nTime (s): $simulation_time"
	    append note "\n|m x h|: [$grid GetMaxMxH]"
	    set comment [$mif GetUserComment]
	    if {![string match {} $comment]} {
		append note "\nUser Comment: $comment"
	    }
        }

        # Write file
	foreach {type precision} [$mif GetTotalFieldOutputFormat] {break}
        $grid WriteOvf h $type $precision $tempname $filename $note
        return [list $tempname $filename]
    }
    method WriteMagFile { {filename {}} {note " "} {quiet 1}} {
        # Writes magnetization to a ovf file
        if {[string match {} $filename]} {
            # Use data in mif to construct "real" filename
            set filename [format "%s.field%04d-pass%05d.omf" \
                    [$mif GetOutBaseName] \
		    [$this GetFieldStepCount] [$this GetODEStepCount]]
            set filename [Oc_DirectPathname $filename] 
            # Get arb. temp name
	    set tempname [Oc_TempName solvem .omf]
        } else {
            set tempname [Oc_DirectPathname $filename]
            set filename {}
        }

        # If $note is a single blank (default), then fill in Happ and
        # iteration information
        if {[string match " " $note]} {
            set note "Field Index: [$this GetFieldStepCount]\
                    \nApplied field (T): [$this GetNomAppliedField]\
                    \nIteration: [$this GetODEStepCount]\
                    \nTime (s): $simulation_time\
                    \n|m x h|: [$grid GetMaxMxH]\
                    \nUser Comment: [$mif GetUserComment]"
        }

        # Write file
	foreach {type precision} \
		[$mif GetMagnetizationOutputFormat] {break}
        $grid WriteOvf m $type $precision $tempname $filename $note

        # If "quiet" is false, make note in log
        if {!$quiet} {
            set logchan [open $logname a+]
            puts -nonewline $logchan "[$this LogStamp] WriteMagFile: "
            if {[string match {} $filename]} {
                puts $logchan $tempname
            } else {
                puts $logchan $filename
            }
            puts $logchan "  ODE Step: $ode_step_count"
            close $logchan
        }

        return [list $tempname $filename]
    }
    private method CloseLog {} {
        if {[info exists logname]} {
            set logchan [open $logname a+]
            puts $logchan "[$this LogStamp] Run Complete"

            # Run max angle
            puts $logchan "  Run Max Angle (deg): $run_maxangle"

            # ODE step stats
            puts $logchan "  ODE step count: $ode_step_count"
            puts $logchan "  Field update count:\
                    [$this GetFieldUpdateCount]"
            puts $logchan "  Grid stats---"
            foreach { steps rejects energy_rejects position_rejects } \
                    [$grid GetStepStats] {
                puts $logchan "   ODE Steps: $steps  \
                        Rejects: $rejects  (energy: $energy_rejects,\
                        position: $position_rejects)"
            }

            # Usage time
            set utimes [$grid GetUsageTimes]
            if {![string match {} $utimes]} {
                regsub -all "\n(.)" "   $utimes" "\n   \\1" utimes
            }
            puts $logchan "$utimes"
            puts $logchan "$hashstring\n"
            close $logchan
            unset logname
        }
    }
    Destructor {
        $this ChangeSolverState Pause  ;# In particular, clear "after" stack
        Oc_EventHandler Generate $this RunDone
        Oc_EventHandler Generate $this Delete
        Oc_EventHandler DeleteGroup $this
        $this CloseLog
        if {[info exists grid]} {
            catch { $grid Delete }
        }
        if {[info exists mif]} {
            catch { $mif Delete }
        }
        if {[info exists fieldrange]} {
            catch { $fieldrange Delete }
        }
    }
}
