@@ -34,13 +34,21 @@ def parse_log(path: Path):
3434 toteng = []
3535 poteng = []
3636 zmin = []
37+ timestep = None # Will try to extract from log
3738
3839 header_re = re .compile (r"\bStep\b.*\bTotEng\b.*\bPotEng\b" )
40+ timestep_re = re .compile (r"timestep\s+([\d.e+-]+)" , re .IGNORECASE )
3941
4042 with path .open (encoding = 'utf-8' , errors = 'ignore' ) as f :
4143 in_block = False
4244 col_idx = {}
4345 for line in f :
46+ # Try to extract timestep from log
47+ if timestep is None :
48+ ts_match = timestep_re .search (line )
49+ if ts_match :
50+ timestep = float (ts_match .group (1 ))
51+
4452 line = line .strip ()
4553 if not line :
4654 in_block = False
@@ -72,7 +80,7 @@ def parse_log(path: Path):
7280 poteng .append (pe )
7381 zmin .append (zm )
7482
75- return step , toteng , poteng , zmin
83+ return step , toteng , poteng , zmin , timestep
7684
7785
7886def parse_dump (path : Path ):
@@ -250,6 +258,7 @@ def main():
250258 parser = argparse .ArgumentParser (description = "Plot combined energy and neighbor distances" )
251259 parser .add_argument ("--dump" , "-d" , required = True , help = "LAMMPS dump file path" )
252260 parser .add_argument ("--log" , "-l" , default = "log.lammps" , help = "LAMMPS log file (default: log.lammps)" )
261+ parser .add_argument ("--timestep" , "-ts" , type = float , default = None , help = "LAMMPS timestep in fs (optional, auto-detected from log if available)" )
253262
254263 args = parser .parse_args ()
255264
@@ -262,13 +271,23 @@ def main():
262271 raise SystemExit (f"Log file not found: { log_path } " )
263272
264273 # Parse both data sources
265- step_log , toteng , poteng , zmin = parse_log (log_path )
274+ step_log , toteng , poteng , zmin , ts_from_log = parse_log (log_path )
275+
276+ # Use provided timestep or auto-detected one, default to 1.0 fs
277+ timestep = args .timestep or ts_from_log or 1.0
278+
279+ # Convert steps to time in nanoseconds
280+ time_log = np .array (step_log ) * timestep / 1000000.0
281+
266282 steps_dump , means_by_layer , first_id_by_layer = compute_neighbor_distances (dump_path )
267283
268284 if not step_log :
269285 raise SystemExit ("No thermo data found (Step/TotEng/PotEng)." )
270286 if not steps_dump :
271287 raise SystemExit ("No dump data found." )
288+
289+ # Convert dump steps to time in nanoseconds
290+ time_dump = np .array (steps_dump ) * timestep / 1000000.0
272291
273292 # Render last frame to temporary file
274293 with tempfile .NamedTemporaryFile (suffix = '.png' , delete = False ) as tmp :
@@ -280,13 +299,14 @@ def main():
280299 gridspec_kw = {'height_ratios' : [1 , 1 , 1.2 ]})
281300
282301 # === Top panel: Energy ===
283- ax1 .plot (step_log , smooth_data (toteng , window = 1 ), label = "TotEng" , linewidth = 1.5 )
284- ax1 .plot (step_log , smooth_data (poteng , window = 1 ), label = "PotEng" , linewidth = 1.5 )
302+ ax1 .plot (time_log , smooth_data (toteng , window = 1 ), label = "TotEng" , linewidth = 1.5 )
303+ ax1 .plot (time_log , smooth_data (poteng , window = 1 ), label = "PotEng" , linewidth = 1.5 )
285304 ax1 .set_ylabel ("Energy (kcal/mol)" )
286305 ax1 .set_title ("Energy Evolution" )
306+ ax1 .set_xlabel ("Time (ns)" )
287307
288308 ax2 = ax1 .twinx ()
289- ax2 .plot (step_log , smooth_data (zmin , window = 1 ), color = "tab:green" , label = "c_zmin" , linewidth = 1.5 )
309+ ax2 .plot (time_log , smooth_data (zmin , window = 1 ), color = "tab:green" , label = "c_zmin" , linewidth = 1.5 )
290310 ax2 .set_ylabel ("c_zmin (Å)" , color = "tab:green" )
291311
292312 lines1 , labels1 = ax1 .get_legend_handles_labels ()
@@ -298,18 +318,18 @@ def main():
298318 for layer , means in sorted (means_by_layer .items ()):
299319 atom_id = first_id_by_layer .get (layer , "?" )
300320 if layer <= 3 or layer >= len (means_by_layer )- 4 :
301- ax3 .plot (steps_dump , means , label = f"layer { layer } (id { atom_id } )" ,
321+ ax3 .plot (time_dump , means , label = f"layer { layer } (id { atom_id } )" ,
302322 linewidth = 1.5 , alpha = 0.8 )
303323
304- ax3 .set_xlabel ("Step " )
324+ ax3 .set_xlabel ("Time (ns) " )
305325 ax3 .set_ylabel ("Mean distance (Å)" )
306326 ax3 .set_title ("Mean Neighbor Distance per Layer" )
307327 ax3 .legend (loc = 2 )
308328 ax3 .grid (True , alpha = 0.3 )
309329 ax3 .set_title (f"{ args .dump } -distance" )
310330 ax3 .set_ylim (2.5 , 4.5 )
311331 ax4 = ax3 .twinx ()
312- ax4 .plot (step_log , smooth_data (zmin , window = 1 ), color = "tab:green" , label = "c_zmin" , linewidth = 1.5 )
332+ ax4 .plot (time_log , smooth_data (zmin , window = 1 ), color = "tab:green" , label = "c_zmin" , linewidth = 1.5 )
313333 ax4 .set_ylabel ("c_zmin (Å)" , color = "tab:green" )
314334
315335 # === Bottom panel: Last frame visualization ===
0 commit comments