Skip to content

MDC API

marekkokot edited this page Feb 27, 2025 · 38 revisions

Introduction

Beyond the possibility of decompressing mdc-compressed files with CLI, it's also possible to access the data from programming language via appropriate API. Before introducing details of specific APIs the general idea is presented. The first thing to do is always open the MDC file via the appropriate module (class in the case of C++, function + struct in the case of C). The handle (object/struct instance) to this file cannot be used directly to perform queries, but instead, it offers a mechanism to obtain another module (query engine) that may be used to query. Multiple query engines may be obtained from a single MDC file handle. To get a query engine, the programmer must provide a list of segment IDs (strings) and a list of atom IDs (ints). Both can be empty (if both are empty, the query engine will select all the atoms from the MDC file). Having query engine programmer may query for a list of frame IDs (resulting frames are in the order of this list). The distinction between a file handler and a query engine is for multithreaded purposes. There should be (usually) a single file handler for a given MDC file, and to perform queries from multiple threads, each thread should have its own query engine.

C++ API (native)

The C++ API is enclosed in mdc namespace. There are two main classes:

  • reader - realizes MDC file handler
  • query_engine - used to perform queries

reader class

  • reader(const std::string& path) - constructor that opens MDC file. In the case of failure, it sets an internal error message that may be checked with the get_current_error method
  • const std::string& get_current_error() const - returns a current error message; if it's empty, it means there were no errors; in the opposite case, it returns the last error message; errors are never cleared and are considered unrecoverable (using a reader object that is in erroneous state for anything other than destruction is undefined behavior)
  • const std::vector<segment_desc_t>& get_segments() const; - returns a description of segments in the MDC file; each segment is described by size (in number of atoms), and type (water, ios, etc.); details on segment_desc_t type are below
  • uint32_t get_no_frames() const; - returns the number of frames in MDC file
  • const std::vector<uint32_t>& get_anchor_ids() const; - returns the ids of anchors frames. Details: some of the frames in MDC file are so-called anchor frames, each anchor frame can be decompressed without the knowledge of previous frames, the knowledge of which are anchor frames may be utilized to build efficient parallel decompressor utilizing API (each thread should start on anchor frame)
  • std::unique_ptr<query_engine> get_query_engine(const std::vector<std::string>& segment_ids, const std::vector<uint32_t>& atom_ids) const; - returns a new query_engine that may be used to perform queries for specified segment IDs and atom IDs (1-base indexing). If segment_ids and atom_ids are empty, all atoms are returned from queries.

query_engine class

  • const std::vector<uint32_t>& get_original_atom_ids() const - allows for remapping the atoms as ordered in the query result to the original numbering
  • bool query(std::span<const uint32_t> frame_ids, query_result& result) - performs a query for atoms specified with get_query_engine for a given frame IDs; the result of a query is represented as query_result type, and its details are below, in the case of failure returns false and details should be checked with get_current_error method
  • bool query(std::initializer_list<uint32_t> frame_ids, query_result& result) - same as above, but allows to pass frame IDs as std::initializer_list
  • const std::string& get_current_error() const - returns a current error message; if it's empty, it means there were no errors; in the opposite case, it returns the last error message; errors are never cleared and are considered unrecoverable (using a query_engine object that is in erroneous state for anything other than destruction is undefined behavior)

segment_desc_t type

It is a structure with the following fields

  • segment_type_t type - is an enum with segment type (one of unknown, molecule, ion, water, none
  • std::string name - the name of a segment
  • uint32_t size - number of atoms in segment

query_result type

query_result represents the result of a single query. It's a struct (with some private fields related to storage), that contains the following field:

  • std::vector<frame> frames - frames being the result of a query, type frame is described below. The order of frames is the same as the order of frame IDs in the first parameter of the performed query method.

frame type

frame is a struct with the following fields:

  • std::span<atom_coords> coords - coordinates of atoms (atom_coords is as struct with three floats: x, y, z)
  • int step - frame step
  • float time - frame time
  • float box[3][3] - frame box
  • float prec - frame precision

Simple example

Let's assume we have file example.cpp:

#include <iostream>
#include <string>
#include "mdc_reader.h"

std::string segment_type_to_str(mdc::segment_type type) {
	switch (type) {
		case mdc::segment_type::unknown: return "unknown";
		case mdc::segment_type::molecule: return "molecule";
		case mdc::segment_type::ion: return "ion";
		case mdc::segment_type::water: return "water";
		case mdc::segment_type::none: return "none";
	}
	return ""; // this should never happen
}

int main(int argc, char* argv[]) {
	mdc::reader reader("example.mdc");

	uint32_t no_frames = reader.get_no_frames();

	std::cerr << "Segments: " << reader.get_segments().size() << ", Frames: " << no_frames << "\n";

	for (const auto& segment : reader.get_segments())
		std::cerr << "Segment: " << segment.name << ", size: " << segment.size << ", type: " << segment_type_to_str(segment.type) << "\n";
	
	//query_engine to get all atoms
	auto query_engine = reader.get_query_engine({}, {});
	
	mdc::query_result result;
	for (uint32_t frame_id = 0; frame_id < no_frames; frame_id++) {
		if (!query_engine->query({ frame_id }, result)) {
			std::cerr << "Error: " << query_engine->get_current_error();
			return 1;
		}
		auto coords = result.frames[0].coords;
		for (const auto & coord : coords)
			std::cout << "[" << coord.x << ", " << coord.y << ", " << coord.z << "]\n";
			
		std::cerr << "Frame time: " << result.frames[0].time << "\tNo. atoms: " << coords.size() << "\n";
	}
	return 0;
}

Compilation

To use an API, one needs to include the mdc_reader.h header file and link against libmdc.a. mdc_reader.h is in src/mdc_lib/ libmdc.a is in bin as a result of running make. Assuming the current working directory is the main root of the cloned repo and simple.cpp is also there, to compile and link the code above, one could use the following command:

g++ -Wall -O3 -std=c++20 -I src/mdc_lib/ -L bin simple.cpp -o simple -lmdc

A more advanced example of C++ API is in src/cpp_mdc_reader_example/main.cpp

Bindings

C API

Rust API

Webassembly

Clone this wiki locally