"""
Classes for reporting simulation data.
This module contains classes for reporting thermodynamic quantities and field values during simulations.
"""
# This file is part of OpenFerro.
import time
import jax.numpy as jnp
[docs]
class Thermo_Reporter:
"""
Reporter for thermodynamic quantities.
Parameters
----------
file : str, optional
Output file name, by default 'thermo.log'
log_interval : int, optional
Number of steps between outputs, by default 100
global_strain : bool, optional
Whether to output global strain, by default False
excess_stress : bool, optional
Whether to output excess stress, by default False
volume : bool, optional
Whether to output volume, by default True
potential_energy : bool, optional
Whether to output potential energy, by default False
kinetic_energy : bool, optional
Whether to output kinetic energy, by default False
temperature : bool, optional
Whether to output temperature, by default False
"""
[docs]
def __init__(self, file='thermo.log', log_interval=100, global_strain=False, excess_stress=False, volume=True, potential_energy=False, kinetic_energy=False, temperature=False):
self.file = file
self.counter = -1
self.log_interval = log_interval
self.report_global_strain = global_strain
self.report_excess_stress = excess_stress
self.report_volume = volume
self.report_potential_energy = potential_energy
self.report_kinetic_energy = kinetic_energy
self.report_temperature = temperature
self.item_list = []
[docs]
def initialize(self, system):
"""
Initialize the reporter.
Parameters
----------
system : System
The physical system to report on
"""
## make the directory if not exists
all_fields = system.get_all_fields()
self.item_list = ['Step']
if self.report_global_strain:
self.item_list.append('Strain-1')
self.item_list.append('Strain-2')
self.item_list.append('Strain-3')
self.item_list.append('Strain-4')
self.item_list.append('Strain-5')
self.item_list.append('Strain-6')
if self.report_excess_stress:
self.item_list.append('ex_stress-1')
self.item_list.append('ex_stress-2')
self.item_list.append('ex_stress-3')
self.item_list.append('ex_stress-4')
self.item_list.append('ex_stress-5')
self.item_list.append('ex_stress-6')
if self.report_volume:
self.item_list.append('Volume')
if self.report_potential_energy:
self.item_list.append('Total_Potential_Energy')
if self.report_kinetic_energy:
for field in all_fields:
self.item_list.append('Kinetic-{}'.format(field.ID))
if self.report_temperature:
for field in all_fields:
self.item_list.append('Temperature-{}'.format(field.ID))
## write the header: system lattice constants, lattice type, lattice size, etc.
with open(self.file, 'a') as f:
## write the start time
f.write(f"# Log file for OpenFerro Simulation started at: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}\n")
f.write(f"# Lattice Type: {system.lattice.name}, Lattice Size: {system.lattice.size}\n")
f.write(f"# Lattice Vectors: {system.lattice.a1}, {system.lattice.a2}, {system.lattice.a3}\n")
f.write(f"# Current Time Step: {self.counter+1}\n")
## write the report items
f.write("# ")
f.write(", ".join(self.item_list))
f.write("\n")
[docs]
def step(self, system):
"""
Record data for current step.
Parameters
----------
system : System
The physical system to report on
"""
self.counter += 1
if self.counter % self.log_interval == 0:
values = [self.counter]
if self.report_global_strain:
gs = system.get_field_by_ID('gstrain').get_values().flatten().tolist()
values.extend(gs)
if self.report_excess_stress:
ex_stress = system.calc_excess_stress()
values.extend(ex_stress)
if self.report_volume:
try:
gs = system.get_field_by_ID('gstrain').get_values().flatten().tolist()
except:
gs = [0.0, 0.0, 0.0]
vol_ref = system.lattice.ref_volume
vol = (1+gs[0] + gs[1] + gs[2]) * vol_ref
values.append(vol)
all_fields = system.get_all_fields()
if self.report_potential_energy:
values.append(system.calc_total_potential_energy())
if self.report_kinetic_energy:
for field in all_fields:
values.append(field.get_kinetic_energy())
if self.report_temperature:
for field in all_fields:
values.append(field.get_temperature())
with open(self.file, 'a') as f:
f.write(", ".join(map(str, values)))
f.write("\n")
[docs]
class Field_Reporter:
"""
Reporter for field values.
Parameters
----------
file_prefix : str
Prefix for output files
field_ID : str
ID of field to report
log_interval : int, optional
Number of steps between outputs, by default 100
field_average : bool, optional
Whether to output field averages, by default True
dump_field : bool, optional
Whether to dump full field values, by default False
"""
[docs]
def __init__(self, file_prefix, field_ID, log_interval=100, field_average=True, dump_field=False):
self.file_prefix = file_prefix
self.file_avg_name = '{}_avg.log'.format(self.file_prefix)
self.field_ID = field_ID
self.log_interval = log_interval
self.report_field_average = field_average
self.dump_field = dump_field
self.item_list = []
self.counter = -1
[docs]
def initialize(self, system):
"""
Initialize the reporter.
Parameters
----------
system : System
The physical system to report on
"""
field = system.get_field_by_ID(self.field_ID) ## check if the field exists
dim = field
if self.report_field_average:
self.item_list = ['Step', 'Average(Vector)']
with open(self.file_avg_name, 'a') as f:
## write the start time
f.write(f"# Log file for the average of field [{self.field_ID}] started at: {time.strftime('%Y-%m-%d %H:%M:%S', time.localtime())}\n")
f.write(f"# Lattice Type: {system.lattice.name}, Lattice Size: {system.lattice.size}\n")
f.write(f"# Lattice Vectors: {system.lattice.a1}, {system.lattice.a2}, {system.lattice.a3}\n")
f.write(f"# Current Time Step: {self.counter+1}\n")
## write the report items
f.write("# ")
f.write(", ".join(self.item_list))
f.write("\n")
[docs]
def step(self, system):
"""
Record data for current step.
Parameters
----------
system : System
The physical system to report on
"""
self.counter += 1
if self.counter % self.log_interval == 0:
field = system.get_field_by_ID(self.field_ID)
values = field.get_values()
dim = len(values.shape)
if self.report_field_average:
over_axes = tuple(range(dim-1))
with open(self.file_avg_name, 'a') as f:
f.write("{}, ".format(self.counter))
f.write(", ".join(map(str, values.mean(over_axes))))
f.write("\n")
if self.dump_field:
file_name = '{}_dump_{}.npy'.format(self.file_prefix, self.counter)
jnp.save(file_name, values)