@@ -79,7 +79,8 @@ def __init__(
7979 system ,
8080 integrator ,
8181 openmm_properties = None ,
82- options = None ):
82+ options = None ,
83+ platform = None ):
8384 """
8485 Parameters
8586 ----------
@@ -95,8 +96,6 @@ def __init__(
9596 keys include GPU floating point precision.
9697 Note that by default the engine selects the fastest currently
9798 available OpenMM platform.
98- If you want to specify the platform you will have to call
99- `engine.initialize(platform)` after creating the engine.
10099 options : dict
101100 a dictionary that provides additional settings for the OPS engine.
102101 Allowed are
@@ -106,6 +105,11 @@ def __init__(
106105 'n_frames_max' : int, default: 5000,
107106 the maximal number of frames allowed for a returned
108107 trajectory object
108+ platform : str or :class:`openmm.Platform` or None
109+ optional default platform for creating the OpenMM simulation.
110+ This value is used by ``initialize`` and serialization. The
111+ ``.platform`` property reflects the initialized simulation context
112+ and remains ``None`` until initialization occurs.
109113
110114 Notes
111115 -----
@@ -138,6 +142,8 @@ def __init__(
138142 openmm_properties = {}
139143
140144 self .openmm_properties = openmm_properties
145+ self ._normalize_platform (platform ) # validate serializability
146+ self ._platform = platform
141147
142148 # set no cached snapshot
143149 self ._current_snapshot = None
@@ -202,7 +208,8 @@ def from_new_options(
202208 self .system ,
203209 integrator ,
204210 openmm_properties = openmm_properties ,
205- options = new_options )
211+ options = new_options ,
212+ platform = self ._platform )
206213
207214 if self ._simulation is not None and \
208215 integrator is self .integrator and \
@@ -220,14 +227,35 @@ def from_new_options(
220227 @property
221228 def platform (self ):
222229 """
223- str : Return the name of the currently used platform
230+ str or None : Return the name of the currently used platform.
231+
232+ This value is populated from the simulation context and is ``None``
233+ until the simulation has been initialized. Configured platform values
234+ may exist before initialization for serialization, but are not exposed
235+ through this property.
224236
225237 """
226238 if self ._simulation is not None :
227239 return self ._simulation .context .getPlatform ().getName ()
228240 else :
229241 return None
230242
243+ @staticmethod
244+ def _normalize_platform (platform ):
245+ if platform is None :
246+ return None
247+
248+ if isinstance (platform , str ):
249+ return platform
250+
251+ try :
252+ return platform .getName ()
253+ except (AttributeError , TypeError ) as err :
254+ raise TypeError (
255+ "platform must be None, a platform name string, or an "
256+ "openmm.Platform instance"
257+ ) from err
258+
231259 @property
232260 def simulation (self ):
233261 if self ._simulation is None :
@@ -261,7 +289,7 @@ def unload_context(self):
261289 def _default_trajectory_writer (self ):
262290 return TRRTrajectoryWriter ()
263291
264- def initialize (self , platform = None ):
292+ def initialize (self , platform = None , openmm_properties = None ):
265293 """
266294 Create the final OpenMMEngine
267295
@@ -270,6 +298,9 @@ def initialize(self, platform=None):
270298 platform : str or :class:`openmm.Platform` or None
271299 either a string with a name of the platform or a platform object
272300 if None it will default to the fastest currently available platform
301+ openmm_properties : dict or None
302+ optional platform properties for this initialize call. If ``None``
303+ the engine-stored ``openmm_properties`` are used.
273304
274305 Notes
275306 -----
@@ -281,37 +312,45 @@ def initialize(self, platform=None):
281312 """
282313
283314 if self ._simulation is None :
284- if type (platform ) is str :
285- self ._simulation = openmm .app .Simulation (
286- topology = self .topology .mdtraj .to_openmm (),
287- system = self .system ,
288- integrator = self .integrator ,
289- platform = openmm .Platform .getPlatformByName (platform ),
290- platformProperties = self .openmm_properties
291- )
292- elif platform is None :
293- # as of OpenMM 8.1, we can't give an empty props dict when
294- # platform is None. This will still raise the internal
295- # OpenMM error is platform is None and properties are
296- # provided.
297- openmm_props = self .openmm_properties
298- if openmm_props == {}:
299- openmm_props = None
315+ if platform is None :
316+ effective_platform = self ._platform
317+ else :
318+ self ._normalize_platform (platform ) # validate serializability
319+ effective_platform = platform
300320
301- self ._simulation = openmm .app .Simulation (
302- topology = self .topology .mdtraj .to_openmm (),
303- system = self .system ,
304- integrator = self .integrator ,
305- platformProperties = openmm_props ,
306- )
321+ if openmm_properties is None :
322+ effective_properties = self .openmm_properties
307323 else :
308- self ._simulation = openmm .app .Simulation (
309- topology = self .topology .mdtraj .to_openmm (),
310- system = self .system ,
311- integrator = self .integrator ,
312- platform = platform ,
313- platformProperties = self .openmm_properties
314- )
324+ effective_properties = openmm_properties
325+
326+ simulation_kwargs = {
327+ 'topology' : self .topology .mdtraj .to_openmm (),
328+ 'system' : self .system ,
329+ 'integrator' : self .integrator ,
330+ }
331+
332+ if effective_platform is None :
333+ if not effective_properties :
334+ openmm_props = None
335+ else :
336+ openmm_props = effective_properties
337+ raise ValueError (
338+ "OpenMM platform-specific properties were provided, "
339+ "but no platform was specified."
340+ )
341+ simulation_kwargs ['platformProperties' ] = openmm_props
342+ else :
343+ if isinstance (effective_platform , str ):
344+ resolved_platform = openmm .Platform .getPlatformByName (
345+ effective_platform
346+ )
347+ else :
348+ resolved_platform = effective_platform
349+
350+ simulation_kwargs ['platform' ] = resolved_platform
351+ simulation_kwargs ['platformProperties' ] = effective_properties
352+
353+ self ._simulation = openmm .app .Simulation (** simulation_kwargs )
315354
316355 logger .info (
317356 'Initialized OpenMM engine using platform `%s`' %
@@ -333,7 +372,8 @@ def to_dict(self):
333372 'integrator_xml' : integrator_xml ,
334373 'topology' : self .topology ,
335374 'options' : self .options ,
336- 'properties' : self .openmm_properties
375+ 'properties' : self .openmm_properties ,
376+ 'platform' : self ._normalize_platform (self ._platform )
337377 }
338378
339379 @classmethod
@@ -343,6 +383,7 @@ def from_dict(cls, dct):
343383 topology = dct ['topology' ]
344384 options = dct ['options' ]
345385 properties = dct ['properties' ]
386+ platform = dct .get ('platform' , None )
346387
347388 # we need to have str as keys
348389 properties = {str (key ): str (value )
@@ -355,7 +396,8 @@ def from_dict(cls, dct):
355396 system = openmm .XmlSerializer .deserialize (system_xml ),
356397 integrator = integrator ,
357398 options = options ,
358- openmm_properties = properties
399+ openmm_properties = properties ,
400+ platform = platform
359401 )
360402
361403 @property
0 commit comments