# C, C++, and Fortran Interfaces for TACO Format

This document describes the C, C++, and Fortran interfaces for the TACO (Trajectory and Compressed Observables) format library.

## Overview

The TACO format library provides foreign function interfaces (FFI) for multiple programming languages:

- **C API**: Low-level C interface suitable for integration with C programs and as a foundation for other language bindings
- **C++ API**: Modern C++ wrapper with RAII, STL containers, and exception handling
- **Fortran API**: Fortran 2008 interface with modern Fortran features and ISO C binding

## Building the Library

First, build the Rust library with C API support:

```bash
cargo build --release --features c_api
```

This creates a static library at `target/release/libtaco_format.a` (Unix) or `target/release/taco_format.lib` (Windows).

## C API

### Headers

Include the main header file:

```c
#include "taco_format.h"
```

### Data Structures

```c
// Error codes
typedef enum {
    TACO_SUCCESS = 0,
    TACO_ERROR_NULL_POINTER = -1,
    TACO_ERROR_INVALID_PATH = -2,
    TACO_ERROR_IO = -3,
    // ... other error codes
} taco_error_t;

// Frame structure
typedef struct {
    uint64_t frame_number;
    double time;
    float *positions;           // 3*num_atoms elements
    float *velocities;          // 3*num_atoms elements or NULL
    float *forces;              // 3*num_atoms elements or NULL
    float *box_dimensions;      // 3 elements or NULL
    float *box_angles;          // 3 elements or NULL
    double potential_energy;
    double kinetic_energy;
    double temperature;
    uint32_t num_atoms;
} taco_frame_t;

// Compression settings
typedef struct {
    int position_precision;     // 0 = lossless, 1 = half precision
    int velocity_precision;
    int force_precision;
    int compression_level;      // 0-22
} taco_compression_settings_t;

// Metadata structures
typedef struct {
    const float *masses;
    const char **names;
    const char **types;
    uint32_t num_atoms;
} taco_atom_metadata_t;

typedef struct {
    const char *ensemble;
    double temperature;
    double pressure;
    uint32_t num_degrees_freedom;
} taco_simulation_metadata_t;
```

### Writer API

```c
// Create writer
taco_writer_t *taco_writer_create(
    const char *path,
    uint32_t num_atoms,
    double time_step,
    const taco_simulation_metadata_t *sim_metadata,
    const taco_atom_metadata_t *atom_metadata,
    taco_compression_settings_t compression_settings
);

// Write frame
int taco_writer_write_frame(taco_writer_t *writer, const taco_frame_t *frame);

// Finish and close
int taco_writer_finish(taco_writer_t *writer);

// Free writer
void taco_writer_free(taco_writer_t *writer);
```

### Reader API

```c
// Open reader
taco_reader_t *taco_reader_open(const char *path);

// Get file info
uint64_t taco_reader_num_frames(const taco_reader_t *reader);
uint32_t taco_reader_num_atoms(const taco_reader_t *reader);

// Read frame (memory is allocated, must be freed)
int taco_reader_read_frame(taco_reader_t *reader, uint64_t frame_index, taco_frame_t *frame);

// Free frame arrays
void taco_frame_free_arrays(taco_frame_t *frame);

// Free reader
void taco_reader_free(taco_reader_t *reader);
```

### Example Usage

