OVF (OOMMF Vector Field file format) parser library with C API and language bindings
MIT License
Simple API for powerful OOMMF Vector Field file parsing
Library Coverage | Python Bindings Coverage |
---|---|
For usage examples, take a look into the test folders: test, python/test or fortran/test.
Except for opening a file or initializing a segment, all functions return status codes
(generally OVF_OK
, OVF_INVALID
or OVF_ERROR
).
When the return code is not OVF_OK
, you can take a look into the latest message,
which should tell you what the problem was
(const char * ovf_latest_message(struct ovf_file *)
in the C API).
In C/C++ and Fortran, before writing a segment, make sure the ovf_segment
you pass in is
initialized, i.e. you already called either ovf_read_segment_header
or ovf_segment_create
.
Opening and closing:
struct ovf_file *myfile = ovf_open("myfilename.ovf")
to open a filemyfile->found
to check if the file exists on diskmyfile->is_ovf
to check if the file contains an OVF headermyfile->n_segments
to check the number of segments the file should containovf_close(myfile);
to close the file and free resourcesReading from a file:
struct ovf_segment *segment = ovf_segment_create()
to initialize a new segment and get the pointerovf_read_segment_header(myfile, index, segment)
to read the header into the segment structovf_read_segment_data_4(myfile, index, segment, data)
to read the segment data into your float arraysegment->N
before reading allows partial reading of large data segmentsWriting and appending to a file:
struct ovf_segment *segment = ovf_segment_create()
to initialize a new segment and get the pointersegment->n_cells[0] = ...
etc to set data dimensions, title and description, etc.ovf_write_segment_4(myfile, segment, data, OVF_FORMAT_TEXT)
to write a file containing the segment header and dataovf_append_segment_4(myfile, segment, data, OVF_FORMAT_TEXT)
to append the segment header and data to the fileTo install the ovf python package, either build and install from source or simply use
pip install ovf
To use ovf
from Python, e.g.
from ovf import ovf
import numpy as np
data = np.zeros((2, 2, 1, 3), dtype='f')
data[0,1,0,:] = [3.0, 2.0, 1.0]
with ovf.ovf_file("out.ovf") as ovf_file:
# Write one segment
segment = ovf.ovf_segment(n_cells=[2,2,1])
if ovf_file.write_segment(segment, data) != -1:
print("write_segment failed: ", ovf_file.get_latest_message())
# Add a second segment to the same file
data[0,1,0,:] = [4.0, 5.0, 6.0]
if ovf_file.append_segment(segment, data) != -1:
print("append_segment failed: ", ovf_file.get_latest_message())
The Fortran bindings are written in object-oriented style for ease of use. Writing a file, for example:
type(ovf_file) :: file
type(ovf_segment) :: segment
integer :: success
real(kind=4), allocatable :: array_4(:,:)
real(kind=8), allocatable :: array_8(:,:)
! Initialize segment
call segment%initialize()
! Write a file
call file%open_file("fortran/test/testfile_f.ovf")
segment%N_Cells = [ 2, 2, 1 ]
segment%N = product(segment%N_Cells)
allocate( array_4(3, segment%N) )
array_4 = 0
array_4(:,1) = [ 6.0, 7.0, 8.0 ]
array_4(:,2) = [ 5.0, 4.0, 3.0 ]
success = file%write_segment(segment, array_4, OVF_FORMAT_TEXT)
if ( success == OVF_OK) then
write (*,*) "test write_segment succeeded."
! write (*,*) "n_cells = ", segment%N_Cells
! write (*,*) "n_total = ", segment%N
else
write (*,*) "test write_segment did not work. Message: ", file%latest_message
STOP 1
endif
For more information on how to generate modern Fortran bindings, see also https://github.com/MRedies/Interfacing-Fortran
If you are using CMake, it is as simple as cloning this into a subdirectory,
e.g. thirdparty/ovf
and using it with add_subdirectory
:
add_subdirectory( ${PROJECT_SOURCE_DIR}/thirdparty/ovf )
set( OVF_INCLUDE_DIRS ${PROJECT_SOURCE_DIR}/thirdparty/ovf/include )
target_include_directories( myproject PRIVATE ${OVF_INCLUDE_DIRS} )
target_link_libraries( myproject PUBLIC ${OVF_LIBRARIES_STATIC} )
If you're not using CMake, you may need to put in some manual work.
Usually:
mkdir build
cd build
cmake ..
make
One possibility:
The following options are ON
by default.
If you want to switch them off, just pass -D<OPTION>=OFF
to CMake,
e.g. -DOVF_BUILD_FORTRAN_BINDINGS=OFF
.
OVF_BUILD_PYTHON_BINDINGS
OVF_BUILD_FORTRAN_BINDINGS
OVF_BUILD_TEST
On Windows, you can also set these from the CMake GUI.
Instead of pip
-installing it, you can e.g. build everything
and then install the package locally, where the -e
flag will
let you change/update the package without having to re-install it.
cd python
pip install -e .
The following is an example of how to manually build the C library and link it with bindings into a corresponding Fortran executable, using gcc.
C library:
g++ -DFMT_HEADER_ONLY -Iinclude -fPIC -std=c++11 -c src/ovf.cpp -o ovf.cpp.o
# static
ar qc libovf_static.a ovf.cpp.o
ranlib libovf_static.a
# shared
g++ -fPIC -shared -lc++ ovf.cpp.o -o libovf_shared.so
C/C++ test executable:
g++ -Iinclude -Itest -std=c++11 -c test/main.cpp -o main.cpp.o
g++ -Iinclude -Itest -std=c++11 -c test/simple.cpp -o simple.cpp.o
# link static lib
g++ -lc++ libovf_static.a main.cpp.o simple.cpp.o -o test_cpp_simple
# link shared lib
g++ libovf_shared.so main.cpp.o simple.cpp.o -o test_cpp_simple
Fortran library:
gfortran -fPIC -c fortran/ovf.f90 -o ovf.f90.o
ar qc libovf_fortran.a libovf_static.a ovf.f90.o
ranlib libovf_fortran.a
Fortran test executable
gfortran -c fortran/test/simple.f90 -o simple.f90.o
gfortran -lc++ libovf_fortran.a simple.f90.o -o test_fortran_simple
When linking statically, you can also link the object file ovf.cpp.o
instead of libovf_static.a
.
Note: depending on compiler and/or system, you may need -lstdc++
instead of -lc++
.
This specification is written according to the NIST user guide for OOMMF and has been implemented, but not tested or verified against OOMMF.
Note: The OVF 2.0 format is a modification to the OVF 1.0 format that also supports fields across three spatial dimensions but having values of arbitrary (but fixed) dimension. The following is a full specification of the 2.0 format.
#
character##
and are ignored by the parser. A comment continues until the end of the line.#
but containing only whitespace are ignored#
but containing an unknown keyword are are an errorAfter an overall header, the file consists of segment blocks, each composed of a segment header, data block and trailer.
N > 0
(This dimension, however, is fixed within each segment).# OOMMF OVF 2.0
# Segment count: 000001
Segment Header
# Begin: <block type>
line, and ends with a corresponding # End: <block type>
line#
up to the first colon (:
) character. Case is ignored, and all whitespace is removed:
) up to a comment (##
) or line endingEverything inside the Header
block should be either comments or one of the following file keyword lines
title
: long file name or titledesc
(optional): description line, use as many as desiredmeshunit
: fundamental mesh spatial unit. The comment marker ##
is not allowed in this line. Example value: nm
valueunits
: should be a (Tcl) list of value units. The comment marker ##
is not allowed in this line. Example value: "kA/m"
. The length of the list should be one of
N
: each element denotes the units for the corresponding dimension index1
: the single element is applied to all dimension indexesvaluelabels
: This should be a N
-item (Tcl) list of value labels, one for each value dimension. The labels identify the quantity in each dimension. For example, in an energy density file, N
would be 1
, valueunits could be "J/m3"
, and valuelabels might be "Exchange energy density"
valuedim
(integer): specifies an integer value, N
, which is the dimensionality of the field. N >= 1
xmin
, ymin
, zmin
, xmax
, ymax
, zmax
: six separate lines, specifying the bounding box for the mesh, in units of meshunit
meshtype
: grid structure; one of
rectangular
: Requires also
xbase
, ybase
, zbase
: three separate lines, denoting the origin (i.e. the position of the first point in the data section), in units of meshunit
xstepsize
, ystepsize
, zstepsize
: three separate lines, specifying the distance between adjacent grid points, in units of meshunit
xnodes
, ynodes
, znodes
(integers): three separate lines, specifying the number of nodes along each axis.irregular
: Requires also
pointcount
(integer): number of data sample points/locations, i.e., nodes. For irregular grids onlySegment Data
# Begin: data <representation>
(and therefore closed by # End: data <representation>
), where <representation>
is one of
text
binary 4
binary 8
N
values, where N
is the value dimension as specified by the valuedim
record in the Segment Header. For irregular meshes, each record consists of N + 3
values, where the first three values are the x , y and z components of the node position.text
data to be in N
columns, separated by whitespaceFor binary data:
1234567.0
for 4-byte mode, corresponding to the LSB hex byte sequence 38 B4 96 49
, and 123456789012345.0
for 8-byte mode, corresponding to the LSB hex byte sequence 40 DE 77 83 21 12 DC 42
These extensions are mainly to help with data for atomistic systems.
#
but containing an unknown keyword are ignored.##
is always a comment and is allowed in all keyword lines, including meshunit
and valueunits
csv
is also a valid ASCII data representation and corresponds to comma-separated columns of text
typevalueunits
and valuelabels
are written and parsed, but not checked for dimensionality or content in eithermin
and max
values are not checked to make sure they are sensible boundsirregular
mesh type is not supported properly, as positions are not accounted for in read or writeAn example OVF 2.0 file for an irregular mesh with N = 2:
# OOMMF OVF 2.0
#
# Segment count: 1
#
# Begin: Segment
# Begin: Header
#
# Title: Long file name or title goes here
#
# Desc: Optional description line 1.
# Desc: Optional description line 2.
# Desc: ...
#
## Fundamental mesh measurement unit. Treated as a label:
# meshunit: nm
#
# meshtype: irregular
# pointcount: 5 ## Number of nodes in mesh
#
# xmin: 0. ## Corner points defining mesh bounding box in
# ymin: 0. ## 'meshunit'. Floating point values.
# zmin: 0.
# xmax: 10.
# ymax: 5.
# zmax: 1.
#
# valuedim: 2 ## Value dimension
#
## Fundamental field value units, treated as labels (i.e., unparsed).
## In general, there should be one label for each value dimension.
# valueunits: J/m^3 A/m
# valuelabels: "Zeeman energy density" "Anisotropy field"
#
# End: Header
#
## Each data records consists of N+3 values: the (x,y,z) node
## location, followed by the N value components. In this example,
## N+3 = 5, the two value components are in units of J/m^3 and A/m,
## corresponding to Zeeman energy density and a magneto-crystalline
## anisotropy field, respectively.
#
# Begin: data text
0.5 0.5 0.5 500. 4e4
9.5 0.5 0.5 300. 5e3
0.5 4.5 0.5 400. 4e4
9.5 4.5 0.5 200. 5e3
5.0 2.5 0.5 350. 2.1e4
# End: data text
# End: segment
# OOMMF OVF 2.0
for both regular and irregular meshes.valuemultiplier
, boundary
, ValueRangeMaxMag
and ValueRangeMinMag
of the OVF 1.0 format are not supported.valuedim
is required. This must specify an integer value, N
, bigger or equal to one.valueunits
keyword replaces the valueunit
keyword of OVF 1.0, which is not allowed in OVF 2.0 files.valuelabels
keyword is required.N = 3
, the data block in OVF 1.0 and OVF 2.0 files are exactly the same. Another common case is N = 1
, which represents scalar fields, such as energy density (in say, J/m3
)