11"""Multi-temperature viscosity pipeline.
22
33Sequentially cools a quenched glass to each target temperature via
4- melt-quench and runs a Green-Kubo viscosity production simulation
5- at each one .
4+ melt-quench, then runs Green-Kubo viscosity production simulations
5+ in parallel using an executor .
66"""
77
88from __future__ import annotations
1414from amorphouspy .fabrication .meltquench import melt_quench_simulation
1515
1616if TYPE_CHECKING :
17+ from concurrent .futures import Executor
18+
1719 import pandas as pd
1820 from ase import Atoms
1921from amorphouspy .properties .viscosity import get_viscosity , viscosity_simulation
2022
2123logger = logging .getLogger (__name__ )
2224
2325
26+ # ---------------------------------------------------------------------------
27+ # Helpers submitted to the executor
28+ # ---------------------------------------------------------------------------
29+
30+
31+ def _cool_and_run_viscosity (
32+ structure : Atoms ,
33+ potential : pd .DataFrame ,
34+ temp_high : float ,
35+ temp_low : float ,
36+ heating_rate : float ,
37+ cooling_rate : float ,
38+ timestep : float ,
39+ n_timesteps : int ,
40+ n_print : int ,
41+ max_lag : int | None ,
42+ server_kwargs : dict [str , Any ],
43+ ) -> dict [str , Any ]:
44+ """Cool to *temp_low* via melt-quench, then run a viscosity simulation.
45+
46+ This function is submitted to the executor as a single unit of work
47+ so that the melt-quench and the production run share the same worker.
48+ """
49+ mq_result = melt_quench_simulation (
50+ structure = structure ,
51+ potential = potential ,
52+ temperature_high = float (temp_high ),
53+ temperature_low = float (temp_low ),
54+ timestep = 1.0 ,
55+ heating_rate = float (heating_rate ),
56+ cooling_rate = float (cooling_rate ),
57+ n_print = 1000 ,
58+ langevin = False ,
59+ server_kwargs = server_kwargs ,
60+ )
61+ cooled_structure = mq_result ["structure" ]
62+
63+ visc_result = viscosity_simulation (
64+ structure = cooled_structure ,
65+ potential = potential ,
66+ temperature_sim = float (temp_low ),
67+ timestep = float (timestep ),
68+ initial_production_steps = int (n_timesteps ),
69+ n_print = int (n_print ),
70+ langevin = False ,
71+ seed = 12345 ,
72+ server_kwargs = server_kwargs ,
73+ )
74+
75+ visc_data = get_viscosity (visc_result , timestep = float (timestep ), max_lag = max_lag )
76+
77+ raw_lag = cast ("list[float]" , visc_data .get ("lag_time_ps" , []))
78+ raw_visc = cast ("list[float]" , visc_data .get ("viscosity_integral" , []))
79+
80+ return {
81+ "temperature" : float (temp_low ),
82+ "viscosity" : float (visc_data ["viscosity" ]),
83+ "max_lag" : float (visc_data ["max_lag" ]),
84+ "simulation_steps" : int (n_timesteps ),
85+ "lag_times_ps" : downsample_log (raw_lag ),
86+ "viscosity_integral" : downsample_log (raw_visc ),
87+ }
88+
89+
90+ # ---------------------------------------------------------------------------
91+ # Public API
92+ # ---------------------------------------------------------------------------
93+
94+
2495def run_viscosity_workflow (
96+ executor : Executor ,
2597 structure : Atoms ,
2698 potential : pd .DataFrame ,
2799 temperatures : list [float ],
@@ -32,15 +104,19 @@ def run_viscosity_workflow(
32104 n_print : int = 1 ,
33105 max_lag : int | None = 1_000_000 ,
34106 server_kwargs : dict [str , Any ] | None = None ,
107+ resource_dict : dict [str , Any ] | None = None ,
35108) -> dict [str , Any ]:
36- """Run viscosity analysis at multiple temperatures.
109+ """Run viscosity analysis at multiple temperatures using an executor .
37110
38- Starting from the quenched structure, the workflow sequentially cools
39- from the highest to the lowest requested temperature. At each step a
40- melt-quench brings the structure to the target temperature, followed by
41- a Green-Kubo viscosity production run.
111+ Each temperature is submitted as an independent task to *executor*.
112+ Every task first cools the starting structure to the target temperature
113+ via a melt-quench, then performs a Green-Kubo viscosity production run.
114+ Because each task starts from the same *structure* and cools
115+ independently, the tasks can run in parallel.
42116
43117 Args:
118+ executor: A ``concurrent.futures.Executor`` or executorlib executor
119+ used to submit per-temperature tasks.
44120 structure: Quenched ASE Atoms from the melt-quench pipeline.
45121 potential: LAMMPS potential object.
46122 temperatures: Target temperatures (K) for viscosity runs.
@@ -51,67 +127,57 @@ def run_viscosity_workflow(
51127 n_print: Thermodynamic output frequency.
52128 max_lag: Maximum correlation lag (steps) for Green-Kubo.
53129 server_kwargs: LAMMPS server/resource configuration.
130+ resource_dict: Optional resource dict forwarded to executorlib
131+ executors (e.g. ``{"cores": 8}``). Ignored for stdlib executors.
54132
55133 Returns:
56134 Result dict with ``temperatures``, ``viscosities``, ``max_lag``,
57135 ``simulation_steps``, ``lag_times_ps``, and ``viscosity_integral``.
58136 """
59137 if server_kwargs is None :
60138 server_kwargs = {}
139+ if resource_dict is None :
140+ resource_dict = {}
61141
62142 sorted_temps = sorted (temperatures , reverse = True )
63- logger .info ("Viscosity temperatures (high→low): %s" , sorted_temps )
64-
143+ logger .info ("Submitting viscosity tasks for temperatures: %s" , sorted_temps )
144+
145+ # Submit all temperatures in parallel — each cools independently
146+ # from the starting structure down to its target temperature.
147+ futures = {}
148+ for temp in sorted_temps :
149+ submit_kwargs : dict [str , Any ] = {
150+ "structure" : structure ,
151+ "potential" : potential ,
152+ "temp_high" : 5000.0 ,
153+ "temp_low" : temp ,
154+ "heating_rate" : heating_rate ,
155+ "cooling_rate" : cooling_rate ,
156+ "timestep" : timestep ,
157+ "n_timesteps" : n_timesteps ,
158+ "n_print" : n_print ,
159+ "max_lag" : max_lag ,
160+ "server_kwargs" : server_kwargs ,
161+ }
162+ if resource_dict :
163+ submit_kwargs ["resource_dict" ] = resource_dict
164+ futures [temp ] = executor .submit (_cool_and_run_viscosity , ** submit_kwargs )
165+
166+ # Collect results in temperature order
65167 viscosities : list [float ] = []
66168 all_max_lags : list [float ] = []
67169 sim_steps : list [int ] = []
68170 lag_times_ps : list [list [float ]] = []
69171 viscosity_integral : list [list [float ]] = []
70172
71- structure_current = structure
72-
73- for idx , temp in enumerate (sorted_temps ):
74- temp_high = 5000.0 if idx == 0 else sorted_temps [idx - 1 ]
75-
76- logger .info ("Cooling from %.1f K to %.1f K" , temp_high , temp )
77- mq_result = melt_quench_simulation (
78- structure = structure_current ,
79- potential = potential ,
80- temperature_high = float (temp_high ),
81- temperature_low = float (temp ),
82- timestep = 1.0 ,
83- heating_rate = float (heating_rate ),
84- cooling_rate = float (cooling_rate ),
85- n_print = 1000 ,
86- langevin = False ,
87- server_kwargs = server_kwargs ,
88- )
89- structure_current = mq_result ["structure" ]
90- logger .info ("Cooled to %.1f K, %d atoms" , temp , len (structure_current ))
91-
92- logger .info ("Running viscosity simulation at %.1f K" , temp )
93- visc_result = viscosity_simulation (
94- structure = structure_current ,
95- potential = potential ,
96- temperature_sim = float (temp ),
97- timestep = float (timestep ),
98- initial_production_steps = int (n_timesteps ),
99- n_print = int (n_print ),
100- langevin = False ,
101- seed = 12345 ,
102- server_kwargs = server_kwargs ,
103- )
104-
105- visc_data = get_viscosity (visc_result , timestep = float (timestep ), max_lag = max_lag )
106- logger .info ("Viscosity at %.1f K: %.3e Pa·s" , temp , visc_data ["viscosity" ])
107-
108- viscosities .append (float (visc_data ["viscosity" ]))
109- all_max_lags .append (float (visc_data ["max_lag" ]))
110- sim_steps .append (int (n_timesteps ))
111- raw_lag_data = cast ("list[float]" , visc_data .get ("lag_time_ps" , []))
112- raw_visc_data = cast ("list[float]" , visc_data .get ("viscosity_integral" , []))
113- lag_times_ps .append (downsample_log (raw_lag_data ))
114- viscosity_integral .append (downsample_log (raw_visc_data ))
173+ for temp in sorted_temps :
174+ result = futures [temp ].result ()
175+ logger .info ("Viscosity at %.1f K: %.3e Pa·s" , temp , result ["viscosity" ])
176+ viscosities .append (result ["viscosity" ])
177+ all_max_lags .append (result ["max_lag" ])
178+ sim_steps .append (result ["simulation_steps" ])
179+ lag_times_ps .append (result ["lag_times_ps" ])
180+ viscosity_integral .append (result ["viscosity_integral" ])
115181
116182 return {
117183 "temperatures" : sorted_temps ,
0 commit comments