```c
#include "taco_format.h"
#include <stdio.h>
#include <stdlib.h>

int main() {
    // Setup metadata
    float masses[] = {15.999, 1.008, 1.008};
    const char* names[] = {"O", "H1", "H2"};
    const char* types[] = {"O", "H", "H"};
    
    taco_atom_metadata_t atom_metadata = {
        .masses = masses,
        .names = names,
        .types = types,
        .num_atoms = 3
    };
    
    const char* ensemble = "NVT";
    taco_simulation_metadata_t sim_metadata = {
        .ensemble = ensemble,
        .temperature = 300.0,
        .pressure = 1.0,
        .num_degrees_freedom = 9
    };
    
    taco_compression_settings_t compression = {
        .position_precision = 1,
        .velocity_precision = 1,
        .force_precision = 0,
        .compression_level = 3
    };
    
    // Create writer
    taco_writer_t* writer = taco_writer_create("output.taco", 3, 0.001,
                                             &sim_metadata, &atom_metadata, compression);
    
    // Write frames
    float positions[] = {0.0, 0.0, 0.0, 0.1, 0.08, 0.0, 0.1, -0.08, 0.0};
    taco_frame_t frame = {
        .frame_number = 0,
        .time = 0.0,
        .positions = positions,
        .num_atoms = 3
    };
    
    taco_writer_write_frame(writer, &frame);
    taco_writer_finish(writer);
    
    // Read back
    taco_reader_t* reader = taco_reader_open("output.taco");
    taco_frame_t read_frame = {0};
    taco_reader_read_frame(reader, 0, &read_frame);
    
    printf("Read frame %lu with %u atoms\n", read_frame.frame_number, read_frame.num_atoms);
    
    taco_frame_free_arrays(&read_frame);
    taco_reader_free(reader);
    
    return 0;
}
```

## C++ API

### Headers

Include the C++ wrapper header:

```cpp
#include "taco_format.hpp"
```

### Classes and Types

```cpp
namespace taco {

// Exception for TACO errors
class TacoException : public std::runtime_error { ... };

// Compression settings
struct CompressionSettings {
    enum class Precision { Lossless = 0, Half = 1 };
    
    Precision position_precision = Precision::Half;
    Precision velocity_precision = Precision::Half;
    Precision force_precision = Precision::Half;
    int compression_level = 3;
};

// Metadata structures
struct AtomMetadata {
    std::vector<float> masses;
    std::vector<std::string> names;
    std::vector<std::string> types;
};

struct SimulationMetadata {
    std::string ensemble = "NVE";
    double temperature = 300.0;
    double pressure = 1.0;
    uint32_t num_degrees_freedom = 0;
};

// Frame structure
struct Frame {
    uint64_t frame_number = 0;
    double time = 0.0;
    std::vector<std::array<float, 3>> positions;
    std::optional<std::vector<std::array<float, 3>>> velocities;
    std::optional<std::vector<std::array<float, 3>>> forces;
    std::optional<std::array<float, 3>> box_dimensions;
    std::optional<std::array<float, 3>> box_angles;
    std::optional<double> potential_energy;
    std::optional<double> kinetic_energy;
    std::optional<double> temperature;
};

// RAII classes
class Writer { ... };
class Reader { ... };

}
```

### Writer API

```cpp
// Constructor
Writer(const std::string& path, uint32_t num_atoms, double time_step,
       const SimulationMetadata& sim_metadata, const AtomMetadata& atom_metadata,
       const CompressionSettings& compression_settings = {});

// Write frame
void write_frame(const Frame& frame);

// Finish (automatic on destruction)
void finish();
```

### Reader API

```cpp
// Constructor
Reader(const std::string& path);

// Get file info
uint64_t num_frames() const;
uint32_t num_atoms() const;

// Read single frame
Frame read_frame(uint64_t frame_index);

// Read all frames
std::vector<Frame> read_all_frames();
```

### Example Usage

