packmol-memgen-minimal 1.1.16__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. packmol_memgen/__init__.py +2 -0
  2. packmol_memgen/__version__.py +34 -0
  3. packmol_memgen/data/LICENSE.Apache-2.0 +201 -0
  4. packmol_memgen/data/extra_solvents.lib +789 -0
  5. packmol_memgen/data/frcmod.lipid_ext +97 -0
  6. packmol_memgen/data/frcmod.solvents +129 -0
  7. packmol_memgen/data/insane_lipids.txt +138 -0
  8. packmol_memgen/data/insane_solvents.txt +45 -0
  9. packmol_memgen/data/leaprc.extra_solvents +42 -0
  10. packmol_memgen/data/leaprc.lipid_ext +48 -0
  11. packmol_memgen/data/lipid_ext.lib +12312 -0
  12. packmol_memgen/data/martini_v3.0.0.itp +356605 -0
  13. packmol_memgen/data/memgen.parm +4082 -0
  14. packmol_memgen/data/pdbs.tar.gz +0 -0
  15. packmol_memgen/data/solvent.parm +14 -0
  16. packmol_memgen/example/example.sh +31 -0
  17. packmol_memgen/lib/__init__.py +0 -0
  18. packmol_memgen/lib/amber.py +77 -0
  19. packmol_memgen/lib/charmmlipid2amber/__init__.py +0 -0
  20. packmol_memgen/lib/charmmlipid2amber/charmmlipid2amber.csv +7164 -0
  21. packmol_memgen/lib/charmmlipid2amber/charmmlipid2amber.py +225 -0
  22. packmol_memgen/lib/pdbremix/LICENSE +21 -0
  23. packmol_memgen/lib/pdbremix/__init__.py +0 -0
  24. packmol_memgen/lib/pdbremix/_version.py +1 -0
  25. packmol_memgen/lib/pdbremix/amber.py +1103 -0
  26. packmol_memgen/lib/pdbremix/asa.py +227 -0
  27. packmol_memgen/lib/pdbremix/data/aminoacid.pdb +334 -0
  28. packmol_memgen/lib/pdbremix/data/binaries.json +26 -0
  29. packmol_memgen/lib/pdbremix/data/charmm22.parameter +2250 -0
  30. packmol_memgen/lib/pdbremix/data/charmm22.topology +1635 -0
  31. packmol_memgen/lib/pdbremix/data/color_b.py +682 -0
  32. packmol_memgen/lib/pdbremix/data/hin.lib +130 -0
  33. packmol_memgen/lib/pdbremix/data/hydroxide.lib +88 -0
  34. packmol_memgen/lib/pdbremix/data/make_chi.py +92 -0
  35. packmol_memgen/lib/pdbremix/data/opls.parameter +1108 -0
  36. packmol_memgen/lib/pdbremix/data/opls.topology +1869 -0
  37. packmol_memgen/lib/pdbremix/data/phd.frcmod +82 -0
  38. packmol_memgen/lib/pdbremix/data/phd.leaprc +4 -0
  39. packmol_memgen/lib/pdbremix/data/phd.prepin +35 -0
  40. packmol_memgen/lib/pdbremix/data/template.pdb +334 -0
  41. packmol_memgen/lib/pdbremix/data/znb.frcmod +24 -0
  42. packmol_memgen/lib/pdbremix/data/znb.leaprc +7 -0
  43. packmol_memgen/lib/pdbremix/data/znb.lib +69 -0
  44. packmol_memgen/lib/pdbremix/data.py +264 -0
  45. packmol_memgen/lib/pdbremix/fetch.py +102 -0
  46. packmol_memgen/lib/pdbremix/force.py +627 -0
  47. packmol_memgen/lib/pdbremix/gromacs.py +978 -0
  48. packmol_memgen/lib/pdbremix/lib/__init__.py +0 -0
  49. packmol_memgen/lib/pdbremix/lib/docopt.py +579 -0
  50. packmol_memgen/lib/pdbremix/lib/pyqcprot.py +305 -0
  51. packmol_memgen/lib/pdbremix/namd.py +1078 -0
  52. packmol_memgen/lib/pdbremix/pdbatoms.py +543 -0
  53. packmol_memgen/lib/pdbremix/pdbtext.py +120 -0
  54. packmol_memgen/lib/pdbremix/protein.py +311 -0
  55. packmol_memgen/lib/pdbremix/pymol.py +480 -0
  56. packmol_memgen/lib/pdbremix/rmsd.py +203 -0
  57. packmol_memgen/lib/pdbremix/simulate.py +420 -0
  58. packmol_memgen/lib/pdbremix/spacehash.py +73 -0
  59. packmol_memgen/lib/pdbremix/trajectory.py +286 -0
  60. packmol_memgen/lib/pdbremix/util.py +273 -0
  61. packmol_memgen/lib/pdbremix/v3.py +16 -0
  62. packmol_memgen/lib/pdbremix/v3array.py +482 -0
  63. packmol_memgen/lib/pdbremix/v3numpy.py +350 -0
  64. packmol_memgen/lib/pdbremix/volume.py +155 -0
  65. packmol_memgen/lib/utils.py +1017 -0
  66. packmol_memgen/main.py +2827 -0
  67. packmol_memgen_minimal-1.1.16.dist-info/METADATA +664 -0
  68. packmol_memgen_minimal-1.1.16.dist-info/RECORD +71 -0
  69. packmol_memgen_minimal-1.1.16.dist-info/WHEEL +4 -0
  70. packmol_memgen_minimal-1.1.16.dist-info/entry_points.txt +2 -0
  71. packmol_memgen_minimal-1.1.16.dist-info/licenses/LICENSE +338 -0
