mirror of
https://gitee.com/mirrors_PX4/PX4-Autopilot.git
synced 2026-05-27 19:40:05 +08:00
Fix README/cmake format.
This commit is contained in:
committed by
James Goppert
parent
d142ac234c
commit
e7c95fa027
+95
-95
@@ -10,8 +10,8 @@ set(CMAKE_CXX_STANDARD 11)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
if (NOT CMAKE_BUILD_TYPE)
|
||||
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Build type" FORCE)
|
||||
message(STATUS "set build type to ${CMAKE_BUILD_TYPE}")
|
||||
set(CMAKE_BUILD_TYPE "RelWithDebInfo" CACHE STRING "Build type" FORCE)
|
||||
message(STATUS "set build type to ${CMAKE_BUILD_TYPE}")
|
||||
endif()
|
||||
|
||||
set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug;Release;RelWithDebInfo;MinSizeRel;Coverage")
|
||||
@@ -28,133 +28,133 @@ set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
include_directories(${CMAKE_SOURCE_DIR})
|
||||
|
||||
if(SUPPORT_STDIOSTREAM)
|
||||
add_definitions(-DSUPPORT_STDIOSTREAM)
|
||||
add_definitions(-DSUPPORT_STDIOSTREAM)
|
||||
endif()
|
||||
|
||||
set(CMAKE_CXX_FLAGS_COVERAGE
|
||||
"--coverage -fprofile-arcs -ftest-coverage -fno-default-inline -fno-inline -fno-inline-small-functions -fno-elide-constructors"
|
||||
CACHE STRING "Flags used by the C++ compiler during coverage builds" FORCE)
|
||||
"--coverage -fprofile-arcs -ftest-coverage -fno-default-inline -fno-inline -fno-inline-small-functions -fno-elide-constructors"
|
||||
CACHE STRING "Flags used by the C++ compiler during coverage builds" FORCE)
|
||||
set(CMAKE_EXE_LINKER_FLAGS_COVERAGE
|
||||
"--coverage -ftest-coverage -lgcov"
|
||||
CACHE STRING "Flags used for linking binaries during coverage builds" FORCE)
|
||||
"--coverage -ftest-coverage -lgcov"
|
||||
CACHE STRING "Flags used for linking binaries during coverage builds" FORCE)
|
||||
mark_as_advanced(CMAKE_CXX_FLAGS_COVERAGE CMAKE_C_FLAGS_COVERAGE CMAKE_EXE_LINKER_FLAGS_COVERAGE)
|
||||
|
||||
add_compile_options(
|
||||
-pedantic
|
||||
-Wall
|
||||
-Warray-bounds
|
||||
-Wcast-align
|
||||
-Wcast-qual
|
||||
-Wconversion
|
||||
-Wctor-dtor-privacy
|
||||
-Wdisabled-optimization
|
||||
-Werror
|
||||
-Wextra
|
||||
-Wfloat-equal
|
||||
-Wformat-security
|
||||
-Wformat=2
|
||||
-Winit-self
|
||||
-Wlogical-op
|
||||
-Wmissing-declarations
|
||||
-Wmissing-include-dirs
|
||||
-Wno-sign-compare
|
||||
-Wno-unused
|
||||
-Wno-unused-parameter
|
||||
-Wnoexcept
|
||||
-Wold-style-cast
|
||||
-Woverloaded-virtual
|
||||
-Wpointer-arith
|
||||
-Wredundant-decls
|
||||
-Wreorder
|
||||
-Wshadow
|
||||
-Wsign-conversion
|
||||
-Wsign-promo
|
||||
-Wstrict-null-sentinel
|
||||
-Wswitch-default
|
||||
-Wundef
|
||||
-Wuninitialized
|
||||
-Wunused-variable
|
||||
)
|
||||
-pedantic
|
||||
-Wall
|
||||
-Warray-bounds
|
||||
-Wcast-align
|
||||
-Wcast-qual
|
||||
-Wconversion
|
||||
-Wctor-dtor-privacy
|
||||
-Wdisabled-optimization
|
||||
-Werror
|
||||
-Wextra
|
||||
-Wfloat-equal
|
||||
-Wformat-security
|
||||
-Wformat=2
|
||||
-Winit-self
|
||||
-Wlogical-op
|
||||
-Wmissing-declarations
|
||||
-Wmissing-include-dirs
|
||||
-Wno-sign-compare
|
||||
-Wno-unused
|
||||
-Wno-unused-parameter
|
||||
-Wnoexcept
|
||||
-Wold-style-cast
|
||||
-Woverloaded-virtual
|
||||
-Wpointer-arith
|
||||
-Wredundant-decls
|
||||
-Wreorder
|
||||
-Wshadow
|
||||
-Wsign-conversion
|
||||
-Wsign-promo
|
||||
-Wstrict-null-sentinel
|
||||
-Wswitch-default
|
||||
-Wundef
|
||||
-Wuninitialized
|
||||
-Wunused-variable
|
||||
)
|
||||
|
||||
# clang tolerate unknown gcc options
|
||||
if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
|
||||
add_compile_options(
|
||||
-Wno-error=unused-command-line-argument-hard-error-in-future
|
||||
-Wno-unknown-warning-option
|
||||
)
|
||||
add_compile_options(
|
||||
-Wno-error=unused-command-line-argument-hard-error-in-future
|
||||
-Wno-unknown-warning-option
|
||||
)
|
||||
else()
|
||||
add_compile_options(
|
||||
-Wstrict-overflow=5
|
||||
)
|
||||
add_compile_options(
|
||||
-Wstrict-overflow=5
|
||||
)
|
||||
endif()
|
||||
|
||||
# santiziers (ASAN, UBSAN)
|
||||
if(ASAN)
|
||||
message(STATUS "address sanitizer enabled")
|
||||
message(STATUS "address sanitizer enabled")
|
||||
|
||||
# environment variables
|
||||
# ASAN_OPTIONS=detect_stack_use_after_return=1
|
||||
# ASAN_OPTIONS=check_initialization_order=1
|
||||
# environment variables
|
||||
# ASAN_OPTIONS=detect_stack_use_after_return=1
|
||||
# ASAN_OPTIONS=check_initialization_order=1
|
||||
|
||||
add_compile_options(
|
||||
-fsanitize=address
|
||||
-g3
|
||||
-O1
|
||||
-fno-omit-frame-pointer
|
||||
)
|
||||
add_compile_options(
|
||||
-fsanitize=address
|
||||
-g3
|
||||
-O1
|
||||
-fno-omit-frame-pointer
|
||||
)
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address)
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address)
|
||||
|
||||
elseif(UBSAN)
|
||||
message(STATUS "undefined behaviour sanitizer enabled")
|
||||
message(STATUS "undefined behaviour sanitizer enabled")
|
||||
|
||||
add_compile_options(
|
||||
-fsanitize=undefined
|
||||
-fsanitize=integer
|
||||
-g3
|
||||
-O1
|
||||
-fno-omit-frame-pointer
|
||||
)
|
||||
add_compile_options(
|
||||
-fsanitize=undefined
|
||||
-fsanitize=integer
|
||||
-g3
|
||||
-O1
|
||||
-fno-omit-frame-pointer
|
||||
)
|
||||
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined)
|
||||
set(CMAKE_EXE_LINKER_FLAGS ${CMAKE_EXE_LINKER_FLAGS} -fsanitize=undefined)
|
||||
endif()
|
||||
|
||||
|
||||
add_custom_target(check COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure)
|
||||
|
||||
if(TESTING)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
add_dependencies(check test_build)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
add_dependencies(check test_build)
|
||||
|
||||
add_custom_target(clang-tidy COMMAND clang-tidy -p . ${CMAKE_SOURCE_DIR}/test/*.cpp)
|
||||
add_dependencies(clang-tidy test_build)
|
||||
add_custom_target(clang-tidy COMMAND clang-tidy -p . ${CMAKE_SOURCE_DIR}/test/*.cpp)
|
||||
add_dependencies(clang-tidy test_build)
|
||||
endif()
|
||||
|
||||
if(FORMAT)
|
||||
set(astyle_exe ${CMAKE_BINARY_DIR}/astyle/src/bin/astyle)
|
||||
add_custom_command(OUTPUT ${astyle_exe}
|
||||
COMMAND wget http://sourceforge.net/projects/astyle/files/astyle/astyle%202.06/astyle_2.06_linux.tar.gz -O /tmp/astyle.tar.gz
|
||||
COMMAND tar -xvf /tmp/astyle.tar.gz
|
||||
COMMAND cd astyle/src && make -f ../build/gcc/Makefile
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
set(astyle_exe ${CMAKE_BINARY_DIR}/astyle/src/bin/astyle)
|
||||
add_custom_command(OUTPUT ${astyle_exe}
|
||||
COMMAND wget http://sourceforge.net/projects/astyle/files/astyle/astyle%202.06/astyle_2.06_linux.tar.gz -O /tmp/astyle.tar.gz
|
||||
COMMAND tar -xvf /tmp/astyle.tar.gz
|
||||
COMMAND cd astyle/src && make -f ../build/gcc/Makefile
|
||||
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
|
||||
)
|
||||
|
||||
add_custom_target(check_format
|
||||
COMMAND scripts/format.sh ${astyle_exe} 0
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
DEPENDS ${astyle_exe}
|
||||
VERBATIM
|
||||
)
|
||||
add_custom_target(check_format
|
||||
COMMAND scripts/format.sh ${astyle_exe} 0
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
DEPENDS ${astyle_exe}
|
||||
VERBATIM
|
||||
)
|
||||
|
||||
add_custom_target(format
|
||||
COMMAND scripts/format.sh ${astyle_exe} 1
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
VERBATIM
|
||||
DEPENDS ${astyle_exe}
|
||||
)
|
||||
add_custom_target(format
|
||||
COMMAND scripts/format.sh ${astyle_exe} 1
|
||||
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
|
||||
VERBATIM
|
||||
DEPENDS ${astyle_exe}
|
||||
)
|
||||
|
||||
add_dependencies(check check_format)
|
||||
add_dependencies(check check_format)
|
||||
endif()
|
||||
|
||||
set(CPACK_PACKAGE_VERSION_MAJOR ${VERSION_MAJOR})
|
||||
@@ -163,4 +163,4 @@ set(CPACK_PACKAGE_VERSION_PATCH ${VERSION_PATCH})
|
||||
set(CPACK_PACKAGE_CONTACT "james.goppert@gmail.com")
|
||||
include(CPack)
|
||||
|
||||
# vim: set noet fenc=utf-8 ft=cmake ff=unix sts=0 sw=4 ts=4 :
|
||||
# vim: set et fenc=utf-8 ft=cmake ff=unix sts=0 sw=4 ts=4 :
|
||||
|
||||
@@ -16,111 +16,112 @@ A simple and efficient template based matrix library.
|
||||
## Library Overview
|
||||
|
||||
* matrix/math.hpp : Provides matrix math routines.
|
||||
* Matrix (MxN)
|
||||
* Square Matrix (MxM, has inverse)
|
||||
* Vector (Mx1)
|
||||
* Scalar (1x1)
|
||||
* Quaternion
|
||||
* Dcm
|
||||
* Euler (Body 321)
|
||||
* Axis Angle
|
||||
* Matrix (MxN)
|
||||
* Square Matrix (MxM, has inverse)
|
||||
* Vector (Mx1)
|
||||
* Scalar (1x1)
|
||||
* Quaternion
|
||||
* Dcm
|
||||
* Euler (Body 321)
|
||||
* Axis Angle
|
||||
|
||||
* matrix/filter.hpp : Provides filtering routines.
|
||||
* kalman_correct
|
||||
* kalman_correct
|
||||
|
||||
* matrix/integrate.hpp : Provides integration routines.
|
||||
* integrate_rk4 (Runge-Kutta 4th order)
|
||||
* integrate_rk4 (Runge-Kutta 4th order)
|
||||
|
||||
## Example
|
||||
|
||||
See the test directory for detailed examples. Some simple examples are included below:
|
||||
|
||||
```c++
|
||||
// define an euler angle (Body 3(yaw)-2(pitch)-1(roll) rotation)
|
||||
float roll = 0.1f;
|
||||
float pitch = 0.2f;
|
||||
float yaw = 0.3f;
|
||||
Eulerf euler(roll, pitch, yaw);
|
||||
// define an euler angle (Body 3(yaw)-2(pitch)-1(roll) rotation)
|
||||
float roll = 0.1f;
|
||||
float pitch = 0.2f;
|
||||
float yaw = 0.3f;
|
||||
Eulerf euler(roll, pitch, yaw);
|
||||
|
||||
// convert to quaternion from euler
|
||||
Quatf q_nb(euler);
|
||||
// convert to quaternion from euler
|
||||
Quatf q_nb(euler);
|
||||
|
||||
// convert to DCM from quaternion
|
||||
Dcmf dcm(q_nb);
|
||||
// convert to DCM from quaternion
|
||||
Dcmf dcm(q_nb);
|
||||
|
||||
// you can assign a rotation instance that already exist to another rotation instance, e.g.
|
||||
dcm = euler;
|
||||
// you can assign a rotation instance that already exist to another rotation instance, e.g.
|
||||
dcm = euler;
|
||||
|
||||
// you can also directly create a DCM instance from euler angles like this
|
||||
dcm = Eulerf(roll, pitch, yaw);
|
||||
// you can also directly create a DCM instance from euler angles like this
|
||||
dcm = Eulerf(roll, pitch, yaw);
|
||||
|
||||
// create an axis angle representation from euler angles
|
||||
AxisAngle<float> axis_angle = euler;
|
||||
// create an axis angle representation from euler angles
|
||||
AxisAngle<float> axis_angle = euler;
|
||||
|
||||
// use axis angle to initialize a DCM
|
||||
Dcmf dcm2(AxisAngle(1, 2, 3));
|
||||
|
||||
// use axis angle with axis/angle separated to init DCM
|
||||
Dcmf dcm3(AxisAngle(Vector3f(1, 0, 0), 0.2));
|
||||
// use axis angle to initialize a DCM
|
||||
Dcmf dcm2(AxisAngle(1, 2, 3));
|
||||
|
||||
// use axis angle with axis/angle separated to init DCM
|
||||
Dcmf dcm3(AxisAngle(Vector3f(1, 0, 0), 0.2));
|
||||
|
||||
// do some kalman filtering
|
||||
const size_t n_x = 5;
|
||||
const size_t n_y = 3;
|
||||
// do some kalman filtering
|
||||
const size_t n_x = 5;
|
||||
const size_t n_y = 3;
|
||||
|
||||
// define matrix sizes
|
||||
SquareMatrix<float, n_x> P;
|
||||
Vector<float, n_x> x;
|
||||
Vector<float, n_y> y;
|
||||
Matrix<float, n_y, n_x> C;
|
||||
SquareMatrix<float, n_y> R;
|
||||
SquareMatrix<float, n_y> S;
|
||||
Matrix<float, n_x, n_y> K;
|
||||
// define matrix sizes
|
||||
SquareMatrix<float, n_x> P;
|
||||
Vector<float, n_x> x;
|
||||
Vector<float, n_y> y;
|
||||
Matrix<float, n_y, n_x> C;
|
||||
SquareMatrix<float, n_y> R;
|
||||
SquareMatrix<float, n_y> S;
|
||||
Matrix<float, n_x, n_y> K;
|
||||
|
||||
// define measurement matrix
|
||||
C = zero<float, n_y, n_x>(); // or C.setZero()
|
||||
C(0,0) = 1;
|
||||
C(1,1) = 2;
|
||||
C(2,2) = 3;
|
||||
// define measurement matrix
|
||||
C = zero<float, n_y, n_x>(); // or C.setZero()
|
||||
C(0,0) = 1;
|
||||
C(1,1) = 2;
|
||||
C(2,2) = 3;
|
||||
|
||||
// set x to zero
|
||||
x = zero<float, n_x, 1>(); // or x.setZero()
|
||||
// set x to zero
|
||||
x = zero<float, n_x, 1>(); // or x.setZero()
|
||||
|
||||
// set P to identity * 0.01
|
||||
P = eye<float, n_x>()*0.01;
|
||||
// set P to identity * 0.01
|
||||
P = eye<float, n_x>()*0.01;
|
||||
|
||||
// set R to identity * 0.1
|
||||
R = eye<float, n_y>()*0.1;
|
||||
// set R to identity * 0.1
|
||||
R = eye<float, n_y>()*0.1;
|
||||
|
||||
// measurement
|
||||
y(0) = 1;
|
||||
y(1) = 2;
|
||||
y(2) = 3;
|
||||
// measurement
|
||||
y(0) = 1;
|
||||
y(1) = 2;
|
||||
y(2) = 3;
|
||||
|
||||
// innovation
|
||||
r = y - C*x;
|
||||
// innovation
|
||||
r = y - C*x;
|
||||
|
||||
// innovations variance
|
||||
S = C*P*C.T() + R;
|
||||
// innovations variance
|
||||
S = C*P*C.T() + R;
|
||||
|
||||
// Kalman gain matrix
|
||||
K = P*C.T()*S.I();
|
||||
// S.I() is the inverse, defined for SquareMatrix
|
||||
// Kalman gain matrix
|
||||
K = P*C.T()*S.I();
|
||||
// S.I() is the inverse, defined for SquareMatrix
|
||||
|
||||
// correction
|
||||
x += K*r;
|
||||
// correction
|
||||
x += K*r;
|
||||
|
||||
// slicing
|
||||
// slicing
|
||||
float data[9] = {0, 2, 3,
|
||||
4, 5, 6,
|
||||
7, 8, 10
|
||||
};
|
||||
SquareMatrix<float, 3> A(data);
|
||||
|
||||
// Slice a 3,3 matrix starting at row 1, col 0,
|
||||
// with size 2 x 3, warning, no size checking
|
||||
// Slice a 3,3 matrix starting at row 1, col 0,
|
||||
// with size 2 x 3, warning, no size checking
|
||||
Matrix<float, 2, 3> B(A.slice<2, 3>(1, 0));
|
||||
|
||||
// this results in:
|
||||
// 4, 5, 6
|
||||
// 7, 8, 10
|
||||
// this results in:
|
||||
// 4, 5, 6
|
||||
// 7, 8, 10
|
||||
```
|
||||
<!-- vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 : -->
|
||||
|
||||
+14
-12
@@ -12,17 +12,19 @@ format_wildcards="""
|
||||
|
||||
if [[ $format -eq 1 ]]
|
||||
then
|
||||
echo formatting
|
||||
$astyle ${format_wildcards}
|
||||
echo formatting
|
||||
$astyle ${format_wildcards}
|
||||
else
|
||||
echo checking format...
|
||||
$astyle --dry-run ${format_wildcards} | grep Formatted &>/dev/null
|
||||
if [[ $? -eq 0 ]]
|
||||
then
|
||||
echo Error: need to format
|
||||
echo "From cmake build directory run: 'make format'"
|
||||
exit 1
|
||||
fi
|
||||
echo no formatting needed
|
||||
exit 0
|
||||
echo checking format...
|
||||
$astyle --dry-run ${format_wildcards} | grep Formatted &>/dev/null
|
||||
if [[ $? -eq 0 ]]
|
||||
then
|
||||
echo Error: need to format
|
||||
echo "From cmake build directory run: 'make format'"
|
||||
exit 1
|
||||
fi
|
||||
echo no formatting needed
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# vim: set et fenc=utf-8 ff=unix sts=0 sw=4 ts=4 :
|
||||
|
||||
+46
-44
@@ -1,57 +1,59 @@
|
||||
set(tests
|
||||
setIdentity
|
||||
inverse
|
||||
slice
|
||||
matrixMult
|
||||
vectorAssignment
|
||||
matrixAssignment
|
||||
matrixScalarMult
|
||||
transpose
|
||||
vector
|
||||
vector2
|
||||
vector3
|
||||
attitude
|
||||
filter
|
||||
integration
|
||||
squareMatrix
|
||||
helper
|
||||
hatvee
|
||||
copyto
|
||||
)
|
||||
setIdentity
|
||||
inverse
|
||||
slice
|
||||
matrixMult
|
||||
vectorAssignment
|
||||
matrixAssignment
|
||||
matrixScalarMult
|
||||
transpose
|
||||
vector
|
||||
vector2
|
||||
vector3
|
||||
attitude
|
||||
filter
|
||||
integration
|
||||
squareMatrix
|
||||
helper
|
||||
hatvee
|
||||
copyto
|
||||
)
|
||||
|
||||
add_custom_target(test_build)
|
||||
foreach(test_name ${tests})
|
||||
add_executable(${test_name}
|
||||
${test_name}.cpp)
|
||||
add_test(test_${test_name} ${test_name})
|
||||
add_dependencies(test_build ${test_name})
|
||||
add_executable(${test_name}
|
||||
${test_name}.cpp)
|
||||
add_test(test_${test_name} ${test_name})
|
||||
add_dependencies(test_build ${test_name})
|
||||
endforeach()
|
||||
|
||||
if (${CMAKE_BUILD_TYPE} STREQUAL "Coverage")
|
||||
|
||||
add_custom_target(coverage_build
|
||||
COMMAND ${CMAKE_CTEST_COMMAND}
|
||||
COMMAND lcov --capture --directory . --output-file coverage.info
|
||||
COMMAND lcov --summary coverage.info
|
||||
WORKING_DIRECTORY ${CMAKE_BUILD_DIR}
|
||||
DEPENDS test_build
|
||||
)
|
||||
add_custom_target(coverage_build
|
||||
COMMAND ${CMAKE_CTEST_COMMAND}
|
||||
COMMAND lcov --capture --directory . --output-file coverage.info
|
||||
COMMAND lcov --summary coverage.info
|
||||
WORKING_DIRECTORY ${CMAKE_BUILD_DIR}
|
||||
DEPENDS test_build
|
||||
)
|
||||
|
||||
add_custom_target(coverage_html
|
||||
COMMAND genhtml coverage.info --output-directory out
|
||||
COMMAND x-www-browser out/index.html
|
||||
WORKING_DIRECTORY ${CMAKE_BUILD_DIR}
|
||||
DEPENDS coverage_build
|
||||
)
|
||||
add_custom_target(coverage_html
|
||||
COMMAND genhtml coverage.info --output-directory out
|
||||
COMMAND x-www-browser out/index.html
|
||||
WORKING_DIRECTORY ${CMAKE_BUILD_DIR}
|
||||
DEPENDS coverage_build
|
||||
)
|
||||
|
||||
set(coverage_deps
|
||||
coverage_build)
|
||||
set(coverage_deps
|
||||
coverage_build)
|
||||
|
||||
if (COV_HTML)
|
||||
list(APPEND coverage_deps coverage_html)
|
||||
endif()
|
||||
if (COV_HTML)
|
||||
list(APPEND coverage_deps coverage_html)
|
||||
endif()
|
||||
|
||||
add_custom_target(coverage
|
||||
DEPENDS ${coverage_deps}
|
||||
)
|
||||
add_custom_target(coverage
|
||||
DEPENDS ${coverage_deps}
|
||||
)
|
||||
endif()
|
||||
|
||||
# vim: set et fenc=utf-8 ft=cmake ff=unix sts=0 sw=4 ts=4 :
|
||||
|
||||
Reference in New Issue
Block a user