```cpp
#include "taco_format.hpp"
#include <iostream>

int main() {
    try {
        // Setup metadata
        std::vector<float> masses = {15.999f, 1.008f, 1.008f};
        std::vector<std::string> names = {"O", "H1", "H2"};
        taco::AtomMetadata atom_metadata(masses, names);
        
        taco::SimulationMetadata sim_metadata;
        sim_metadata.ensemble = "NVT";
        sim_metadata.temperature = 300.0;
        
        // Create writer
        taco::Writer writer("output.taco", 3, 0.001, sim_metadata, atom_metadata);
        
        // Create and write frame
        taco::Frame frame;
        frame.frame_number = 0;
        frame.time = 0.0;
        frame.positions = {
            {0.0f, 0.0f, 0.0f},
            {0.1f, 0.08f, 0.0f},
            {0.1f, -0.08f, 0.0f}
        };
        
        writer.write_frame(frame);
        writer.finish();
        
        // Read back
        taco::Reader reader("output.taco");
        auto read_frame = reader.read_frame(0);
        
        std::cout << "Read frame " << read_frame.frame_number 
                  << " with " << read_frame.positions.size() << " atoms\n";
        
    } catch (const taco::TacoException& e) {
        std::cerr << "Error: " << e.what() << "\n";
        return 1;
    }
    
    return 0;
}
```

## Fortran API

### Module

Use the TACO format module:

```fortran
program example
    use taco_format
    implicit none
    ! ... your code here
end program
```

### Types and Constants

```fortran
! Error codes
integer(c_int), parameter :: TACO_SUCCESS = 0
integer(c_int), parameter :: TACO_ERROR_NULL_POINTER = -1
! ... other error codes

! Opaque handles
type, bind(c) :: taco_writer_t
    type(c_ptr) :: ptr
end type

type, bind(c) :: taco_reader_t
    type(c_ptr) :: ptr
end type

! Frame structure
type, bind(c) :: taco_frame_t
    integer(c_int64_t) :: frame_number
    real(c_double) :: time
    type(c_ptr) :: positions
    type(c_ptr) :: velocities
    type(c_ptr) :: forces
    type(c_ptr) :: box_dimensions
    type(c_ptr) :: box_angles
    real(c_double) :: potential_energy
    real(c_double) :: kinetic_energy
    real(c_double) :: temperature
    integer(c_int) :: num_atoms
end type

! Compression settings
type, bind(c) :: taco_compression_settings_t
    integer(c_int) :: precision
    integer(c_int) :: zstd_level
end type

! Metadata structures
type, bind(c) :: taco_atom_metadata_t
    type(c_ptr) :: masses
    type(c_ptr) :: names
    type(c_ptr) :: types
    integer(c_int) :: num_atoms
end type

type, bind(c) :: taco_simulation_metadata_t
    type(c_ptr) :: name
    type(c_ptr) :: description
    type(c_ptr) :: ensemble
    real(c_double) :: temperature
    real(c_double) :: pressure
    type(c_ptr) :: software
    real(c_double) :: timestep_fs
end type
```

### Writer API

```fortran
! Create writer
function taco_writer_create(path, num_atoms, time_step, sim_metadata, &
                          atom_metadata, compression_settings) result(writer)
    character(len=*), intent(in) :: path
    integer, intent(in) :: num_atoms
    real(real64), intent(in) :: time_step
    type(taco_simulation_metadata_t), intent(in) :: sim_metadata
    type(taco_atom_metadata_t), intent(in) :: atom_metadata
    type(taco_compression_settings_t), intent(in) :: compression_settings
    type(c_ptr) :: writer
end function

! Write frame
function taco_writer_write_frame(writer, frame) result(error_code)
    type(c_ptr), intent(in) :: writer
    type(taco_frame_t), intent(in) :: frame
    integer :: error_code
end function

! Finish writing
function taco_writer_finish(writer) result(error_code)
    type(c_ptr), intent(in) :: writer
    integer :: error_code
end function

! Free writer
subroutine taco_writer_free(writer)
    type(c_ptr), intent(in) :: writer
end subroutine
```

### Reader API

