Skip to content

Commit 8db4231

Browse files
authored
ThinCurr: Improvements to mesh/model handling and HODLR compression (OpenFUSIONToolkit#332)
- Support resistivity and coil definitions within the mesh file itself, instead of relying on a separate XML file * Separate holes and jumpers in mesh definition for improved clarity * Holes are now checked when a model is loaded, using the automated script, to avoid accidentally loading malformed meshes * Define coils generically in file to enable runtime selection of Icoil vs Vcoil * Add a new `OFT_ThinCurr_mesh.py` script that can be used set the above properties and modify/merge ThinCurr mesh files - Improve HODLR performance and robustness * HODLR tolerances can now be set from within the Python API using `set_hodlr_options()` * HODLR tolerances now support mesh-relative setting using the average cell size (default) * Improve data-locality in parallelization of HODLR operators - Improve the performance of the hole-finding script `OFT_ThinCurr_holes.py` - Add support for variable timestep size in `ThinCurr.run_td()` (requires `direct=False`) - Increase magnetic field operator finite-difference step size and make user-configurable - Update Icoil source handling in `ThinCurr.run_td()` to be more consistent across Vcoil/Icoil usage - Reported iterations/time in `ThinCurr.run_td()` are now the average across the printing frequency - Standardize mesh reading and writing across Python API - Add documentation pages for all OFT python scripts (eg. `OFT_convert_Cubit.py`) - Remove legacy ThinCurr fortran executables This PR makes the following **changes to default settings/behavior**: - The default tolerances for HODLR operators are now mesh size based (10E-5; relative and scaling with RMS edge size) - The default step size for mesh B-field calculations is larger and mesh based (1E-2; relative and scaling with RMS edge size) This PR makes the following **backwards-compatible changes to the Python API**: - A new `no_verify_holes` argument to `ThinCurr.setup_model()` to optionally skip the newly added hole verification on model load - A new `B_dx` argument to `ThinCurr.compute_Bmat()` to set the finite difference step size - A new `times` argument to `ThinCurr.run_td()` to enable specifying variable timesteps in a simulation - A new `ThinCurr.set_hodlr_options()` method to enable adjusting HODLR tolerances and other parameters - A new `ThinCurr.update_coil_types()` method to enable setting coil types (`Icoil` or `Vcoil`) at runtime -------------- Co-authored by: Claude Opus 4.8
1 parent d08f001 commit 8db4231

69 files changed

Lines changed: 3420 additions & 3281 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/CMakeLists.txt

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ option(OFT_PACKAGE_PYTHON "Setup Python packaging?" ON)
2929
option(OFT_COVERAGE "Build with code coverage information?" OFF)
3030
option(OFT_EXECSTACK "Allow executable stack in shared libraries/executables?" OFF)
3131
option(OFT_TOKAMAKER_LEGACY "UNSUPPORTED: Build legacy TokaMaker executables?" OFF)
32-
option(OFT_THINCURR_LEGACY "DEPRECATED: Build legacy ThinCurr executables?" OFF)
32+
option(OFT_THINCURR_LEGACY "UNSUPPORTED: Build legacy ThinCurr executables?" OFF)
3333
option(OFT_PY_KERNEL "Python kernel name to use for testing examples" "python3")
3434
set(OFT_MPI_PLEN "4" CACHE STRING "Size of strings for MPI processor index")
3535
set(OFT_PATH_SLEN "200" CACHE STRING "Size of path strings")
@@ -59,15 +59,13 @@ if(OFT_PACKAGE_BUILD AND OFT_PACKAGE_NIGHTLY)
5959
set( OFT_VER_SUFFIX "-${GIT_REV_HASH}" CACHE INTERNAL "Version suffix" )
6060
endif()
6161

62-
if(OFT_BUILD_DOCS OR OFT_PACKAGE_BUILD OR OFT_DEBUG_STACK)
63-
if(OFT_PACKAGE_BUILD AND OFT_PACKAGE_PYTHON)
64-
if(APPLE AND (NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET))
65-
message( FATAL_ERROR "Python package build on macOS requires the MACOSX_DEPLOYMENT_TARGET environment variable to be set" )
66-
endif()
67-
set(Python3_FIND_VIRTUALENV ONLY)
62+
if(OFT_PACKAGE_BUILD AND OFT_PACKAGE_PYTHON)
63+
if(APPLE AND (NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET))
64+
message( FATAL_ERROR "Python package build on macOS requires the MACOSX_DEPLOYMENT_TARGET environment variable to be set" )
6865
endif()
69-
find_package(Python3 COMPONENTS Interpreter REQUIRED)
66+
set(Python3_FIND_VIRTUALENV ONLY)
7067
endif()
68+
find_package(Python3 COMPONENTS Interpreter REQUIRED)
7169

7270
########################
7371
# Build documentation
@@ -195,7 +193,7 @@ elseif("${CMAKE_Fortran_COMPILER_ID}" MATCHES "GNU")
195193
set( CMAKE_Fortran_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Werror=line-truncation -Wconversion" )
196194
if(NOT OFT_PACKAGE_BUILD)
197195
if(OFT_DEBUG_CHECK)
198-
set( CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} -fcheck=all" )
196+
set( CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} -fcheck=all,no-array-temps" )
199197
elseif(OFT_DEBUG_SANITIZER)
200198
set( CMAKE_Fortran_FLAGS_DEBUG "${CMAKE_Fortran_FLAGS_DEBUG} -fsanitize=address" )
201199
endif()
@@ -430,6 +428,9 @@ endif()
430428
if( OFT_TOKAMAKER_LEGACY )
431429
message( FATAL_ERROR "Legacy TokaMaker functionality is no longer available." )
432430
endif()
431+
if( OFT_THINCURR_LEGACY )
432+
message( FATAL_ERROR "Legacy ThinCurr functionality is no longer available." )
433+
endif()
433434
# Include files
434435
add_subdirectory( include )
435436
include_directories( ${CMAKE_BINARY_DIR}/include )
@@ -533,9 +534,7 @@ if( OFT_BUILD_PYTHON )
533534
set(OFT_SHARED_LIBS ${ARGV0} ${OFT_SHARED_LIBS} CACHE INTERNAL "package_shared_libs")
534535
endfunction(oft_add_shared)
535536
# Library directories
536-
add_subdirectory( python/OpenFUSIONToolkit )
537-
add_subdirectory( python/wrappers )
538-
add_subdirectory( python/scripts )
537+
add_subdirectory( python )
539538
endif()
540539

541540
########################

src/base/oft_io.F90

Lines changed: 106 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ MODULE oft_io
111111
!------------------------------------------------------------------------------
112112
INTERFACE hdf5_read
113113
MODULE PROCEDURE hdf5_read_string
114+
MODULE PROCEDURE hdf5_read_strings
114115
MODULE PROCEDURE hdf5_read_scalar_r8
115116
MODULE PROCEDURE hdf5_read_scalar_i4
116117
MODULE PROCEDURE hdf5_read_scalar_l
@@ -589,7 +590,8 @@ function hdf5_field_exist(filepath,path) result(exists)
589590
character(LEN=*), intent(in) :: path !< Path of field in file
590591
integer :: access_flag,error,subpath
591592
integer(HID_T) :: file_id,dset_id
592-
logical :: exists
593+
logical :: exists,exists_lc
594+
character(LEN=:), allocatable :: path_lc
593595
exists=oft_file_exist(filepath)
594596
IF(.NOT.exists)RETURN
595597
DEBUG_STACK_PUSH
@@ -604,15 +606,27 @@ function hdf5_field_exist(filepath,path) result(exists)
604606
IF(subpath<LEN_TRIM(path).AND.path(subpath:subpath)/="/")CYCLE
605607
CALL h5lexists_f(file_id, "/"//path(1:subpath), exists, error)
606608
END DO
609+
!---Check for lowercase variant of path (HDF5 is case sensitive)
610+
IF(.NOT.exists)THEN
611+
path_lc=TRIM(path)
612+
CALL string_to_lower(path_lc)
613+
exists_lc=.TRUE.
614+
DO subpath=1,LEN_TRIM(path)
615+
IF(.NOT.exists_lc)EXIT
616+
IF(subpath<LEN_TRIM(path).AND.path(subpath:subpath)/="/")CYCLE
617+
CALL h5lexists_f(file_id, "/"//path_lc(1:subpath), exists_lc, error)
618+
END DO
619+
IF(exists_lc)THEN
620+
CALL oft_warn('Case variant of path exists in HDF5 file: '//TRIM(path))
621+
END IF
622+
END IF
607623
!---Close file and finalize HDF5
608624
call h5fclose_f(file_id, error)
609625
call h5close_f(error)
610626
DEBUG_STACK_POP
611627
end function hdf5_field_exist
612628
!------------------------------------------------------------------------------
613629
!> Test for exitence of a field in a HDF5 file
614-
!!
615-
!! @result Logical flag indicating existence of field and file
616630
!------------------------------------------------------------------------------
617631
subroutine hdf5_field_get_sizes(filepath,path,ndims,dim_sizes)
618632
character(LEN=*), intent(in) :: filepath !< Path to file
@@ -634,15 +648,32 @@ subroutine hdf5_field_get_sizes(filepath,path,ndims,dim_sizes)
634648
CALL h5dopen_f(file_id, "/"//TRIM(path), dset_id, error)
635649
IF(error==0)THEN
636650
CALL h5dget_space_f(dset_id, dspace_id, error)
637-
CALL h5sget_simple_extent_ndims_f(dspace_id, ndims, error)
638-
ALLOCATE(dim_sizes(ndims),tmp_sizes(ndims),maxdims(ndims))
639-
CALL h5sget_simple_extent_dims_f(dspace_id, tmp_sizes, maxdims, error)
640-
dim_sizes=INT(tmp_sizes,4)
641-
DEALLOCATE(tmp_sizes,maxdims)
642-
!---Close dataspace/set
643-
call h5sclose_f(dspace_id, error)
644-
call h5dclose_f(dset_id, error)
651+
IF(error==0)THEN
652+
CALL h5sget_simple_extent_ndims_f(dspace_id, ndims, error)
653+
IF(error==0)THEN
654+
ALLOCATE(dim_sizes(ndims),tmp_sizes(ndims),maxdims(ndims))
655+
CALL h5sget_simple_extent_dims_f(dspace_id, tmp_sizes, maxdims, error)
656+
IF(error>=0)THEN
657+
dim_sizes=INT(tmp_sizes,4)
658+
ELSE
659+
ndims=-6
660+
DEALLOCATE(dim_sizes)
661+
END IF
662+
DEALLOCATE(tmp_sizes,maxdims)
663+
!---Close dataspace/set
664+
call h5sclose_f(dspace_id, error)
665+
ELSE
666+
ndims=-5
667+
END IF
668+
call h5dclose_f(dset_id, error)
669+
ELSE
670+
ndims=-4
671+
END IF
672+
ELSE
673+
ndims=-3
645674
END IF
675+
ELSE
676+
ndims=-2
646677
END IF
647678
!---Close file and finalize HDF5
648679
call h5fclose_f(file_id, error)
@@ -1204,10 +1235,55 @@ subroutine hdf5_read_string(string,filename,path,success)
12041235
character(LEN=*), intent(in) :: filename !< Path to file
12051236
character(LEN=*), intent(in) :: path !< Variable path in file
12061237
logical, optional, intent(out) :: success !< Successful read?
1238+
integer(i4) :: i
1239+
character(LEN=:), allocatable :: strings(:)
1240+
IF(ALLOCATED(string))DEALLOCATE(string)
1241+
CALL hdf5_read_strings(strings,filename,path,success)
1242+
IF(PRESENT(success))THEN
1243+
IF(.NOT.success)RETURN
1244+
END IF
1245+
IF(.NOT.ALLOCATED(strings))THEN
1246+
IF(PRESENT(success))THEN
1247+
success=.FALSE.
1248+
RETURN
1249+
ELSE
1250+
CALL oft_abort('Error reading string from file: '//TRIM(filename)//' path: '//TRIM(path), &
1251+
'hdf5_read_string',__FILE__)
1252+
END IF
1253+
END IF
1254+
IF(SIZE(strings)/=1)THEN
1255+
!---Handle strings stored as a character array
1256+
IF(LEN(strings(1))==1)THEN
1257+
ALLOCATE(character(LEN=SIZE(strings)) :: string)
1258+
DO i=1,SIZE(strings)
1259+
string(i:i)=strings(i) ! Concatenate single-characters into one string
1260+
END DO
1261+
DEALLOCATE(strings)
1262+
RETURN
1263+
END IF
1264+
IF(PRESENT(success))THEN
1265+
success=.FALSE.
1266+
RETURN
1267+
ELSE
1268+
CALL oft_abort('Error reading string from file: '//TRIM(filename)//' path: '//TRIM(path), &
1269+
'hdf5_read_string',__FILE__)
1270+
END IF
1271+
END IF
1272+
string = strings(1)
1273+
DEALLOCATE(strings)
1274+
end subroutine hdf5_read_string
1275+
!------------------------------------------------------------------------------
1276+
!> String implementation of \ref oft_io::hdf5_read
1277+
!------------------------------------------------------------------------------
1278+
subroutine hdf5_read_strings(strings,filename,path,success)
1279+
character(LEN=:), allocatable, intent(inout) :: strings(:) !< String to read from file
1280+
character(LEN=*), intent(in) :: filename !< Path to file
1281+
character(LEN=*), intent(in) :: path !< Variable path in file
1282+
logical, optional, intent(out) :: success !< Successful read?
12071283
integer(i4) :: error
12081284
integer(i4), parameter :: one=1,zero=0
1209-
INTEGER(HSIZE_T) :: space_count
1210-
integer(HID_T) :: file_id,dset_id,dspace_id
1285+
INTEGER(HSIZE_T) :: space_count,string_size
1286+
integer(HID_T) :: file_id,dset_id,dspace_id,type_id
12111287
INTEGER(HSIZE_T), DIMENSION(1) :: dims
12121288
! DEBUG_STACK_PUSH
12131289
IF(PRESENT(success))THEN
@@ -1217,23 +1293,30 @@ subroutine hdf5_read_string(string,filename,path,success)
12171293
!---Initialize HDF5 and open file
12181294
call h5open_f(error)
12191295
call h5fopen_f(TRIM(filename), H5F_ACC_RDONLY_F, file_id, error)
1220-
IF(error/=0)GOTO 103
1296+
IF(error/=0)GOTO 104
12211297
CALL h5dopen_f(file_id, "/"//TRIM(path), dset_id, error)
1298+
IF(error/=0)GOTO 103
1299+
!---Get string size
1300+
CALL H5Dget_type_f(dset_id, type_id, error)
12221301
IF(error/=0)GOTO 102
1223-
!---
1302+
CALL H5Tget_size_f(type_id, string_size, error)
1303+
IF(error/=0)GOTO 101
1304+
!---Get dataset size (should be 1)
12241305
CALL h5dget_space_f(dset_id, dspace_id, error)
12251306
IF(error/=0)GOTO 101
12261307
CALL h5sget_simple_extent_npoints_f(dspace_id, space_count, error)
12271308
IF(error/=0)GOTO 100
12281309
dims = space_count
1229-
ALLOCATE(CHARACTER(LEN=dims(1)) :: string)
1230-
call h5dread_f(dset_id, H5T_NATIVE_CHARACTER, string, dims, error)
1310+
IF(ALLOCATED(strings))DEALLOCATE(strings)
1311+
ALLOCATE(CHARACTER(LEN=string_size) :: strings(dims(1)))
1312+
call h5dread_f(dset_id, type_id, strings, dims, error)
12311313
IF(error/=0)THEN
1232-
DEALLOCATE(string)
1314+
DEALLOCATE(strings)
12331315
GOTO 100
12341316
END IF
12351317
!---Close and finalize HDF5
12361318
CALL h5sclose_f(dspace_id, error)
1319+
CALL h5tclose_f(type_id, error)
12371320
call h5dclose_f(dset_id, error)
12381321
call h5fclose_f(file_id, error)
12391322
call h5close_f(error)
@@ -1244,11 +1327,12 @@ subroutine hdf5_read_string(string,filename,path,success)
12441327
END IF
12451328
RETURN
12461329
100 CALL h5sclose_f(dspace_id, error)
1247-
101 CALL h5dclose_f(dset_id, error)
1248-
102 CALL h5fclose_f(file_id, error)
1249-
103 CALL h5close_f(error)
1330+
101 CALL h5tclose_f(type_id, error)
1331+
102 CALL h5dclose_f(dset_id, error)
1332+
103 CALL h5fclose_f(file_id, error)
1333+
104 CALL h5close_f(error)
12501334
IF(PRESENT(success))CALL h5eset_auto_f(one, error)
1251-
end subroutine hdf5_read_string
1335+
end subroutine hdf5_read_strings
12521336
!------------------------------------------------------------------------------
12531337
!> real(r8) scalar implementation of \ref oft_io::hdf5_read
12541338
!------------------------------------------------------------------------------

src/base/oft_local.F90

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,13 @@ END FUNCTION oft_sleep
5656
!------------------------------------------------------------------------------
5757
!> Simple in-memory hashing function for dataset checksumming
5858
!------------------------------------------------------------------------------
59-
FUNCTION oft_simple_hash(key,length) BIND(C)
59+
FUNCTION oft_simple_hash_c(key,length,starting_value) BIND(C)
6060
IMPORT c_int, c_long, c_ptr
61-
INTEGER(c_int) :: oft_simple_hash !< Hash of data
61+
INTEGER(c_int) :: oft_simple_hash_c !< Hash of data
6262
TYPE(c_ptr), VALUE, INTENT(in) :: key !< Location of data
6363
INTEGER(c_long), VALUE, INTENT(in) :: length !< Length of data to hash in bytes
64-
END FUNCTION oft_simple_hash
64+
INTEGER(c_int), VALUE, INTENT(in) :: starting_value !< Starting value for the hash
65+
END FUNCTION oft_simple_hash_c
6566
END INTERFACE
6667
!------------------------------------------------------------------------------
6768
!> One dimensional integer set
@@ -116,6 +117,7 @@ END FUNCTION oft_2d_func
116117
!---------------------------------------------------------------------------------
117118
TYPE :: oft_timer
118119
INTEGER(i8) :: count = 0 !< Integer value of system clock at last call
120+
INTEGER(i8) :: crate = 0 !< Integer value of system clock rate
119121
CONTAINS
120122
!> Start or reset timer
121123
procedure :: tick => oft_timer_start
@@ -128,6 +130,20 @@ END FUNCTION oft_2d_func
128130
END TYPE oft_timer
129131
PRIVATE oft_timer_start, oft_timer_elapsed, oft_timer_intelapsed, oft_timer_timeout
130132
CONTAINS
133+
!------------------------------------------------------------------------------
134+
!> Simple in-memory hashing function for dataset checksumming
135+
!------------------------------------------------------------------------------
136+
FUNCTION oft_simple_hash(key,length,starting_value) RESULT(data_hash)
137+
TYPE(c_ptr), INTENT(in) :: key !< Location of data
138+
INTEGER(c_long), INTENT(in) :: length !< Length of data to hash in bytes
139+
INTEGER(c_int), OPTIONAL, INTENT(in) :: starting_value !< Starting value for the hash
140+
INTEGER(c_int) :: data_hash !< Hash of data
141+
IF(PRESENT(starting_value))THEN
142+
data_hash=oft_simple_hash_c(key,length,starting_value)
143+
ELSE
144+
data_hash=oft_simple_hash_c(key,length,0)
145+
END IF
146+
END FUNCTION oft_simple_hash
131147
!---------------------------------------------------------------------------------
132148
!> Returns the corresponding lowercase letter, if `c` is an uppercase
133149
!! ASCII character, otherwise `c` itself.
@@ -141,8 +157,8 @@ ELEMENTAL FUNCTION char_to_lower(c) result(t)
141157
INTEGER(i4), PARAMETER :: BA=iachar('A')
142158
INTEGER(i4), PARAMETER :: BZ=iachar('Z')
143159
INTEGER(i4) :: k
144-
k = ichar(c)
145-
IF(k>=BA.and.k<=BZ)k = k + wp
160+
k = ichar(c)
161+
IF(k>=BA.and.k<=BZ)k = k + wp
146162
t = char(k)
147163
END FUNCTION char_to_lower
148164
!---------------------------------------------------------------------------------
@@ -168,8 +184,8 @@ ELEMENTAL FUNCTION char_to_upper(c) result(t)
168184
INTEGER(i4), PARAMETER :: BA=iachar('a')
169185
INTEGER(i4), PARAMETER :: BZ=iachar('z')
170186
INTEGER(i4) :: k
171-
k = ichar(c)
172-
IF(k>=BA.and.k<=BZ)k = k + wp
187+
k = ichar(c)
188+
IF(k>=BA.and.k<=BZ)k = k + wp
173189
t = char(k)
174190
END FUNCTION char_to_upper
175191
!---------------------------------------------------------------------------------
@@ -187,7 +203,8 @@ END SUBROUTINE string_to_upper
187203
!---------------------------------------------------------------------------------
188204
SUBROUTINE oft_timer_start(self)
189205
CLASS(oft_timer), INTENT(inout) :: self !< Calling timer class
190-
self%count=oft_time_i8()
206+
INTEGER(i8) :: cmax
207+
CALL system_clock(self%count,self%crate,cmax)
191208
END SUBROUTINE oft_timer_start
192209
!---------------------------------------------------------------------------------
193210
!> Set elapsed time since last tick/tock
@@ -204,9 +221,6 @@ FUNCTION oft_timer_elapsed(self) RESULT(time)
204221
END FUNCTION oft_timer_elapsed
205222
!---------------------------------------------------------------------------------
206223
!> Get elapsed time since last tick/tock in integer counts
207-
!!
208-
!! @param[in,out] self Calling timer class
209-
!! @return Number of integer counts since last tick/tock
210224
!---------------------------------------------------------------------------------
211225
FUNCTION oft_timer_intelapsed(self) result(dt)
212226
CLASS(oft_timer), INTENT(inout) :: self !< Calling timer class

src/base/oft_local_c.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,9 @@ void oft_set_signal_handlers()
8383
}
8484

8585
// Jenkins's one_at_a_time hash
86-
uint32_t oft_simple_hash(const uint8_t* key, long length) {
86+
uint32_t oft_simple_hash_c(const uint8_t* key, long length, uint32_t starting_value) {
8787
long i = 0;
88-
uint32_t hash = 0;
88+
uint32_t hash = starting_value;
8989
while (i != length) {
9090
hash += key[i++];
9191
hash += hash << 10;

src/bin/CMakeLists.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,6 @@ set( THINCURR_EXES
1717
)
1818

1919
set( BIN_SRCS ${OFT_EXES} ${MARKLIN_EXES} )
20-
if( OFT_THINCURR_LEGACY )
21-
set( BIN_SRCS ${BIN_SRCS} ${THINCURR_EXES} )
22-
endif()
2320

2421
foreach( file ${BIN_SRCS} )
2522
oft_add_exe( ${file} )

0 commit comments

Comments
 (0)