Skip to content

Commit

Permalink
Statsitics & snapshots:
Browse files Browse the repository at this point in the history
 - Refactor of statistics gathering and snapshots saving code.
 - Fixed uncontrollable secondly structure snapshot saving.
 - Frequent saving setting does not impact performance anymore.
 - Both routines now strictly use intrinsic simulation time. The interval setting on the interface also refers to simulation time. The time corresponds to the patterning time.
 - Minor process visualization refactor
  • Loading branch information
MrCheatak committed Jul 12, 2024
1 parent 59cc25a commit 50c96e7
Show file tree
Hide file tree
Showing 3 changed files with 87 additions and 48 deletions.
47 changes: 36 additions & 11 deletions febid/Statistics.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import time
import timeit
from math import floor, log
from threading import Thread
from threading import Thread, Condition
from dataclasses import dataclass

import numpy as np
Expand All @@ -20,8 +20,19 @@ class SynchronizationHelper:
"""
Secures a flag that serves as a signal to the threads that have it weather to stop execution or not.
True to stop execution, False to continue.
Also contains timer that counts intrinsic simulation time.
"""
run_flag: bool
loop_tick: Condition = Condition() # this allows the thread to pause instead of constantly looping
_current_time: float = 0

@property
def timer(self):
return self._current_time

@timer.setter
def timer(self, value):
self._current_time = value