```fortran
! Open reader
function taco_reader_open(path) result(reader)
    character(len=*), intent(in) :: path
    type(c_ptr) :: reader
end function

! Get file info
function taco_reader_num_frames(reader) result(num_frames)
    type(c_ptr), intent(in) :: reader
    integer(int64) :: num_frames
end function

function taco_reader_num_atoms(reader) result(num_atoms)
    type(c_ptr), intent(in) :: reader
    integer :: num_atoms
end function

! Read frame (allocates arrays)
function taco_reader_read_frame(reader, frame_index, frame) result(error_code)
    type(c_ptr), intent(in) :: reader
    integer(int64), intent(in) :: frame_index
    type(taco_frame_t), intent(out) :: frame
    integer :: error_code
end function

! Free frame arrays
subroutine taco_frame_free_arrays(frame)
    type(taco_frame_t), intent(inout) :: frame
end subroutine

! Free reader
subroutine taco_reader_free(reader)
    type(c_ptr), intent(in) :: reader
end subroutine
```

### Example Usage

```fortran
program taco_example
    use iso_c_binding
    use iso_fortran_env, only: real32, real64, int64
    use taco_format
    implicit none

    integer, parameter :: NUM_ATOMS = 3
    
    type(c_ptr) :: writer, reader
    type(taco_frame_t) :: frame
    type(taco_compression_settings_t) :: compression_settings
    type(taco_atom_metadata_t) :: atom_meta
    type(taco_simulation_metadata_t) :: sim_meta
    real(real32), target :: masses(NUM_ATOMS)
    real(real32), target :: positions(3*NUM_ATOMS)
    character(len=8), target :: names(NUM_ATOMS)
    character(len=4), target :: types(NUM_ATOMS)
    type(c_ptr), target :: name_ptrs(NUM_ATOMS), type_ptrs(NUM_ATOMS)
    integer :: ierr, num_frames, num_atoms, i

    ! Setup metadata
    masses = [15.999, 1.008, 1.008]
    names = ['O   ', 'H1  ', 'H2  ']
    types = ['O', 'H', 'H']
    
    ! Create C pointers for strings
    do i = 1, NUM_ATOMS
        name_ptrs(i) = c_loc(names(i))
        type_ptrs(i) = c_loc(types(i))
    end do
    
    ! Setup simulation metadata
    sim_meta%name = c_null_ptr
    sim_meta%description = c_null_ptr
    sim_meta%ensemble = c_null_ptr
    sim_meta%temperature = 300.0_c_double
    sim_meta%pressure = 1.0_c_double
    sim_meta%software = c_null_ptr
    sim_meta%timestep_fs = 1.0_c_double
    
    atom_meta%masses = c_loc(masses)
    atom_meta%names = c_loc(name_ptrs)
    atom_meta%types = c_loc(type_ptrs)
    atom_meta%num_atoms = NUM_ATOMS
    
    compression_settings%precision = 1
    compression_settings%zstd_level = 3

    ! Create writer
    writer = taco_writer_create('output.taco', NUM_ATOMS, 0.001_real64, &
                              sim_meta, atom_meta, compression_settings)

    ! Setup positions
    positions(1:3) = [0.0, 0.0, 0.0]      ! O
    positions(4:6) = [0.1, 0.08, 0.0]     ! H1
    positions(7:9) = [0.1, -0.08, 0.0]    ! H2

    ! Write frame
    frame%frame_number = 0_c_int64_t
    frame%time = 0.0_c_double
    frame%positions = c_loc(positions)
    frame%velocities = c_null_ptr
    frame%forces = c_null_ptr
    frame%box_dimensions = c_null_ptr
    frame%box_angles = c_null_ptr
    frame%potential_energy = -76.4_c_double
    frame%kinetic_energy = 12.3_c_double
    frame%temperature = 300.0_c_double
    frame%num_atoms = NUM_ATOMS
    
    ierr = taco_writer_write_frame(writer, frame)
    if (ierr /= TACO_SUCCESS) then
        write(*,*) 'Error writing frame'
        stop 1
    end if

    ! Finish
    ierr = taco_writer_finish(writer)
    call taco_writer_free(writer)

    ! Read back
    reader = taco_reader_open('output.taco')
    num_frames = taco_reader_num_frames(reader)
    num_atoms = taco_reader_num_atoms(reader)
    
    write(*,*) 'File has', num_frames, 'frames with', num_atoms, 'atoms'
    
    ierr = taco_reader_read_frame(reader, 0_c_int64_t, frame)
    if (ierr == TACO_SUCCESS) then
        write(*,*) 'Frame time:', frame%time
        write(*,*) 'Potential energy:', frame%potential_energy
        
        ! Access positions
        real(real32), pointer :: pos(:)
        call c_f_pointer(frame%positions, pos, [3*num_atoms])
        write(*,*) 'First atom position:', pos(1:3)
        
        call taco_frame_free_arrays(frame)
    end if

    call taco_reader_free(reader)

end program
```

