Histogram memory data acquisition is now controlled by the histogram server.
Implemented "histmem" convenience command for use on command line and in batch files. Make sure that the maximum time bin is set when setting the histogram memory frame frequency. x_bin and y_bin axes are now available for nexus data entries. r2203 | ffr | 2007-10-31 16:39:00 +1100 (Wed, 31 Oct 2007) | 5 lines
This commit is contained in:
committed by
Douglas Clowes
parent
f54146b58e
commit
5c66aa29ac
@@ -1,5 +1,5 @@
|
|||||||
# $Revision: 1.19 $
|
# $Revision: 1.20 $
|
||||||
# $Date: 2007-10-23 02:40:09 $
|
# $Date: 2007-10-31 05:39:00 $
|
||||||
# Author: Ferdi Franceschini
|
# Author: Ferdi Franceschini
|
||||||
# Based on the examples in the hs_test.tcl sample configuration by Mark Lesha.
|
# Based on the examples in the hs_test.tcl sample configuration by Mark Lesha.
|
||||||
# http://gumtree.ansto.gov.au:9080/nbicms/bragg-systems/histogram-server/hs_test.tcl/view
|
# http://gumtree.ansto.gov.au:9080/nbicms/bragg-systems/histogram-server/hs_test.tcl/view
|
||||||
@@ -270,6 +270,64 @@ namespace eval histogram_memory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
##
|
||||||
|
# @brief Provides y_bin boundary array for data axes
|
||||||
|
proc y_bin {args} {
|
||||||
|
set opt [lindex $args 0]
|
||||||
|
set arglist [lrange $args 1 end]
|
||||||
|
set proc_name [namespace origin [lindex [info level 0] 0]]
|
||||||
|
switch -- $opt {
|
||||||
|
"-centres" - "-boundaries" - "-graph_type" {
|
||||||
|
return [calc_axis $proc_name @none @none @none $opt $arglist]
|
||||||
|
}
|
||||||
|
"-arrayname" {
|
||||||
|
return [calc_axis $proc_name 1.0 0.0 [OAT_TABLE -get Y_BOUNDARIES] $opt $arglist]
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
return [calc_axis $proc_name 1.0 0.0 [OAT_TABLE -get Y_BOUNDARIES] $args]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set script_name ::histogram_memory::y_bin
|
||||||
|
publish $script_name user
|
||||||
|
sicslist setatt $script_name privilege internal
|
||||||
|
sicslist setatt $script_name kind script
|
||||||
|
sicslist setatt $script_name access read_only
|
||||||
|
sicslist setatt $script_name dtype floatvarar
|
||||||
|
sicslist setatt $script_name dlen 100
|
||||||
|
sicslist setatt $script_name klass detector
|
||||||
|
sicslist setatt $script_name mutable false
|
||||||
|
sicslist setatt $script_name long_name y_bin
|
||||||
|
|
||||||
|
##
|
||||||
|
# @brief Provides x_bin boundary array for data axes
|
||||||
|
proc x_bin {args} {
|
||||||
|
set opt [lindex $args 0]
|
||||||
|
set arglist [lrange $args 1 end]
|
||||||
|
set proc_name [namespace origin [lindex [info level 0] 0]]
|
||||||
|
switch -- $opt {
|
||||||
|
"-centres" - "-boundaries" - "-graph_type" {
|
||||||
|
return [calc_axis $proc_name @none @none @none $opt $arglist]
|
||||||
|
}
|
||||||
|
"-arrayname" {
|
||||||
|
return [calc_axis $proc_name 1.0 0.0 [OAT_TABLE -get X_BOUNDARIES] $opt $arglist]
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
return [calc_axis $proc_name 1.0 0.0 [OAT_TABLE -get X_BOUNDARIES] $args]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
set script_name ::histogram_memory::x_bin
|
||||||
|
publish $script_name user
|
||||||
|
sicslist setatt $script_name privilege internal
|
||||||
|
sicslist setatt $script_name kind script
|
||||||
|
sicslist setatt $script_name access read_only
|
||||||
|
sicslist setatt $script_name dtype floatvarar
|
||||||
|
sicslist setatt $script_name dlen 100
|
||||||
|
sicslist setatt $script_name klass detector
|
||||||
|
sicslist setatt $script_name mutable false
|
||||||
|
sicslist setatt $script_name long_name x_bin
|
||||||
|
|
||||||
# requires detector_active_width_mm det_radius_mm
|
# requires detector_active_width_mm det_radius_mm
|
||||||
proc y_pixel_offset {args} {
|
proc y_pixel_offset {args} {
|
||||||
variable state
|
variable state
|
||||||
@@ -285,7 +343,8 @@ namespace eval histogram_memory {
|
|||||||
set max_b [OAT_TABLE -get Y_MAX]
|
set max_b [OAT_TABLE -get Y_MAX]
|
||||||
set min_b [OAT_TABLE -get Y_MIN]
|
set min_b [OAT_TABLE -get Y_MIN]
|
||||||
set scale_factor [expr {$det_height_mm / ($max_b - $min_b)}]
|
set scale_factor [expr {$det_height_mm / ($max_b - $min_b)}]
|
||||||
return [calc_axis $proc_name 1.0 0.0 [OAT_TABLE -get Y_BOUNDARIES] $opt $arglist]
|
set offset 0.0
|
||||||
|
return [calc_axis $proc_name $scale_factor $offset [OAT_TABLE -get Y_BOUNDARIES] $opt $arglist]
|
||||||
}
|
}
|
||||||
"-units" {
|
"-units" {
|
||||||
return "mm"
|
return "mm"
|
||||||
@@ -295,7 +354,8 @@ namespace eval histogram_memory {
|
|||||||
set max_b [OAT_TABLE -get Y_MAX]
|
set max_b [OAT_TABLE -get Y_MAX]
|
||||||
set min_b [OAT_TABLE -get Y_MIN]
|
set min_b [OAT_TABLE -get Y_MIN]
|
||||||
set scale_factor [expr {$det_height_mm / ($max_b - $min_b)}]
|
set scale_factor [expr {$det_height_mm / ($max_b - $min_b)}]
|
||||||
return [calc_axis $proc_name 1.0 0.0 [OAT_TABLE -get Y_BOUNDARIES] $args]
|
set offset 0.0
|
||||||
|
return [calc_axis $proc_name $scale_factor $offset [OAT_TABLE -get Y_BOUNDARIES] $args]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -406,6 +466,8 @@ namespace eval histogram_memory {
|
|||||||
# parameters.\n
|
# parameters.\n
|
||||||
# -get return the value for the named attribute or element\n
|
# -get return the value for the named attribute or element\n
|
||||||
# -attlist list all of the attributes with their values.\n
|
# -attlist list all of the attributes with their values.\n
|
||||||
|
# TODO Maintain "proposed" and "current" tables. Provide a setcurrent command which can
|
||||||
|
# only be called by the upload_config command to set the proposed tables as current
|
||||||
# TODO Allow for top level content in tables and attributes in sub-elements
|
# TODO Allow for top level content in tables and attributes in sub-elements
|
||||||
proc XXX_TABLE {tag attributes element_list args} {
|
proc XXX_TABLE {tag attributes element_list args} {
|
||||||
global hmm_xml
|
global hmm_xml
|
||||||
@@ -450,6 +512,11 @@ proc XXX_TABLE {tag attributes element_list args} {
|
|||||||
set hmm_xml($tag,[string toupper $par]) $val
|
set hmm_xml($tag,[string toupper $par]) $val
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"-set" {
|
||||||
|
foreach {par val} $arglist {
|
||||||
|
set hmm_xml($tag,[string toupper $par]) $val
|
||||||
|
}
|
||||||
|
}
|
||||||
"-get" {
|
"-get" {
|
||||||
set par [string toupper [lindex $arglist 0]]
|
set par [string toupper [lindex $arglist 0]]
|
||||||
if {[info exists hmm_xml($tag,$par)]} {
|
if {[info exists hmm_xml($tag,$par)]} {
|
||||||
@@ -625,18 +692,25 @@ proc OAT_TABLE {args} {
|
|||||||
set hmm_xml(OAT,$coord) $param($coord)
|
set hmm_xml(OAT,$coord) $param($coord)
|
||||||
set b0 [lindex $param($coord) 0]
|
set b0 [lindex $param($coord) 0]
|
||||||
set bstep [expr {[lindex $param($coord) 1] - $b0}]
|
set bstep [expr {[lindex $param($coord) 1] - $b0}]
|
||||||
|
if {$bstep == 0} {
|
||||||
|
return -code error "The generating bin boundaries for $coord are equal"
|
||||||
|
}
|
||||||
if {[info exists param(N${coord}C)]} {
|
if {[info exists param(N${coord}C)]} {
|
||||||
set NO${coord}CH $param(N${coord}C)
|
set NO${coord}CH $param(N${coord}C)
|
||||||
for {set bb $b0; set i 0} {$i <= [set NO${coord}CH]} {incr i; set bb [expr $bb + $bstep] } {
|
for {set bb $b0; set i 0} {$i <= [set NO${coord}CH]} {incr i; set bb [expr $bb + $bstep] } {
|
||||||
lappend hmm_xml(OAT,${coord}_BOUNDARIES) $bb
|
lappend hmm_xml(OAT,${coord}_BOUNDARIES) $bb
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
set bmax [set hmm_xml(OAT,${coord}_MAX)]
|
if {$bstep > 0} {
|
||||||
set brange [expr {$bmax - $b0}]
|
set bfinal [set hmm_xml(OAT,${coord}_MAX)]
|
||||||
set NO${coord}CH [expr {int(floor($brange/$bstep))}]
|
} else {
|
||||||
|
set bfinal [set hmm_xml(OAT,${coord}_MIN)]
|
||||||
|
}
|
||||||
|
set brange [expr {abs($bfinal - $b0)}]
|
||||||
|
set NO${coord}CH [expr {int(floor(abs($brange/$bstep)))}]
|
||||||
for {set bb $b0} {1} {set bb [expr $bb + $bstep] } {
|
for {set bb $b0} {1} {set bb [expr $bb + $bstep] } {
|
||||||
lappend hmm_xml(OAT,${coord}_BOUNDARIES) $bb
|
lappend hmm_xml(OAT,${coord}_BOUNDARIES) $bb
|
||||||
if [expr {abs($bmax - $bb) < abs($bstep)}] { break }
|
if [expr {abs($bfinal - $bb) < abs($bstep)}] { break }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -701,12 +775,14 @@ proc ::histogram_memory::filler_defaults {args} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# XXX DEPRECATED, use upload_config instead.
|
||||||
proc ::histogram_memory::configure_server {instdef} {
|
proc ::histogram_memory::configure_server {instdef} {
|
||||||
clientput "WARNING: ::histogram_memory::configure_server is deprecated, call ::histogram_memory::upload_config instead"
|
clientput "WARNING: ::histogram_memory::configure_server is deprecated, call ::histogram_memory::upload_config instead"
|
||||||
::histogram_memory::upload_config $instdef
|
::histogram_memory::upload_config $instdef
|
||||||
}
|
}
|
||||||
proc ::histogram_memory::upload_config {instdef} {
|
# TODO Set current oat table after uploading proposed oat_table
|
||||||
::histogram_memory::filler_defaults $instdef
|
proc ::histogram_memory::upload_config {filler_defaults} {
|
||||||
|
::histogram_memory::filler_defaults $filler_defaults
|
||||||
#XXX ::histogram_memory::setup
|
#XXX ::histogram_memory::setup
|
||||||
hmm stop
|
hmm stop
|
||||||
hmm configure init 1
|
hmm configure init 1
|
||||||
@@ -724,6 +800,7 @@ proc ::histogram_memory::upload_config {instdef} {
|
|||||||
::histogram_memory::configure_dims
|
::histogram_memory::configure_dims
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# @brief Configure the dimensions for the controlling histogram object, and for
|
# @brief Configure the dimensions for the controlling histogram object, and for
|
||||||
# each auxiliary histogram object.
|
# each auxiliary histogram object.
|
||||||
@@ -790,18 +867,32 @@ proc ::histogram_memory::configure_dims {} {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
proc ::histogram_memory::set_frame_freq {freq} {
|
||||||
|
set clock_scale_ns 1000.0
|
||||||
|
OAT_TABLE -set T_MAX [expr 1.0e9/($freq*$clock_scale_ns)]
|
||||||
|
hmm configure fat_frame_frequency $freq
|
||||||
|
hmm stop
|
||||||
|
hmm init 0
|
||||||
|
hmm init
|
||||||
|
}
|
||||||
|
publish ::histogram_memory::set_frame_freq user
|
||||||
|
definealias set_hmfreq ::histogram_memory::set_frame_freq
|
||||||
|
|
||||||
|
proc ::histogram_memory::t_max {} {
|
||||||
|
set frame_freq [SplitReply [hmm configure fat_frame_frequency]]
|
||||||
|
}
|
||||||
##
|
##
|
||||||
# @brief Sets histogram server to default configuration, initialises SICS histogram memory
|
# @brief Sets histogram server to default configuration, initialises SICS histogram memory
|
||||||
# dictionary values and clears SICS OAT BAT CAT FAT ... tables
|
# dictionary values and clears SICS OAT BAT CAT FAT ... tables
|
||||||
proc ::histogram_memory::_initialize {} {
|
proc ::histogram_memory::_initialize {} {
|
||||||
set configuration "::histogram_memory::returnconfigfile config/hmm/anstohm_linked.xml"
|
set configuration "::histogram_memory::returnconfigfile config/hmm/anstohm_linked.xml"
|
||||||
y_pixel_offset -centres
|
::histogram_memory::y_bin -boundaries
|
||||||
x_pixel_offset -centres
|
::histogram_memory::x_bin -boundaries
|
||||||
time_channel -boundaries
|
::histogram_memory::y_pixel_offset -boundaries
|
||||||
|
::histogram_memory::x_pixel_offset -boundaries
|
||||||
|
::histogram_memory::time_channel -boundaries
|
||||||
::histogram_memory::clear_tables
|
::histogram_memory::clear_tables
|
||||||
#XXX ::histogram_memory::upload_config Filler_defaults
|
#XXX ::histogram_memory::upload_config Filler_defaults
|
||||||
OAT_TABLE -init T_MIN 0 T_MAX 200000
|
|
||||||
FAT_TABLE -init SIZE_PERIOD_MAX 125000000
|
|
||||||
|
|
||||||
foreach hm_obj [sicslist type histmem] {
|
foreach hm_obj [sicslist type histmem] {
|
||||||
$hm_obj configure hmaddress http://das1-[instname].nbi.ansto.gov.au:8080
|
$hm_obj configure hmaddress http://das1-[instname].nbi.ansto.gov.au:8080
|
||||||
@@ -814,6 +905,10 @@ proc ::histogram_memory::_initialize {} {
|
|||||||
hmm configure statuscheck true
|
hmm configure statuscheck true
|
||||||
hmm stop
|
hmm stop
|
||||||
hmm configure statuscheck false
|
hmm configure statuscheck false
|
||||||
|
OAT_TABLE -init
|
||||||
|
OAT_TABLE -set T_MIN 0
|
||||||
|
::histogram_memory::set_frame_freq 50
|
||||||
|
FAT_TABLE -init SIZE_PERIOD_MAX 125000000
|
||||||
hmm configure hmDataPath ../HMData
|
hmm configure hmDataPath ../HMData
|
||||||
hmm configure hmconfigscript $configuration
|
hmm configure hmconfigscript $configuration
|
||||||
::histogram_memory::configure_dims
|
::histogram_memory::configure_dims
|
||||||
@@ -832,31 +927,60 @@ Publish SAT_TABLE user
|
|||||||
|
|
||||||
proc ::histogram_memory::pre_count {} {}
|
proc ::histogram_memory::pre_count {} {}
|
||||||
proc ::histogram_memory::post_count {} {}
|
proc ::histogram_memory::post_count {} {}
|
||||||
namespace eval ::histogram_memory {
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# @brief Start an acquisition, non-blocking by default
|
# @brief Start an acquisition, non-blocking by default
|
||||||
#
|
#
|
||||||
# @param block (optional) default="noblock"
|
# @param block (optional) default="noblock"
|
||||||
proc start {{blocking "block"}} {
|
proc ::histogram_memory::start {{blocking "noblock"}} {
|
||||||
|
set options [list block noblock]
|
||||||
|
if {[lsearch $options $blocking] == -1} {
|
||||||
|
return -code error "Valid options are $options"
|
||||||
|
}
|
||||||
::histogram_memory::pre_count
|
::histogram_memory::pre_count
|
||||||
hmm init 0
|
hmm init 0
|
||||||
hmm init
|
hmm init
|
||||||
hmc start 1000000000 timer pause 1
|
if [catch {hmc start 1000000000 timer pause 1}] {
|
||||||
|
return -code error $::errorInfo
|
||||||
|
}
|
||||||
if {$blocking == "block"} {
|
if {$blocking == "block"} {
|
||||||
blockctr count 0
|
blockctr count 0
|
||||||
::histogram_memory::stop
|
::histogram_memory::stop
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
proc stop {} {
|
|
||||||
# pausing actually stops the acquisition but leaves the
|
##
|
||||||
# histogram server in a ready state for the next acquisition
|
# @brief This sends the magic incantation which stops the histogram server.
|
||||||
|
proc ::histogram_memory::stop {} {
|
||||||
|
if [ catch {
|
||||||
hmm pause
|
hmm pause
|
||||||
|
hmm configure statuscheck true
|
||||||
|
hmm stop
|
||||||
|
hmm configure statuscheck false
|
||||||
::histogram_memory::post_count
|
::histogram_memory::post_count
|
||||||
|
} errmsg ] {
|
||||||
|
return -code error $errmsg
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
##
|
||||||
|
# @brief Allows resume if MULTIPLE_DATASETS=DISABLE, otherwise if MULTIPLE_DATASETS=ENABLE
|
||||||
|
# (the default) this acts like a stop but allows a fast restart.
|
||||||
|
proc ::histogram_memory::pause {} {
|
||||||
|
if [ catch {
|
||||||
|
hmm pause
|
||||||
|
::histogram_memory::post_count
|
||||||
|
} errmsg ] {
|
||||||
|
return -code error $errmsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
##
|
##
|
||||||
# @brief Choose method for controlling acquisition duration.
|
# @brief Choose method for controlling acquisition duration.
|
||||||
command count_method {text:time,monitor,unlimited,period,count,frame method} {
|
proc ::histogram_memory::count_method {text:time,monitor,unlimited,period,count,frame method} {
|
||||||
|
set modes [list time monitor unlimited period count frame]
|
||||||
|
if {[lsearch $modes $method] == -1} {
|
||||||
|
return -code error "Count mode, $method, must be one of $modes"
|
||||||
|
}
|
||||||
hmm configure FAT_COUNT_METHOD $method
|
hmm configure FAT_COUNT_METHOD $method
|
||||||
hmm init 0
|
hmm init 0
|
||||||
hmm init
|
hmm init
|
||||||
@@ -866,32 +990,76 @@ namespace eval ::histogram_memory {
|
|||||||
#
|
#
|
||||||
# @param preset: The interpretation of the preset depends on the count method.
|
# @param preset: The interpretation of the preset depends on the count method.
|
||||||
# @see count_method
|
# @see count_method
|
||||||
command count_size {float: preset} {
|
proc ::histogram_memory::count_size {float: preset} {
|
||||||
hmm configure FAT_COUNT_SIZE $preset
|
hmm configure FAT_COUNT_SIZE [expr 100.0 * $preset]
|
||||||
hmm init 0
|
hmm init 0
|
||||||
hmm init
|
hmm init
|
||||||
}
|
}
|
||||||
##
|
##
|
||||||
# @brief Set stop condition for histogram memory
|
# @brief Set stop condition for histogram memory
|
||||||
#
|
#
|
||||||
# @param stop_method
|
# @param condition
|
||||||
command stop_condition {text:immediate,period condition} {
|
proc ::histogram_memory::stop_condition {text:immediate,period condition} {
|
||||||
array set count_stop {immediate IMMEDIATE period AT_END_OF_PERIOD}
|
array set count_stop {immediate IMMEDIATE period AT_END_OF_PERIOD}
|
||||||
hmm configure FAT_COUNT_STOP $count_stop($condition)
|
hmm configure FAT_COUNT_STOP $count_stop($condition)
|
||||||
hmm init 0
|
hmm init 0
|
||||||
hmm init
|
hmm init
|
||||||
}
|
}
|
||||||
|
namespace eval ::histogram_memory {
|
||||||
|
#TODO Create GumTree commands to setup, start and stop the histmem
|
||||||
|
##
|
||||||
|
# @brief Choose method for controlling acquisition duration.
|
||||||
|
#command mode {text:time,monitor,unlimited,period,count,frame method} {}
|
||||||
|
##
|
||||||
|
# @brief Count until the preset count size has been reached.
|
||||||
|
#
|
||||||
|
# @param preset: The interpretation of the preset depends on the count method.
|
||||||
|
# @see count_method
|
||||||
|
#command preset {float: pre} {}
|
||||||
|
##
|
||||||
|
# @brief Set stop condition for histogram memory
|
||||||
|
#
|
||||||
|
# @param condition
|
||||||
|
#command stop_condition {text:immediate,period condition}
|
||||||
}
|
}
|
||||||
##
|
|
||||||
# @brief Start an acquisition on the histogram server, this blocks by default
|
##
|
||||||
#
|
# @brief Convenience command providing user interface to histogram control
|
||||||
# @param method: count method, available methods are time,monitor,unlimited,period,count,frame
|
#
|
||||||
# @param preset: termination condition
|
# @param cmd is one of start, stop, pause, mode, preset, loadconf
|
||||||
proc ::histogram_memory::count {method preset stop_condition {blockmode "block"}} {
|
# @param args is an optional list of arguments for the given command
|
||||||
array set count_stop {immediate IMMEDIATE period AT_END_OF_PERIOD}
|
proc histmem {cmd args} {
|
||||||
hmm configure FAT_COUNT_METHOD $method
|
if [ catch {
|
||||||
hmm configure FAT_COUNT_STOP $count_stop($stop_condition)
|
switch $cmd {
|
||||||
hmm configure FAT_COUNT_SIZE $preset
|
"start" {
|
||||||
::histogram_memory::start $blockmode
|
eval "::histogram_memory::start $args"
|
||||||
}
|
}
|
||||||
publish ::histogram_memory::count user
|
"stop" {
|
||||||
|
::histogram_memory::stop
|
||||||
|
}
|
||||||
|
"pause" {
|
||||||
|
::histogram_memory::pause
|
||||||
|
}
|
||||||
|
"mode" {
|
||||||
|
eval "::histogram_memory::count_method $args"
|
||||||
|
}
|
||||||
|
"preset" {
|
||||||
|
eval "::histogram_memory::count_size $args"
|
||||||
|
}
|
||||||
|
"loadconf" {
|
||||||
|
# Loads configuration tables (OAT, FAT, ...) to histogram server
|
||||||
|
if {$args == ""} {
|
||||||
|
::histogram_memory::upload_config Filler_defaults
|
||||||
|
} else {
|
||||||
|
eval "::histogram_memory::upload_config $args"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
error "Available commands are, start stop pause mode preset loadconf"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} errmsg ] {
|
||||||
|
return -code error $errmsg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
publish histmem user
|
||||||
|
|||||||
Reference in New Issue
Block a user