def __repr__(self):
return str(self.run_flag)
Expand All @@ -37,13 +48,21 @@ def __init__(self, run_flag: SynchronizationHelper, refresh_rate, purpose='Unide
self.refresh_rate = refresh_rate
self.purpose = purpose
self.start_time = timeit.default_timer()
self.start_time_sim = run_flag.timer
self.passed_time = run_flag.timer

def run(self):
print(f'Starting {self.purpose} daemon.')
next_record_time = self.passed_time + self.refresh_rate
self.run_flag.loop_tick.acquire()
while not self.run_flag:
self.looped_func()
time.sleep(self.refresh_rate)
self.run_flag.loop_tick.wait()
if next_record_time < self.run_flag.timer:
self.passed_time = self.run_flag.timer
next_record_time = self.passed_time + self.refresh_rate
self.looped_func()
self.looped_func(end=True)
self.run_flag.loop_tick.release()
print(f'Closing {self.purpose} daemon.')

def looped_func(self, end=False):
Expand Down Expand Up @@ -182,6 +201,13 @@ def save_to_file(self, force=False):
The gathered statistics are appended to the end of the table every couple of seconds
Caution: the session keeps the file open until it finishes.
"""

def write_to_file(data, header, last_row):
args, kwargs = self.__get_writer_args_and_kwargs()
with pd.ExcelWriter(*args, **kwargs) as writer:
data.to_excel(writer, startrow=last_row, sheet_name=self.sheet_name, header=header)
self.last_row = writer.sheets[self.sheet_name].max_row

if timeit.default_timer() - self.time <= self.save_freq and not force:
return
else:
Expand All @@ -193,14 +219,13 @@ def save_to_file(self, force=False):
last_row = self.last_row
header = False
data = self.data.iloc[last_row:]
try:
args, kwargs = self.__get_writer_args_and_kwargs()
with pd.ExcelWriter(*args, **kwargs) as writer:
data.to_excel(writer, startrow=last_row, sheet_name=self.sheet_name, header=header)
self.last_row = writer.sheets[self.sheet_name].max_row
except Exception as e:
print(f'Was unable to save statistics to file, the following error occurred: {e.args}')
sys.exit()
while True:
try:
write_to_file(data, header, last_row)
break
except PermissionError as e:
print(f'Was unable to save statistics to file, the following error occurred: {e.args}')
input('Please close the file and press Enter to continue recording.')

def get_growth_rate(self):
delta = 4
Expand Down
87 changes: 50 additions & 37 deletions febid/febid_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,25 +144,28 @@ def run_febid(structure, precursor_params, settings, sim_params, path, temperatu
stats.get_params(precursor_params, 'Precursor parameters')
stats.get_params(settings, 'Beam parameters and settings')
stats.get_params(sim_params, 'Simulation volume parameters')
process_obj.stats_frequency = min(saving_params['gather_stats_interval'], saving_params['save_snapshot_interval'],
rendering.get('frame_rate', 1))
process_obj.stats_frequency = min(saving_params.get('gather_stats_interval', 1),
saving_params.get('save_snapshot_interval', 1),
rendering.get('frame_rate', 1))
stats.start()
if saving_params['save_snapshot']:
struc = StructureSaver(process_obj, flag, saving_params['save_snapshot'], saving_params['filename'])
struc = StructureSaver(process_obj, flag, saving_params['save_snapshot_interval'], saving_params['filename'])
struc.start()
printing.start()
if rendering['show_process']:
visualize_process(process_obj, flag, **rendering)
if rendering['show_process']: # running visualization in the main loop
total_time = visualize_process(process_obj, flag, **rendering)
printing.join()
if saving_params['gather_stats']:
stats.join()
if saving_params['save_snapshot']:
struc.join()
print('Finished path.')
if rendering['show_process']:
visualize_result(process_obj, total_time, **rendering)
return process_obj, sim


def print_all(path, pr, sim, run_flag):
def print_all(path, pr: Process, sim: MC_Simulation, run_flag: SynchronizationHelper):
"""
Main event loop, that iterates through consequent points in a stream-file.
Expand All @@ -172,6 +175,7 @@ def print_all(path, pr, sim, run_flag):
:param run_flag:
:return:
"""
pr.start_time = datetime.datetime.now()
pr.x0, pr.y0 = path[0, 0:2]
start = 0
total_time = int(path[:, 2].sum() * pr.deposition_scaling * 1e6)
Expand All @@ -189,11 +193,11 @@ def print_all(path, pr, sim, run_flag):
if pr.temperature_tracking:
pr.heat_transfer(sim.beam_heating)
pr.request_temp_recalc = False
print_step(y, x, step, pr, sim, t)
print_step(y, x, step, pr, sim, t, run_flag)
run_flag.run_flag = True


def print_step(y, x, dwell_time, pr: Process, sim, t):
def print_step(y, x, dwell_time, pr: Process, sim: MC_Simulation, t, run_flag: SynchronizationHelper):
"""
Sub-loop, that iterates through the dwell time by a time step
Expand All @@ -203,6 +207,7 @@ def print_step(y, x, dwell_time, pr: Process, sim, t):
:param pr: Process object
:param sim: MC simulation object
:param t: tqdm progress bar
:param run_flag: Thread synchronization object
:return:
"""
Expand Down Expand Up @@ -240,47 +245,59 @@ def print_step(y, x, dwell_time, pr: Process, sim, t):
pr.precursor_density() # recalculate precursor coverage
pr.t += pr.dt * pr.deposition_scaling
time_passed += pr.dt
run_flag.timer = pr.t
t.update(pr.dt * pr.deposition_scaling * 1e6)
if time_passed % pr.stats_frequency < pr.dt * 1.5:
pr.min_precursor_coverage = pr.precursor_min
pr.dep_vol = pr.deposited_vol
pr.reset_dt()
# Allow only one tick of the loop for daemons per one tick of simulation
run_flag.loop_tick.acquire()
run_flag.loop_tick.notify_all()
run_flag.loop_tick.release()


def visualize_process(pr: Process, run_flag, show_process=False, frame_rate=1, displayed_data='precursor'):
def visualize_process(pr: Process, run_flag, frame_rate=1, displayed_data='precursor', **kwargs):
"""
A daemon process function to manage statistics gathering and graphics update.
:param pr: object of the core deposition process
:param run_flag:
:param show_process: True will enable graphical monitoring of the process
:param run_flag: thread synchronization object, allows to stop visualization when simulation concludes
:param frame_rate: redrawing delay
:param displayed_data: name of the displayed data
:param displayed_data: name of the displayed data. Options: 'precursor', 'deposit', 'temperature', 'surface_temperature'
:return:
"""

# When deposition process thread finishes, it sets flag to False which will finish current thread

pr.start_time = datetime.datetime.now()
start_time = timeit.default_timer()
# Initializing graphical monitoring
if show_process:
rn = vr.Render(pr.structure.cell_size)
rn.p.clear
pr.redraw = True
# Event loop
while not run_flag:
now = timeit.default_timer()
update_graphical(rn, pr, now - start_time, displayed_data)
time.sleep(frame_rate)
print('Rendering last frame interactively.')

rn = vr.Render(pr.structure.cell_size)
rn.p.clear()
pr.redraw = True
now = 0
# Event loop
while not run_flag:
now = timeit.default_timer()
rn.p.close()
rn = vr.Render(pr.structure.cell_size)
pr.redraw = True
update_graphical(rn, pr, now - start_time, displayed_data, False)
rn.show(interactive_update=False)
print('Closing rendering.')
update_graphical(rn, pr, now - start_time, displayed_data)
time.sleep(frame_rate)
rn.p.close()
print('Closing rendering.')
return now - start_time


def visualize_result(pr, total_time, displayed_data='precursor', **kwargs):
"""
Rendering the final state of the process.
:param pr: object of the core deposition process
:param displayed_data: name of the displayed data
:param total_time: total time of the simulation
:return:
"""
print('Rendering last frame interactively.')
rn = vr.Render(pr.structure.cell_size)
pr.redraw = True
update_graphical(rn, pr, total_time, displayed_data, False)
rn.show(interactive_update=False)


def update_graphical(rn: vr.Render, pr: Process, time_spent, displayed_data='precursor', update=True):
Expand Down Expand Up @@ -375,7 +392,7 @@ def update_graphical(rn: vr.Render, pr: Process, time_spent, displayed_data='pre
f'Sim. time: {(pr.t):.8f} s \n' # showing simulation time passed
f'Speed: {speed:.8f} \n'
f'Av. growth rate: {growth_rate} nm^3/s \n'
f'Max. temperature: {max_T:.3f} K')
f'Max. temperature: {max_T:.1f} K')
rn.p.actors['stats'].SetText(3,
f'Cells: {pr.n_filled_cells[i]} \n' # showing total number of deposited cells
f'Height: {height} nm \n'
Expand All @@ -397,10 +414,6 @@ def update_graphical(rn: vr.Render, pr: Process, time_spent, displayed_data='pre
return 0


def dump_structure(structure: Structure, sim_t=None, t=None, beam_position=None, filename='FEBID_result'):
vr.save_deposited_structure(structure, sim_t, t, beam_position, filename)


if __name__ == '__main__':
print('##################### FEBID Simulator ###################### \n')
print('Please use `python -m febid` for launching')
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ openpyxl
tqdm
pyqt5
pyaml
vtk==9.3.1
numexpr_mod

0 comments on commit 50c96e7

Please sign in to comment.