## Examples and Tests

The TACO library includes comprehensive examples and tests for all language bindings:

### Fortran Examples

Located in `c_api/fortran/examples/`:

- **simple_water.f90**: Basic water molecule trajectory with vibration
  - Demonstrates basic file I/O, metadata setup, and frame writing/reading
  - Shows proper string handling and memory management
  - Water molecule with O-H bond vibration simulation

- **md_simulation.f90**: Complete MD simulation with all properties
  - Full MD trajectory including positions, velocities, forces, and box dimensions
  - Methane molecule dynamics with thermal motion
  - Demonstrates time-varying energies and temperature
  - Shows advanced data structures and pointer handling

- **random_access.f90**: Non-sequential frame reading and analysis
  - Demonstrates reading frames in non-sequential order
  - Statistical analysis over sampled frames
  - Center of mass calculations and energy tracking
  - Performance comparison between sequential and random access

- **compression_test.f90**: Compression settings comparison
  - Tests different compression levels and precision settings
  - Compares lossless vs lossy compression
  - Accuracy verification and file size analysis
  - Large system handling with multiple atoms

### Fortran Tests

Located in `c_api/fortran/tests/`:

- **unit_tests.f90**: Individual function tests
  - Tests writer/reader creation and destruction
  - Frame writing and reading validation
  - Error handling and null pointer management
  - Memory management verification

- **integration_tests.f90**: End-to-end functionality tests
  - Complete write-read cycles
  - Multiple frame handling
  - All frame properties testing
  - Large system and compression accuracy tests

### Building and Running

#### Quick Start
```bash
# Build C API library first
cd c_api
cargo build --release

# Build and run Fortran examples
cd fortran
make all
make run-examples
```

#### Individual Examples
```bash
# Build specific examples
make examples
cd build

# Run examples with proper library path
LD_LIBRARY_PATH=../../target/release ./simple_water
LD_LIBRARY_PATH=../../target/release ./md_simulation
LD_LIBRARY_PATH=../../target/release ./random_access
LD_LIBRARY_PATH=../../target/release ./compression_test
```

#### Running Tests
```bash
# Build and run all tests
make run-tests

# Run specific test suites
make tests
cd build
LD_LIBRARY_PATH=../../target/release ./unit_tests
LD_LIBRARY_PATH=../../target/release ./integration_tests
```

#### Build Options
```bash
# Debug build with bounds checking
make debug

# Optimized release build
make release

# Get help and available targets
make help
```

### Example Output

#### Simple Water Example
```
=== Simple Water Molecule Example ===
Creating water molecule trajectory...
Writing 10 frames...
Writing complete!
Reading back trajectory...
File contains 10 frames with 3 atoms
Frame 0:
  Time: 0.000000
  Potential energy: -100.000000
  Kinetic energy: 10.000000
  O position: 0.000000 0.000000 0.000000
  H1 position: 0.096000 0.000000 0.000000
  H2 position: -0.024000 0.093000 0.000000
=== Example completed successfully! ===
```

