Task 120: .CUBE File Format
Task 120: .CUBE File Format
1. Properties of the .CUBE File Format
The .CUBE file format, commonly known as the Gaussian cube format, is a text-based structure used to store molecular geometric data and volumetric scalar field data, such as electron density or molecular orbitals. The properties intrinsic to the format include the following elements, derived from its header and data sections:
- Comment Line 1: A string providing descriptive information, typically ignored during parsing.
- Comment Line 2: A second string for additional descriptive information, also typically ignored.
- Number of Atoms (Natoms): An integer specifying the count of atoms in the molecular system.
- Origin Coordinates: Three floating-point values representing the X, Y, and Z coordinates of the volumetric data origin.
- X-Axis Definition: An integer for the number of voxels along the X-axis (NX; positive for Bohr units, negative for Angstrom units), followed by three floating-point values for the X-axis vector components.
- Y-Axis Definition: An integer for the number of voxels along the Y-axis (NY), followed by three floating-point values for the Y-axis vector components.
- Z-Axis Definition: An integer for the number of voxels along the Z-axis (NZ), followed by three floating-point values for the Z-axis vector components.
- Atom Data (per atom, repeated Natoms times): An integer for the atomic number, a floating-point value for the atomic charge, and three floating-point values for the atom's X, Y, and Z coordinates.
- Volumetric Data: A 3D grid of floating-point values (NX × NY × NZ in total), arranged with the X-axis as the outermost loop, Y-axis as the middle loop, and Z-axis as the innermost loop. Values are typically formatted in groups, with newlines for readability, but whitespace-separated in general.
These properties define the file's structure, ensuring compatibility with computational chemistry software.
2. Direct Download Links for .CUBE Files
Two direct download links for sample .CUBE files are provided below:
- http://sassie-web.chem.utk.edu/sassie2/docs/analyze/density_plot/density_vmd_example_1/files/test_5.0_equalweights_complete.cube
- http://sassie-web.chem.utk.edu/sassie2/docs/analyze/density_plot/density_vmd_example_1/files/test_5.0_equalweights_segment_1_region_1.cube
3. HTML/JavaScript for Drag-and-Drop .CUBE File Processing
The following is an HTML document with embedded JavaScript that enables a user to drag and drop a .CUBE file. Upon dropping, the script parses the file and displays all properties listed in section 1 on the screen.
This code creates a drop zone, reads the file as text, parses the header and data according to the format, and outputs the properties.
4. Python Class for .CUBE File Handling
The following Python class can open, decode, read, write, and print the properties of a .CUBE file to the console.
import struct
import numpy as np
class CubeFile:
def __init__(self, filename=None):
self.comment1 = ''
self.comment2 = ''
self.natoms = 0
self.origin = np.zeros(3)
self.nx, self.ny, self.nz = 0, 0, 0
self.vx = np.zeros(3)
self.vy = np.zeros(3)
self.vz = np.zeros(3)
self.atoms = [] # list of dicts: {'num': int, 'charge': float, 'pos': np.array(3)}
self.vol_data = None # 3D numpy array
if filename:
self.read(filename)
def read(self, filename):
with open(filename, 'r') as f:
lines = f.readlines()
index = 0
self.comment1 = lines[index].strip()
index += 1
self.comment2 = lines[index].strip()
index += 1
parts = lines[index].split()
self.natoms = int(parts[0])
self.origin = np.array([float(p) for p in parts[1:4]])
index += 1
parts = lines[index].split()
self.nx = int(parts[0])
self.vx = np.array([float(p) for p in parts[1:4]])
index += 1
parts = lines[index].split()
self.ny = int(parts[0])
self.vy = np.array([float(p) for p in parts[1:4]])
index += 1
parts = lines[index].split()
self.nz = int(parts[0])
self.vz = np.array([float(p) for p in parts[1:4]])
index += 1
self.atoms = []
for _ in range(abs(self.natoms)):
parts = lines[index].split()
atom = {
'num': int(parts[0]),
'charge': float(parts[1]),
'pos': np.array([float(p) for p in parts[2:5]])
}
self.atoms.append(atom)
index += 1
# Read volumetric data
flat_data = []
for line in lines[index:]:
flat_data.extend([float(x) for x in line.split() if x.strip()])
self.vol_data = np.array(flat_data).reshape((abs(self.nx), abs(self.ny), abs(self.nz)))
def write(self, filename):
with open(filename, 'w') as f:
f.write(f"{self.comment1}\n")
f.write(f"{self.comment2}\n")
f.write(f"{self.natoms:5d} {self.origin[0]:12.6f} {self.origin[1]:12.6f} {self.origin[2]:12.6f}\n")
f.write(f"{self.nx:5d} {self.vx[0]:12.6f} {self.vx[1]:12.6f} {self.vx[2]:12.6f}\n")
f.write(f"{self.ny:5d} {self.vy[0]:12.6f} {self.vy[1]:12.6f} {self.vy[2]:12.6f}\n")
f.write(f"{self.nz:5d} {self.vz[0]:12.6f} {self.vz[1]:12.6f} {self.vz[2]:12.6f}\n")
for atom in self.atoms:
pos = atom['pos']
f.write(f"{atom['num']:5d} {atom['charge']:12.6f} {pos[0]:12.6f} {pos[1]:12.6f} {pos[2]:12.6f}\n")
# Write volumetric data, 6 per line
flat_data = self.vol_data.flatten()
for i in range(len(flat_data)):
f.write(f"{flat_data[i]:12.5E}")
if (i + 1) % 6 == 0:
f.write("\n")
else:
f.write(" ")
if len(flat_data) % 6 != 0:
f.write("\n")
def print_properties(self):
print(f"Comment 1: {self.comment1}")
print(f"Comment 2: {self.comment2}")
print(f"Number of Atoms: {self.natoms}")
print(f"Origin: {self.origin}")
print(f"X-Axis: NX={self.nx}, Vector={self.vx}")
print(f"Y-Axis: NY={self.ny}, Vector={self.vy}")
print(f"Z-Axis: NZ={self.nz}, Vector={self.vz}")
for i, atom in enumerate(self.atoms):
print(f"Atom {i+1}: Number={atom['num']}, Charge={atom['charge']}, Position={atom['pos']}")
print(f"Volumetric Data Shape: {self.vol_data.shape}")
print(f"Volumetric Data Sample (first 10 values): {self.vol_data.flatten()[:10]}")
# Example usage:
# cube = CubeFile('example.cube')
# cube.print_properties()
# cube.write('output.cube')
5. Java Class for .CUBE File Handling
The following Java class can open, decode, read, write, and print the properties of a .CUBE file to the console.
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
public class CubeFile {
private String comment1;
private String comment2;
private int natoms;
private double[] origin = new double[3];
private int nx;
private double[] vx = new double[3];
private int ny;
private double[] vy = new double[3];
private int nz;
private double[] vz = new double[3];
private List<Atom> atoms = new ArrayList<>();
private double[][][] volData;
static class Atom {
int num;
double charge;
double[] pos = new double[3];
}
public CubeFile(String filename) throws IOException {
read(filename);
}
public void read(String filename) throws IOException {
try (Scanner scanner = new Scanner(new File(filename))) {
comment1 = scanner.nextLine().trim();
comment2 = scanner.nextLine().trim();
natoms = scanner.nextInt();
origin[0] = scanner.nextDouble();
origin[1] = scanner.nextDouble();
origin[2] = scanner.nextDouble();
scanner.nextLine(); // Consume rest
nx = scanner.nextInt();
vx[0] = scanner.nextDouble();
vx[1] = scanner.nextDouble();
vx[2] = scanner.nextDouble();
scanner.nextLine();
ny = scanner.nextInt();
vy[0] = scanner.nextDouble();
vy[1] = scanner.nextDouble();
vy[2] = scanner.nextDouble();
scanner.nextLine();
nz = scanner.nextInt();
vz[0] = scanner.nextDouble();
vz[1] = scanner.nextDouble();
vz[2] = scanner.nextDouble();
scanner.nextLine();
for (int i = 0; i < Math.abs(natoms); i++) {
Atom atom = new Atom();
atom.num = scanner.nextInt();
atom.charge = scanner.nextDouble();
atom.pos[0] = scanner.nextDouble();
atom.pos[1] = scanner.nextDouble();
atom.pos[2] = scanner.nextDouble();
atoms.add(atom);
scanner.nextLine();
}
int absNx = Math.abs(nx), absNy = Math.abs(ny), absNz = Math.abs(nz);
volData = new double[absNx][absNy][absNz];
int x = 0, y = 0, z = 0;
while (scanner.hasNextDouble()) {
volData[x][y][z] = scanner.nextDouble();
z++;
if (z == absNz) {
z = 0;
y++;
if (y == absNy) {
y = 0;
x++;
}
}
}
}
}
public void write(String filename) throws IOException {
try (PrintWriter writer = new PrintWriter(new FileWriter(filename))) {
writer.println(comment1);
writer.println(comment2);
writer.printf("%5d %12.6f %12.6f %12.6f%n", natoms, origin[0], origin[1], origin[2]);
writer.printf("%5d %12.6f %12.6f %12.6f%n", nx, vx[0], vx[1], vx[2]);
writer.printf("%5d %12.6f %12.6f %12.6f%n", ny, vy[0], vy[1], vy[2]);
writer.printf("%5d %12.6f %12.6f %12.6f%n", nz, vz[0], vz[1], vz[2]);
for (Atom atom : atoms) {
writer.printf("%5d %12.6f %12.6f %12.6f %12.6f%n", atom.num, atom.charge, atom.pos[0], atom.pos[1], atom.pos[2]);
}
int count = 0;
for (int i = 0; i < Math.abs(nx); i++) {
for (int j = 0; j < Math.abs(ny); j++) {
for (int k = 0; k < Math.abs(nz); k++) {
writer.printf("%12.5E ", volData[i][j][k]);
count++;
if (count % 6 == 0) {
writer.println();
count = 0;
}
}
if (count != 0) {
writer.println();
count = 0;
}
}
}
}
}
public void printProperties() {
System.out.println("Comment 1: " + comment1);
System.out.println("Comment 2: " + comment2);
System.out.println("Number of Atoms: " + natoms);
System.out.printf("Origin: X=%.6f, Y=%.6f, Z=%.6f%n", origin[0], origin[1], origin[2]);
System.out.printf("X-Axis: NX=%d, Vector=(%.6f, %.6f, %.6f)%n", nx, vx[0], vx[1], vx[2]);
System.out.printf("Y-Axis: NY=%d, Vector=(%.6f, %.6f, %.6f)%n", ny, vy[0], vy[1], vy[2]);
System.out.printf("Z-Axis: NZ=%d, Vector=(%.6f, %.6f, %.6f)%n", nz, vz[0], vz[1], vz[2]);
for (int i = 0; i < atoms.size(); i++) {
Atom atom = atoms.get(i);
System.out.printf("Atom %d: Number=%d, Charge=%.6f, Position=(%.6f, %.6f, %.6f)%n",
i + 1, atom.num, atom.charge, atom.pos[0], atom.pos[1], atom.pos[2]);
}
System.out.printf("Volumetric Data Dimensions: %d x %d x %d%n", Math.abs(nx), Math.abs(ny), Math.abs(nz));
// Print sample data
System.out.print("Volumetric Data Sample (first 10 values): ");
int count = 0;
outer: for (double[][] plane : volData) {
for (double[] row : plane) {
for (double val : row) {
System.out.printf("%.5E ", val);
count++;
if (count == 10) break outer;
}
}
}
System.out.println();
}
// Example usage:
// public static void main(String[] args) throws IOException {
// CubeFile cube = new CubeFile("example.cube");
// cube.printProperties();
// cube.write("output.cube");
// }
}
6. JavaScript Class for .CUBE File Handling
The following JavaScript class can open (via FileReader), decode, read, write (to console or blob), and print the properties of a .CUBE file to the console. Note that writing in JavaScript typically involves creating a downloadable blob.
class CubeFile {
constructor(file = null) {
this.comment1 = '';
this.comment2 = '';
this.natoms = 0;
this.origin = [0, 0, 0];
this.nx = 0;
this.vx = [0, 0, 0];
this.ny = 0;
this.vy = [0, 0, 0];
this.nz = 0;
this.vz = [0, 0, 0];
this.atoms = []; // array of {num, charge, pos: [x,y,z]}
this.volData = []; // flat array for simplicity
if (file) {
this.read(file);
}
}
read(file) {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = (e) => {
const content = e.target.result;
const lines = content.split('\n').map(l => l.trim());
let index = 0;
this.comment1 = lines[index++];
this.comment2 = lines[index++];
let parts = lines[index++].split(/\s+/).map(Number);
this.natoms = parts[0];
this.origin = parts.slice(1, 4);
parts = lines[index++].split(/\s+/).map(Number);
this.nx = parts[0];
this.vx = parts.slice(1, 4);
parts = lines[index++].split(/\s+/).map(Number);
this.ny = parts[0];
this.vy = parts.slice(1, 4);
parts = lines[index++].split(/\s+/).map(Number);
this.nz = parts[0];
this.vz = parts.slice(1, 4);
this.atoms = [];
for (let i = 0; i < Math.abs(this.natoms); i++) {
parts = lines[index++].split(/\s+/).map(Number);
this.atoms.push({
num: parts[0],
charge: parts[1],
pos: parts.slice(2, 5)
});
}
this.volData = [];
for (let i = index; i < lines.length; i++) {
if (lines[i]) {
this.volData.push(...lines[i].split(/\s+/).map(Number).filter(n => !isNaN(n)));
}
}
resolve();
};
reader.onerror = reject;
reader.readAsText(file);
});
}
write() {
let output = `${this.comment1}\n`;
output += `${this.comment2}\n`;
output += `${this.natoms.toString().padStart(5)} ${this.origin.map(n => n.toFixed(6).padStart(12)).join(' ')}\n`;
output += `${this.nx.toString().padStart(5)} ${this.vx.map(n => n.toFixed(6).padStart(12)).join(' ')}\n`;
output += `${this.ny.toString().padStart(5)} ${this.vy.map(n => n.toFixed(6).padStart(12)).join(' ')}\n`;
output += `${this.nz.toString().padStart(5)} ${this.vz.map(n => n.toFixed(6).padStart(12)).join(' ')}\n`;
this.atoms.forEach(atom => {
output += `${atom.num.toString().padStart(5)} ${atom.charge.toFixed(6).padStart(12)} ${atom.pos.map(n => n.toFixed(6).padStart(12)).join(' ')}\n`;
});
let count = 0;
this.volData.forEach(val => {
output += val.toExponential(5).padStart(12).toUpperCase() + ' ';
count++;
if (count % 6 === 0) {
output += '\n';
count = 0;
}
});
if (count !== 0) output += '\n';
// For writing: create blob and download
const blob = new Blob([output], { type: 'text/plain' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = 'output.cube';
a.click();
URL.revokeObjectURL(url);
}
printProperties() {
console.log(`Comment 1: ${this.comment1}`);
console.log(`Comment 2: ${this.comment2}`);
console.log(`Number of Atoms: ${this.natoms}`);
console.log(`Origin: ${this.origin.join(', ')}`);
console.log(`X-Axis: NX=${this.nx}, Vector=${this.vx.join(', ')}`);
console.log(`Y-Axis: NY=${this.ny}, Vector=${this.vy.join(', ')}`);
console.log(`Z-Axis: NZ=${this.nz}, Vector=${this.vz.join(', ')}`);
this.atoms.forEach((atom, i) => {
console.log(`Atom ${i+1}: Number=${atom.num}, Charge=${atom.charge}, Position=${atom.pos.join(', ')}`);
});
console.log(`Volumetric Data Length: ${this.volData.length}`);
console.log(`Volumetric Data Sample (first 10): ${this.volData.slice(0, 10).join(', ')}`);
}
}
// Example usage (assuming file input):
// const input = document.createElement('input');
// input.type = 'file';
// input.onchange = async (e) => {
// const file = e.target.files[0];
// const cube = new CubeFile();
// await cube.read(file);
// cube.printProperties();
// cube.write();
// };
// input.click();
7. C Implementation for .CUBE File Handling
Since C does not support classes natively, the following implementation uses a struct and associated functions to open, decode, read, write, and print the properties of a .CUBE file to the console.
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
typedef struct {
char *comment1;
char *comment2;
int natoms;
double origin[3];
int nx;
double vx[3];
int ny;
double vy[3];
int nz;
double vz[3];
struct Atom *atoms;
double *vol_data; // flat array
int vol_size;
} CubeFile;
typedef struct Atom {
int num;
double charge;
double pos[3];
} Atom;
CubeFile* cube_new() {
CubeFile* cube = (CubeFile*)malloc(sizeof(CubeFile));
memset(cube, 0, sizeof(CubeFile));
cube->comment1 = (char*)malloc(256);
cube->comment2 = (char*)malloc(256);
return cube;
}
void cube_free(CubeFile* cube) {
free(cube->comment1);
free(cube->comment2);
free(cube->atoms);
free(cube->vol_data);
free(cube);
}
int cube_read(CubeFile* cube, const char* filename) {
FILE* fp = fopen(filename, "r");
if (!fp) return -1;
fgets(cube->comment1, 256, fp);
cube->comment1[strcspn(cube->comment1, "\n")] = 0;
fgets(cube->comment2, 256, fp);
cube->comment2[strcspn(cube->comment2, "\n")] = 0;
fscanf(fp, "%d %lf %lf %lf", &cube->natoms, &cube->origin[0], &cube->origin[1], &cube->origin[2]);
fscanf(fp, "%d %lf %lf %lf", &cube->nx, &cube->vx[0], &cube->vx[1], &cube->vx[2]);
fscanf(fp, "%d %lf %lf %lf", &cube->ny, &cube->vy[0], &cube->vy[1], &cube->vy[2]);
fscanf(fp, "%d %lf %lf %lf", &cube->nz, &cube->vz[0], &cube->vz[1], &cube->vz[2]);
int abs_natoms = abs(cube->natoms);
cube->atoms = (Atom*)malloc(sizeof(Atom) * abs_natoms);
for (int i = 0; i < abs_natoms; i++) {
fscanf(fp, "%d %lf %lf %lf %lf", &cube->atoms[i].num, &cube->atoms[i].charge,
&cube->atoms[i].pos[0], &cube->atoms[i].pos[1], &cube->atoms[i].pos[2]);
}
int abs_nx = abs(cube->nx), abs_ny = abs(cube->ny), abs_nz = abs(cube->nz);
cube->vol_size = abs_nx * abs_ny * abs_nz;
cube->vol_data = (double*)malloc(sizeof(double) * cube->vol_size);
int idx = 0;
double val;
while (fscanf(fp, "%lf", &val) == 1) {
if (idx < cube->vol_size) {
cube->vol_data[idx++] = val;
}
}
fclose(fp);
return 0;
}
int cube_write(CubeFile* cube, const char* filename) {
FILE* fp = fopen(filename, "w");
if (!fp) return -1;
fprintf(fp, "%s\n", cube->comment1);
fprintf(fp, "%s\n", cube->comment2);
fprintf(fp, "%5d %12.6f %12.6f %12.6f\n", cube->natoms, cube->origin[0], cube->origin[1], cube->origin[2]);
fprintf(fp, "%5d %12.6f %12.6f %12.6f\n", cube->nx, cube->vx[0], cube->vx[1], cube->vx[2]);
fprintf(fp, "%5d %12.6f %12.6f %12.6f\n", cube->ny, cube->vy[0], cube->vy[1], cube->vy[2]);
fprintf(fp, "%5d %12.6f %12.6f %12.6f\n", cube->nz, cube->vz[0], cube->vz[1], cube->vz[2]);
int abs_natoms = abs(cube->natoms);
for (int i = 0; i < abs_natoms; i++) {
Atom a = cube->atoms[i];
fprintf(fp, "%5d %12.6f %12.6f %12.6f %12.6f\n", a.num, a.charge, a.pos[0], a.pos[1], a.pos[2]);
}
int count = 0;
for (int i = 0; i < cube->vol_size; i++) {
fprintf(fp, "%12.5E ", cube->vol_data[i]);
count++;
if (count % 6 == 0) {
fprintf(fp, "\n");
count = 0;
}
}
if (count != 0) fprintf(fp, "\n");
fclose(fp);
return 0;
}
void cube_print_properties(CubeFile* cube) {
printf("Comment 1: %s\n", cube->comment1);
printf("Comment 2: %s\n", cube->comment2);
printf("Number of Atoms: %d\n", cube->natoms);
printf("Origin: %12.6f %12.6f %12.6f\n", cube->origin[0], cube->origin[1], cube->origin[2]);
printf("X-Axis: NX=%5d, Vector=%12.6f %12.6f %12.6f\n", cube->nx, cube->vx[0], cube->vx[1], cube->vx[2]);
printf("Y-Axis: NY=%5d, Vector=%12.6f %12.6f %12.6f\n", cube->ny, cube->vy[0], cube->vy[1], cube->vy[2]);
printf("Z-Axis: NZ=%5d, Vector=%12.6f %12.6f %12.6f\n", cube->nz, cube->vz[0], cube->vz[1], cube->vz[2]);
int abs_natoms = abs(cube->natoms);
for (int i = 0; i < abs_natoms; i++) {
Atom a = cube->atoms[i];
printf("Atom %d: Number=%5d, Charge=%12.6f, Position=%12.6f %12.6f %12.6f\n",
i + 1, a.num, a.charge, a.pos[0], a.pos[1], a.pos[2]);
}
printf("Volumetric Data Size: %d\n", cube->vol_size);
printf("Volumetric Data Sample (first 10): ");
for (int i = 0; i < 10 && i < cube->vol_size; i++) {
printf("%12.5E ", cube->vol_data[i]);
}
printf("\n");
}
// Example usage:
// int main() {
// CubeFile* cube = cube_new();
// if (cube_read(cube, "example.cube") == 0) {
// cube_print_properties(cube);
// cube_write(cube, "output.cube");
// }
// cube_free(cube);
// return 0;
// }