@@ -0,0 +1,420 @@
1
+ # encoding: utf-8
2
+
3
+ __doc__ = """
4
+
5
+ Common Interface for molecular dynamics packages.
6
+
7
+ It provides a common API to 3 modules that wrap standard
8
+ relatively-free molecular-dynamics packages:
9
+
10
+ - amber.py
11
+ - gromacs.py
12
+ - namd.py
13
+
14
+ The routines are divided into roughly three sections:
15
+
16
+ 1. Read and write restart files
17
+ 2. Generate restart files from PDB
18
+ 3. Run simulations from restart files
19
+
20
+ """
21
+
22
+ import os
23
+ import shutil
24
+ import copy
25
+
26
+ from . import util
27
+
28
+ from . import amber
29
+ from . import namd
30
+ from . import gromacs
31
+
32
+
33
+
34
+ # ##########################################################
35
+
36
+ # 0. Housekeeping function to figure out simulation package
37
+
38
+ def get_md_module(force_field):
39
+ """
40
+ Returns the specific interface module that is referenced by
41
+ force_field.
42
+ """
43
+ if force_field.startswith('GROMACS'):
44
+ return gromacs
45
+ elif force_field.startswith('AMBER'):
46
+ return amber
47
+ elif force_field.startswith('NAMD'):
48
+ return namd
49
+ else:
50
+ raise ValueError("unrecognized force-field" + force_field)
51
+
52
+
53
+
54
+ # ##########################################################
55
+
56
+ # 1. Reading and writing restart files
57
+
58
+ # In PDBREMIX, all simulations require restart files to run.
59
+
60
+ # 1. topology file (top)
61
+ # 2. coordinates file (crds)
62
+ # 3. velocities file (vels)
63
+
64
+ # When read into a Soup object, it is assumed that the units
65
+ # in the atom.pos and atom.vel vectors are:
66
+
67
+ # - positions: angstroms
68
+ # - velocities: angstroms/picosecond
69
+
70
+
71
+ def expand_restart_files(force_field, basename):
72
+ """
73
+ Returns expanded restart files (top, crds, vels) with basename
74
+ for the package implied by force_field. No file checking.
75
+ """
76
+ md_module = get_md_module(force_field)
77
+ return md_module.expand_restart_files(basename)
78
+
79
+
80
+ def get_restart_files(basename):
81
+ """
82
+ Returns restart files (top, crds, vels) only if they exist,
83
+ otherwise raises Exception. Will deduce package by
84
+ file extensions attached to the basename.
85
+ """
86
+ for module in [amber, gromacs, namd]:
87
+ try:
88
+ return module.get_restart_files(basename)
89
+ except util.FileException:
90
+ pass
91
+ raise util.FileException("Couldn't find restart files for " + basename)
92
+
93
+
94
+ def soup_from_restart_files(basename, skip_solvent=True):
95
+ """
96
+ Reads a Soup from the restart files.
97
+ """
98
+ for module in [amber, gromacs, namd]:
99
+ try:
100
+ top, crds, vels = module.get_restart_files(basename)
101
+ return module.soup_from_restart_files(
102
+ top, crds, vels, skip_solvent)
103
+ except util.FileException:
104
+ pass
105
+ raise util.FileException("Couldn't find restart files for " + basename)
106
+
107
+
108
+ def write_soup_to_crds_and_vels(force_field, soup, basename):
109
+ """
110
+ From soup, writes out the coordinate/velocities for a given
111
+ packaged that is deduced from the force_field.
112
+ """
113
+ md_module = get_md_module(force_field)
114
+ return md_module.write_soup_to_crds_and_vels(soup, basename)
115
+
116
+
117
+ def convert_restart_to_pdb(basename, pdb):
118
+ """
119
+ Converts restart files with basename into PDB file.
120
+ """
121
+ # Will now try to guess restart files by trying each module in
122
+ # turn. Since the functions throws a FileException if any
123
+ # expected files are missing, catching these will determine
124
+ # failure
125
+ for module in [amber, gromacs, namd]:
126
+ try:
127
+ return module.convert_restart_to_pdb(basename, pdb)
128
+ except util.FileException:
129
+ pass
130
+ raise util.FileException("Couldn't find restart files for " + basename)
131
+
132
+
133
+ # # 2. Generate restart files from PDB
134
+
135
+ # The restart files used for PDBREMIX assumes a consistent file
136
+ # naming.
137
+
138
+ # To generate a topology file from the PDB file:
139
+ # - handles multiple protein chains
140
+ # - hydrogens are removed and then regenerated
141
+ # - disulfide bonds are identified
142
+ # - charged residue protonation states are auto-detected
143
+ # - explicit water in cubic box with 10.0 angstrom buffer
144
+ # - counterions to neutralize the system
145
+
146
+
147
+ def pdb_to_top_and_crds(
148
+ force_field, raw_pdb, basename, solvent_buffer=10.0):
149
+ """
150
+ Creates and returns the absolute pathnames to topology and
151
+ coordinate files required to start an MD simulation using the
152
+ package implied in the force_field.
153
+ """
154
+ md_module = get_md_module(force_field)
155
+ top, crd = md_module.pdb_to_top_and_crds(
156
+ force_field, raw_pdb, basename, solvent_buffer)
157
+ return os.path.abspath(top), os.path.abspath(crd)
158
+
159
+
160
+ # ##########################################################
161
+
162
+ # # 3. Run simulations from restart files
163
+
164
+ # Simulation approach for implicit solvent:
165
+ # - optional positional constraints: 100 kcal/mol/angs**2
166
+ # - Langevin thermostat for constant temperature
167
+
168
+ # Simulation approach for explict water:
169
+ # - optional positional restraints: 100 kcal/mol/angs**2
170
+ # - periodic box with PME electrostatics
171
+ # - Langevin thermostat for constant temperature
172
+ # - Nose-Hoover barometer with flexible box size
173
+
174
+ # Each package maintains its own files, but all required
175
+ # will share a common basename with standard extensions
176
+
177
+
178
+ def fetch_simulation_parameters(
179
+ force_field, top, crds, restraint_pdb,
180
+ simulation_type, basename, restraint_force=None):
181
+ """
182
+ Returns a dictionary that contains all the high level
183
+ parameters needed to run an MD simulation using the package
184
+ implied in force_field.
185
+
186
+ Options for simulation_type:
187
+ 1. 'minimization'
188
+ 2. 'constant_energy'
189
+ 3. 'langevin_thermometer'
190
+ """
191
+ # use a bit of magic to get the dictionary. Pesumably the
192
+ # dictionary with the correct name is present in each
193
+ # simulation module
194
+ md_module = get_md_module(force_field)
195
+ parms_dict_name = '%s_parms' % simulation_type
196
+ parms = getattr(md_module, parms_dict_name).copy()
197
+ # Several fields in parms is common across all packages, these
198
+ # are taken from the parameters of this function:
199
+ parms.update({
200
+ 'force_field': force_field,
201
+ 'topology': top,
202
+ 'input_crds': crds,
203
+ 'output_basename': basename,
204
+ })
205
+ if restraint_pdb:
206
+ parms['restraint_pdb'] = restraint_pdb
207
+ if restraint_force is not None:
208
+ parms['restraint_force'] = restraint_force
209
+ return parms
210
+
211
+
212
+ def run_simulation_with_parameters(parms):
213
+ """
214
+ Carries out simulations based on parms
215
+ """
216
+ # For housekeeping, the parms dictionary is written to a
217
+ # .config file. As an Exception is thrown if simulation failed,
218
+ # the existence of an equivalent .config file is an indicator
219
+ # that the simulation has already successfully run.
220
+ config = parms['output_basename'] + ".config"
221
+ if util.is_same_dict_in_file(parms, config):
222
+ print("Skipping: simulation already run.")
223
+ return
224
+ md_module = get_md_module(parms['force_field'])
225
+ md_module.run(parms)
226
+ # No exceptions were thrown - write .config file.
227
+ util.write_dict(config, parms)
228
+
229
+
230
+ def minimize(
231
+ force_field, in_basename, basename,
232
+ restraint_pdb="", restraint_force=None, n_step=200):
233
+ """
234
+ Runs an energy minimization on the restart files top & crd.
235
+
236
+ This is a crucial step as minimization is more robust than
237
+ dynamics calculations. An initial minimization will find a good
238
+ local energy minimum conformation for a dynamics simulation to
239
+ start. This will avoid spurious initial energy fluctuations for
240
+ future dynamics.
241
+ """
242
+ md_module = get_md_module(force_field)
243
+ top, crds, vels = md_module.get_restart_files(in_basename)
244
+ parms = fetch_simulation_parameters(
245
+ force_field, top, crds, restraint_pdb,
246
+ 'minimization', basename, restraint_force)
247
+ parms['n_step_minimization'] = n_step
248
+ run_simulation_with_parameters(parms)
249
+
250
+
251
+ def langevin_thermometer(
252
+ force_field, in_basename, n_step, temp, basename,
253
+ n_step_per_snapshot=50, restraint_pdb="", restraint_force=None,
254
+ random_seed=2343):
255
+ """
256
+ Runs a constant temperature simulation using a Langevin
257
+ thermometer.
258
+
259
+ There are two basic thermometers in most packages. Anderson
260
+ and Langevin. Anderson simply rescales the energy to satisfy
261
+ the average kinetic energy equation wheras Langevin adds a
262
+ little random force. Langevin thus avoids getting trapped in
263
+ unintended energy minima for the cost of a bit of stochasity.
264
+ """
265
+ md_module = get_md_module(force_field)
266
+ top, crds, vels = md_module.get_restart_files(in_basename)
267
+ parms = fetch_simulation_parameters(
268
+ force_field, top, crds, restraint_pdb,
269
+ 'langevin_thermometer', basename, restraint_force)
270
+ parms['input_vels'] = vels
271
+ parms['random_seed'] = random_seed
272
+ parms['n_step_dynamics'] = n_step
273
+ parms['n_step_per_snapshot'] = n_step_per_snapshot
274
+ parms['temperature_thermometer'] = "%.1f" % temp
275
+ parms['temperature_initial_velocities'] = "%.1f" % temp
276
+ run_simulation_with_parameters(parms)
277
+
278
+
279
+ def constant_energy(
280
+ force_field, in_basename, n_step, basename,
281
+ n_step_per_snapshot=50, restraint_pdb="", restraint_force=None):
282
+ """
283
+ Runs a constant energy simulation.
284
+
285
+ Constant energy simulation are useful if you want to capture
286
+ energy transfers exactly. Normally such simulations are
287
+ preceeded by a period of thermal regulation using a Langevin
288
+ thermometer.
289
+ """
290
+ md_module = get_md_module(force_field)
291
+ top, crds, vels = md_module.get_restart_files(in_basename)
292
+ parms = fetch_simulation_parameters(
293
+ force_field, top, crds, restraint_pdb,
294
+ 'constant_energy', basename, restraint_force)
295
+ parms['input_vels'] = vels
296
+ parms['n_step_dynamics'] = n_step
297
+ parms['n_step_per_snapshot'] = n_step_per_snapshot
298
+ assert 'temperature_thermometer' not in parms
299
+ assert 'temp_init' not in parms
300
+ run_simulation_with_parameters(parms)
301
+
302
+
303
+ def merge_trajectories(force_field, basename, src_basenames):
304
+ md_module = get_md_module(force_field)
305
+ md_module.merge_trajectories(basename, src_basenames)
306
+
307
+
308
+ def merge_simulations(force_field, basename, sim_dirs):
309
+ """
310
+ Splices together a bunch of simulations, all with the same
311
+ basename, into one large simulation in the current directory.
312
+ """
313
+ if not sim_dirs:
314
+ return
315
+ src_basenames = [os.path.join(s, basename) for s in sim_dirs]
316
+ merge_trajectories(force_field, basename, src_basenames)
317
+
318
+
319
+ def pulse(
320
+ force_field, in_basename, basename, n_step, pulse_fn,
321
+ n_step_per_pulse=100, restraint_pdb="", restraint_force=None):
322
+ """
323
+ Runs a pulse simulation that uses the restart-file modification
324
+ strategy to manage a steered-molecular dynamics simulation.
325
+
326
+ The pulsed approacha pplies external forces in pulses, which is
327
+ practically carried out be running short constant-energy
328
+ simulations and directly modifying the restart velocities
329
+ between each simulation.
330
+
331
+ Pulse simulations has certain advantages: for instance, the
332
+ system can respond to the forces between pulses, and the
333
+ incredibly flexibility in applying forces. The disadvantage is
334
+ the costly setup which is hopefully, mitigated by this library.
335
+
336
+ Reference: Bosco K. Ho and David A. Agard (2010) "An improved
337
+ strategy for generating forces in steered molecular dynamics:
338
+ the mechanical unfolding of titin, e2lip3 and ubiquitin" PLoS
339
+ ONE 5(9):e13068.
340
+ """
341
+
342
+ # Grab the simulation prameters for a constant energy
343
+ # simulation. Constant energy is preferred as we want to ensure
344
+ # energy changes come only from our velocity modification.
345
+ top, crds, vels = get_restart_files(in_basename)
346
+ # use dummy top and crds, which will be overriden
347
+ overall_config_parms = fetch_simulation_parameters(
348
+ force_field, top, crds, restraint_pdb,
349
+ 'constant_energy', basename, restraint_force)
350
+ overall_config_parms.update({
351
+ 'input_md_name': in_basename,
352
+ 'input_vels': vels,
353
+ 'n_step_dynamics': n_step,
354
+ 'n_step_per_snapshot': n_step_per_pulse // 2,
355
+ 'n_step_per_pulse': n_step_per_pulse
356
+ })
357
+
358
+ # Check if the simulation has already run as the config
359
+ # file is not written until the very end
360
+ config = basename + ".config"
361
+ if util.is_same_dict_in_file(overall_config_parms, config):
362
+ print("Skipping: pulsing simulation already run.")
363
+ return
364
+
365
+ # The overall_config_parms will be written out at the end.
366
+ # We make a copy for internal use
367
+ pulse_parms = copy.deepcopy(overall_config_parms)
368
+
369
+ # Calculate steps for each pulse, esp for last step
370
+ n_pulse = pulse_parms['n_step_dynamics'] / n_step_per_pulse
371
+ n_step_list = [n_step_per_pulse for i in range(n_pulse)]
372
+ n_excess_step = pulse_parms['n_step_dynamics'] % n_step_per_pulse
373
+ if n_excess_step > 0:
374
+ n_pulse += 1
375
+ n_step_list.append(n_excess_step)
376
+
377
+ # Prepare restart files for first step
378
+ pulse_parms['topology'] = os.path.abspath(pulse_parms['topology'])
379
+ in_basename = pulse_parms['input_md_name']
380
+ pulse_parms['input_md_name'] = os.path.abspath(in_basename)
381
+
382
+ # Now loop through pulses
383
+ timer = util.Timer()
384
+ save_dir = os.getcwd()
385
+ pulses = ["pulse%d" % i for i in range(n_pulse)]
386
+ for pulse, n_step in zip(pulses, n_step_list):
387
+ print("Pulse: %s/%d" % (pulse, n_pulse))
388
+
389
+ os.chdir(save_dir)
390
+ util.goto_dir(pulse)
391
+
392
+ pulse_parms['n_step_dynamics'] = n_step
393
+
394
+ soup = soup_from_restart_files(pulse_parms['input_md_name'])
395
+
396
+ # Apply forces by modifying the velocities directly
397
+ pulse_fn(soup)
398
+
399
+ crds, vels = write_soup_to_crds_and_vels(
400
+ force_field, soup, basename + '.pulse.in')
401
+ pulse_parms['input_crds'] = crds
402
+ pulse_parms['input_vels'] = vels
403
+
404
+ run_simulation_with_parameters(pulse_parms)
405
+
406
+ # Setup new restart files based on just-finished pulse
407
+ pulse_parms['input_md_name'] = os.path.abspath(basename)
408
+
409
+ os.chdir(save_dir)
410
+
411
+ merge_simulations(force_field, basename, pulses)
412
+
413
+ # cleanup pulses after merging
414
+ util.clean_fname(*pulses)
415
+
416
+ # everything worked, no exceptions thrown
417
+ open(basename+'.time', 'w').write(timer.str()+'\n')
418
+ util.write_dict(config, overall_config_parms)
419
+
420
+
@@ -0,0 +1,73 @@
1
+ import array
2
+ import math
3
+ from . import v3
4
+
5
+
6
+ class SpaceHash(object):
7
+ """
8
+ A geometrical object to sort a set of vertices into disjoint
9
+ boxes to optimize pair sorting by distance.
10
+
11
+ The boxes are defined by div, and close pairs generates listing
12
+ of indices of the vertices that are within div apart.
13
+ """
14
+
15
+ def __init__(self, vertices, div=5.3, padding=0.05):
16
+ self.vertices = vertices
17
+ self.div = div
18
+ self.inv_div = 1.0 / self.div
19
+ self.padding = padding
20
+
21
+ zero3 = lambda: [0.0] * 3
22
+ self.minima = zero3()
23
+ self.maxima = zero3()
24
+ self.spans = zero3()
25
+ self.sizes = zero3()
26
+
27
+ for i in range(3):
28
+ self.minima[i] = min([v[i] for v in self.vertices])
29
+ self.maxima[i] = max([v[i] for v in self.vertices])
30
+ self.minima[i] -= self.padding
31
+ self.maxima[i] += self.padding
32
+ self.spans[i] = self.maxima[i] - self.minima[i]
33
+ self.sizes[i] = int(math.ceil(self.spans[i] * self.inv_div))
34
+
35
+ self.size1_size2 = self.sizes[1] * self.sizes[2]
36
+ self.size2 = self.sizes[2]
37
+
38
+ self.cells = {}
39
+ self.spaces = []
40
+ for i_vertex, vertex in enumerate(self.vertices):
41
+ space = self.vertex_to_space(vertex)
42
+ self.spaces.append(space)
43
+ space_hash = self.space_to_hash(space)
44
+ cell = self.cells.setdefault(space_hash, array.array('L'))
45
+ cell.append(i_vertex)
46
+
47
+ def vertex_to_space(self, v):
48
+ return [int((v[i] - self.minima[i]) * self.inv_div) for i in range(3)]
49
+
50
+ def space_to_hash(self, s):
51
+ return s[0] * self.size1_size2 + s[1] * self.size2 + s[2]
52
+
53
+ def neighbourhood(self, space):
54
+ def neighbourhood_in_dim(space, i_dim):
55
+ i = max(0, space[i_dim] - 1)
56
+ j = min(self.sizes[i_dim], space[i_dim] + 2)
57
+ return list(range(i, j))
58
+ for s0 in neighbourhood_in_dim(space, 0):
59
+ for s1 in neighbourhood_in_dim(space, 1):
60
+ for s2 in neighbourhood_in_dim(space, 2):
61
+ yield [s0, s1, s2]
62
+
63
+ def close_pairs(self):
64
+ n_vertex = len(self.vertices)
65
+ for i_vertex0 in range(n_vertex):
66
+ space0 = self.spaces[i_vertex0]
67
+ for space1 in self.neighbourhood(space0):
68
+ hash1 = self.space_to_hash(space1)
69
+ for i_vertex1 in self.cells.get(hash1, []):
70
+ if i_vertex0 < i_vertex1:
71
+ yield i_vertex0, i_vertex1
72
+
73
+