#### MD Simulation Example
```
=== MD Simulation Example ===
Creating methane molecule MD trajectory...
Writing 50 MD steps...
  Written frame 0 / time = 0.000000 ps
  Written frame 10 / time = 0.005000 ps
  ...
Writing complete!
Reading back and analyzing trajectory...
File contains 50 frames with 4 atoms
Frame 0:
  Time: 0.000000 ps
  Temperature: 0.000000 K
  Total energy: -22.000000 kcal/mol
  C position: 0.000000 0.000000 0.000000
  Box dimensions: 10.000000 10.000000 10.000000
=== MD Simulation example completed successfully! ===
```

### Performance and Features Demonstrated

The examples showcase:

- **Memory Management**: Proper allocation and deallocation of C arrays
- **String Handling**: Converting between Fortran and C strings
- **Error Handling**: Comprehensive error checking and reporting
- **Compression**: Different compression settings and their effects
- **Large Systems**: Handling hundreds of atoms efficiently
- **Random Access**: Non-sequential frame reading for analysis
- **Data Types**: All supported frame properties (positions, velocities, forces, box)
- **Metadata**: Complete atom and simulation metadata handling

### Requirements and Troubleshooting

#### Requirements
- GNU Fortran (gfortran) 7.0+
- TACO C API library built with `cargo build --release`
- Linux/macOS environment

#### Common Issues
- **Library not found**: Ensure C API is built first
- **Segmentation faults**: Check memory management and proper use of `taco_frame_free_arrays`
- **Compilation errors**: Verify Fortran 2008 compatibility and module dependencies

#### Performance Tips
- Use appropriate compression settings for your use case
- Sequential reading is more efficient than random access
- Half precision can significantly reduce file size with minimal accuracy loss

## Building Examples and Tests

### Using Make

```bash
# Build examples
cd examples
make all

# Run examples
make test

# Build tests
cd tests/c_api
make all

# Run tests
make test
```

### Using CMake

```bash
# Build examples
cd examples
mkdir build && cd build
cmake ..
make

# Run tests
ctest
```

## Error Handling

All C API functions return integer error codes. The C++ API uses exceptions. The Fortran API returns integer error codes.

### Error Codes

- `TACO_SUCCESS` (0): Operation successful
- `TACO_ERROR_NULL_POINTER` (-1): Null pointer provided
- `TACO_ERROR_INVALID_PATH` (-2): Invalid file path
- `TACO_ERROR_IO` (-3): I/O error
- `TACO_ERROR_FORMAT` (-4): Format error
- `TACO_ERROR_COMPRESSION` (-5): Compression error
- `TACO_ERROR_INVALID_FRAME` (-6): Invalid frame
- `TACO_ERROR_OUT_OF_BOUNDS` (-7): Index out of bounds
- `TACO_ERROR_INVALID_METADATA` (-8): Invalid metadata

## Memory Management

### C API
- The C API allocates memory for frame arrays when reading
- Always call `taco_frame_free_arrays()` after reading frames
- Call appropriate `_free()` functions for writers and readers

### C++ API
- Uses RAII for automatic resource management
- STL containers handle memory automatically
- Writers and readers are automatically closed in destructors

### Fortran API
- Arrays are automatically allocated when reading frames
- Use `deallocate()` for allocated arrays as needed
- Call `taco_reader_free()` and `taco_writer_free()` for handles

## Compression Settings

Control compression behavior with these settings:

- **Position precision**: 0 (lossless) or 1 (half precision)
- **Velocity precision**: 0 (lossless) or 1 (half precision)  
- **Force precision**: 0 (lossless) or 1 (half precision)
- **Compression level**: 0-22 (higher = better compression, slower)

## Thread Safety

The TACO library is not thread-safe. Do not access the same reader or writer from multiple threads simultaneously. Different readers/writers can be used concurrently from different threads.

## Performance Notes

- Use appropriate compression settings for your use case
- Half precision can significantly reduce file size with minimal accuracy loss for many applications
- Consider the trade-off between file size and compression time when choosing compression levels
- Reading frames sequentially is more efficient than random access
