PyParticles3 0.4.0__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 (100) hide show
  1. pyparticles/__init__.py +26 -0
  2. pyparticles/__main__.py +7 -0
  3. pyparticles/animation/__init__.py +21 -0
  4. pyparticles/animation/animated_cli.py +53 -0
  5. pyparticles/animation/animated_ogl.py +691 -0
  6. pyparticles/animation/animated_ogl_compat.py +243 -0
  7. pyparticles/animation/animated_scatter.py +94 -0
  8. pyparticles/animation/animation.py +251 -0
  9. pyparticles/animation/test_animation.py +310 -0
  10. pyparticles/demo/__init__.py +33 -0
  11. pyparticles/demo/bubble.py +106 -0
  12. pyparticles/demo/electromagnetic_demo.py +128 -0
  13. pyparticles/demo/electrostatic_demo.py +126 -0
  14. pyparticles/demo/fountain.py +478 -0
  15. pyparticles/demo/gas_lennard_jones.py +87 -0
  16. pyparticles/demo/gravity_clusters.py +73 -0
  17. pyparticles/demo/solar_system.py +222 -0
  18. pyparticles/demo/springs.py +141 -0
  19. pyparticles/demo/springs_constr.py +135 -0
  20. pyparticles/demo/test.py +34 -0
  21. pyparticles/forces/__init__.py +21 -0
  22. pyparticles/forces/const_force.py +140 -0
  23. pyparticles/forces/damping.py +174 -0
  24. pyparticles/forces/drag.py +167 -0
  25. pyparticles/forces/electromagnetic.py +105 -0
  26. pyparticles/forces/electromagnetic_field.py +95 -0
  27. pyparticles/forces/electrostatic.py +72 -0
  28. pyparticles/forces/force.py +75 -0
  29. pyparticles/forces/force_constrained.py +32 -0
  30. pyparticles/forces/fused_const_drag.py +445 -0
  31. pyparticles/forces/gravity.py +345 -0
  32. pyparticles/forces/lennard_jones.py +74 -0
  33. pyparticles/forces/linear_spring.py +86 -0
  34. pyparticles/forces/linear_spring_constrained.py +72 -0
  35. pyparticles/forces/multiple_force.py +134 -0
  36. pyparticles/forces/pseudo_bubble.py +226 -0
  37. pyparticles/forces/van_der_waals_force.py +60 -0
  38. pyparticles/forces/vector_field_force.py +52 -0
  39. pyparticles/geometry/__init__.py +21 -0
  40. pyparticles/geometry/dist.py +24 -0
  41. pyparticles/geometry/intersection.py +62 -0
  42. pyparticles/geometry/transformations.py +387 -0
  43. pyparticles/main/__init__.py +21 -0
  44. pyparticles/main/main.py +466 -0
  45. pyparticles/measures/__init__.py +21 -0
  46. pyparticles/measures/elastic_potential_energy.py +67 -0
  47. pyparticles/measures/gravitational_potential_energy.py +69 -0
  48. pyparticles/measures/kinetic_energy.py +68 -0
  49. pyparticles/measures/mass.py +59 -0
  50. pyparticles/measures/measure.py +156 -0
  51. pyparticles/measures/momentum.py +144 -0
  52. pyparticles/measures/total_energy.py +68 -0
  53. pyparticles/ode/__init__.py +21 -0
  54. pyparticles/ode/euler_solver.py +214 -0
  55. pyparticles/ode/euler_solver_constrained.py +49 -0
  56. pyparticles/ode/leapfrog_solver.py +37 -0
  57. pyparticles/ode/leapfrog_solver_constrained.py +53 -0
  58. pyparticles/ode/midpoint_solver.py +43 -0
  59. pyparticles/ode/midpoint_solver_constrained.py +60 -0
  60. pyparticles/ode/ode_solver.py +134 -0
  61. pyparticles/ode/ode_solver_constrained.py +43 -0
  62. pyparticles/ode/runge_kutta_solver.py +79 -0
  63. pyparticles/ode/runge_kutta_solver_constrained.py +98 -0
  64. pyparticles/ode/sim_time.py +56 -0
  65. pyparticles/ode/stormer_verlet_solver.py +50 -0
  66. pyparticles/ode/stormer_verlet_solver_constrained.py +73 -0
  67. pyparticles/ogl/__init__.py +21 -0
  68. pyparticles/ogl/axis_ogl.py +210 -0
  69. pyparticles/ogl/draw_particles_ogl.py +313 -0
  70. pyparticles/ogl/draw_particles_ogl_compat.py +509 -0
  71. pyparticles/ogl/draw_vector_field.py +221 -0
  72. pyparticles/ogl/opencl_gl_vbo.py +485 -0
  73. pyparticles/ogl/trackball.py +131 -0
  74. pyparticles/ogl/translate_scene.py +87 -0
  75. pyparticles/pset/__init__.py +21 -0
  76. pyparticles/pset/boundary.py +69 -0
  77. pyparticles/pset/cluster.py +28 -0
  78. pyparticles/pset/constrained_force_interactions.py +63 -0
  79. pyparticles/pset/constrained_x.py +158 -0
  80. pyparticles/pset/constraint.py +42 -0
  81. pyparticles/pset/default_boundary.py +43 -0
  82. pyparticles/pset/file_cluster.py +131 -0
  83. pyparticles/pset/logger.py +152 -0
  84. pyparticles/pset/octree.py +451 -0
  85. pyparticles/pset/opencl_context.py +374 -0
  86. pyparticles/pset/particles_set.py +499 -0
  87. pyparticles/pset/periodic_boundary.py +36 -0
  88. pyparticles/pset/rand_cluster.py +188 -0
  89. pyparticles/pset/rebound_boundary.py +68 -0
  90. pyparticles/utils/__init__.py +21 -0
  91. pyparticles/utils/parse_args.py +72 -0
  92. pyparticles/utils/problem_config.py +608 -0
  93. pyparticles/utils/pypart_global.py +122 -0
  94. pyparticles/utils/time_formatter.py +50 -0
  95. pyparticles3-0.4.0.dist-info/METADATA +395 -0
  96. pyparticles3-0.4.0.dist-info/RECORD +100 -0
  97. pyparticles3-0.4.0.dist-info/WHEEL +5 -0
  98. pyparticles3-0.4.0.dist-info/entry_points.txt +3 -0
  99. pyparticles3-0.4.0.dist-info/licenses/LICENSE-gpl-3.0.txt +674 -0
  100. pyparticles3-0.4.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,499 @@
1
+ # PyParticles : Particles simulation in python
2
+ # Copyright (C) 2012 Simone Riva
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+
17
+
18
+ import numpy as np
19
+ from collections import deque
20
+
21
+ import pyparticles.pset.logger as log
22
+
23
+ import random
24
+ import string
25
+
26
+ class ParticlesSet(object):
27
+ """
28
+ The main class for storing the particles data set.
29
+
30
+ Constructor:
31
+
32
+ :param size: (default 1) Number of particles
33
+ :param dim: (default 3) dimensions of the system 2 or 3 ... 2D 3D
34
+ :param mass: (dafault True) if True the particles have a mass.
35
+ :param label: (default False) if true it's possible to set a name for each particle
36
+ :param velocity: (dafault True) if true the particles has a velocity
37
+ :param charge: (default False) if true the particles have an electric charge.
38
+ :param log_X: (default False) if true it's possible to logging the position
39
+ :param log_V: (default False) if true it's possible to logging the velocity
40
+ :param log_max_size: (default 0) set the maximal size of the log queue
41
+ :param dtype: (default np.float64) the floating point type ot the set
42
+
43
+ .. note::
44
+
45
+ The properties: position X and velocity V are mandatory.
46
+ """
47
+ def __init__( self , size=1 , dim=3 , boundary=None ,
48
+ label=False , mass=True , velocity=True , charge=False ,
49
+ log_X=False , log_V=False , log_max_size=0 , dtype=np.float64 ):
50
+
51
+ if size < 0 :
52
+ raise
53
+
54
+ self.__dtype = dtype
55
+
56
+ self.__X = np.zeros((size,dim) , dtype=dtype )
57
+
58
+ if velocity:
59
+ self.__V = np.zeros((size,dim) , dtype=dtype )
60
+ else:
61
+ self.__V = None
62
+
63
+ if mass :
64
+ self.__mass = np.zeros((size,1) , dtype=dtype )
65
+ else:
66
+ self.__mass = None
67
+
68
+ if charge :
69
+ self.__Q = np.zeros(( size , 1 ) , dtype=dtype )
70
+ else:
71
+ self.__Q = None
72
+
73
+ if not label :
74
+ self.__label = None
75
+ else:
76
+ self.__label = list( "" for i in range(size) )
77
+
78
+ self.__size = int( size )
79
+ self.__dim = int( dim )
80
+ self.__centre_mass = None
81
+
82
+ self.__bound = boundary
83
+
84
+ self.__unit = 1.0
85
+ self.__mass_unit = 1.0
86
+
87
+ self.__log = dict()
88
+ self.__default_logger = None
89
+
90
+ self.__property_dict = dict()
91
+ self.__property_dict['X'] = self.__X
92
+ self.__property_dict['V'] = self.__V
93
+
94
+ if self.__mass is not None :
95
+ self.__property_dict['M'] = self.__mass
96
+
97
+ if self.__label is not None :
98
+ self.__property_dict['label'] = self.__label
99
+
100
+ if self.__Q is not None :
101
+ self.__property_dict['Q'] = self.__Q
102
+
103
+ self.__notify_set_changed = []
104
+
105
+
106
+ def realloc( self , size , dim , boundary=None ,
107
+ label=False , mass=True , velocity=True , charge=False ,
108
+ log_X=False , log_V=False , log_max_size=0 ):
109
+ """
110
+ Realloc the particle set, it uses the same args of the constructor,
111
+
112
+ **Attention!** this method remove the dictionary of the of the extra properties
113
+ """
114
+ del self.__X
115
+ del self.__V
116
+ del self.__mass
117
+ del self.__label
118
+ del self.__property_dict
119
+
120
+ self.__init__( size , dim , boundary , label , mass , velocity , charge , log_X , log_V , log_max_size )
121
+
122
+
123
+ def resize( self , new_size ):
124
+ """
125
+ Resize the particles set with the new_size.
126
+
127
+ If the new size is bigger the old data are copied in the new particles, according to the function numpy.resize
128
+ if it is smaller it cancels the data.
129
+
130
+ If the property is a list, the new elements will be filled with 'None' or empty string for the labels
131
+
132
+ The dim of the set will be not changed.
133
+ """
134
+
135
+ for k in self.__property_dict.keys() :
136
+ if self.__property_dict[k] is None :
137
+ continue
138
+
139
+ if k == "label" :
140
+ lst = list( "" for i in range(new_size) )
141
+
142
+ mn = min( [ self.size , new_size ] )
143
+ lst[:mn] = self.__label[:mn]
144
+
145
+ self.__property_dict[k] = list( lst )
146
+ self.__label = lst
147
+
148
+ elif isinstance( self.__property_dict[k] , list ) :
149
+ lst = list( None for i in range(new_size) )
150
+
151
+ mn = min( [ self.size , new_size ] )
152
+ lst[:mn] = self.__property_dict[k][:mn]
153
+ self.__property_dict[k] = lst
154
+
155
+ else :
156
+ NP = np.resize( self.__property_dict[k] ,
157
+ ( new_size , self.__property_dict[k].shape[1] ) )
158
+ self.__property_dict[k] = NP
159
+
160
+ if k == "M" :
161
+ self.__mass = NP
162
+ elif k == "X" :
163
+ self.__X = NP
164
+ elif k == "V" :
165
+ self.__V = NP
166
+ elif k == "Q" :
167
+ self.__Q = NP
168
+
169
+ self.__size = int( new_size )
170
+
171
+
172
+ def get_by_name( self , property_name ):
173
+ """
174
+ Return a property reference by name:
175
+ for example 'X' , 'V' , 'M' , 'Q' ...
176
+
177
+ :param property_name: The name of a property
178
+
179
+ ::
180
+
181
+ # set to [1,2,3] the coordinates of the 10th particle
182
+ pset.get_by_name('X')[10,:] = [1,2,3]
183
+ """
184
+ return self.__property_dict[property_name]
185
+
186
+
187
+ def add_property_by_name( self , property_name , dim=None , model="numpy_array" , to_type=None ):
188
+ """
189
+ Insert a new property by name. If the dim is not specified it uses the current dimension of the set.
190
+
191
+ If the model of the property is 'list' the dim is forced to 1
192
+
193
+ :param property_name: the name of the new property
194
+ :param dim: the dimension of the new property ( 2 = "2D , 3 = 3D ... )
195
+ :param model: 'list' or 'numpy_array'
196
+ :param to_type: [self.dtype] an array-numpy type for the model 'numpy_array' [ np.float64 , np.int64 ... ]
197
+
198
+
199
+ For example add 'friction' or 'radius':
200
+ ::
201
+
202
+ # Add the friction to the particles set
203
+ pset.add_property_by_name( "friction" , dim=1 , to_type=np.float32 )
204
+ pset.add_property_by_name( "radius" , dim=1 , to_type=np.float64 )
205
+ """
206
+
207
+ if to_type is None :
208
+ to_type = self.dtype
209
+
210
+ if dim is None :
211
+ dim = self.dim
212
+
213
+ if model == "numpy_array" :
214
+ self.__property_dict[property_name] = to_type( np.zeros(( self.size , dim ) ) )
215
+ elif model == "list" :
216
+ self.__property_dict[property_name] = list( None for i in range(self.size) )
217
+
218
+
219
+ def get_properties_names(self):
220
+ """
221
+ Return a list of containing the names of all properties
222
+ """
223
+ return list( self.__property_dict.keys() )
224
+
225
+ def get_dtype(self):
226
+ return self.__dtype
227
+
228
+ dtype = property( get_dtype , doc="return the dtype of the set" )
229
+
230
+ def getX(self):
231
+ return self.__X
232
+
233
+ X = property( getX , doc="return the reference to the array of the positions" )
234
+
235
+
236
+ def getM(self):
237
+ return self.__mass
238
+
239
+ M = property( getM , doc="return the reference to the array of the masses" )
240
+
241
+
242
+ def getQ(self):
243
+ return self.__Q
244
+
245
+ Q = property( getQ , doc="return the reference to the array of the charges" )
246
+
247
+
248
+ def getV(self):
249
+ return self.__V
250
+
251
+ V = property( getV , doc="return the reference to the velocities array" )
252
+
253
+
254
+ def get_list( self , i , to=float ):
255
+ """
256
+ return a list containing all data of the i-th particle
257
+ TODO: adapt to property by name
258
+ """
259
+ #
260
+ #lst = []
261
+ #for k in self.__property_dict.keys() :
262
+ # pass
263
+
264
+ lstX = []
265
+ lstV = []
266
+ lstM = to( self.M[i] )
267
+
268
+ for j in range( self.dim ):
269
+ lstX.append( to( self.X[i,j] ) )
270
+ lstV.append( to( self.V[i,j] ) )
271
+
272
+ lst = lstX + lstV
273
+ lst.append( lstM )
274
+
275
+ if self.__label is not None :
276
+ lst.append( self.__label[i] )
277
+
278
+ return lst
279
+
280
+
281
+ def get_label( self ):
282
+ return self.__label
283
+
284
+ label = property( get_label , doc="return the reference to the label list" )
285
+
286
+
287
+ def append( self , p_dict ) :
288
+ """
289
+ Append the particle(s) described in the given dictionary
290
+
291
+ If the particle don't contain every required data will be rejected.
292
+
293
+ The dictionary *p_dict* must contains the name of the property and it's value, and it **must include all property**, also the user defined!
294
+ """
295
+
296
+ for k in p_dict.keys():
297
+ if k not in self.__property_dict :
298
+ raise ValueError
299
+
300
+ for kpr in self.__property_dict.keys() :
301
+ if isinstance( self.__property_dict[kpr] , list ):
302
+ self.__property_dict[kpr].append( p_dict[kpr] )
303
+ else :
304
+ self.__property_dict[kpr] = np.append( self.__property_dict[kpr] , p_dict[kpr] , 0 )
305
+ if kpr == "X" :
306
+ self.__X = self.__property_dict[kpr]
307
+ elif kpr == "V" :
308
+ self.__V = self.__property_dict[kpr]
309
+ elif kpr == "M" :
310
+ self.__mass = self.__property_dict[kpr]
311
+ elif kpr == "Q" :
312
+ self.__Q = self.__property_dict[kpr]
313
+
314
+ self.notify_set_changed()
315
+
316
+ def notify_set_changed(self):
317
+ """
318
+ Call this methods when the particle set is modified.
319
+ """
320
+ for e in self.__notify_set_changed :
321
+ e.particles_set_changed( self )
322
+
323
+ def add_set_changed_listener( self , listener ) :
324
+ """
325
+ Add an object that contains a member methods called: *particles_set_changed( pset )* that there will be called if the particle set is modified.
326
+ """
327
+ self.__notify_set_changed.append( listener )
328
+
329
+ def update_boundary( self ):
330
+ """
331
+ Update the particle set according to the boundary rule
332
+ """
333
+ if self.__bound is not None :
334
+ self.__bound.boundary( self )
335
+
336
+ def get_boundary( self ):
337
+ return self.__bound
338
+
339
+ def set_boundary( self , boundary):
340
+ self.__bound = boundary
341
+
342
+ boundary = property( get_boundary , set_boundary , doc="return the reference to the boundary, None if the boundary are not set or open")
343
+
344
+
345
+
346
+
347
+ def append_logger( self , logger , key=None ):
348
+ if key is None :
349
+ key = "".join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for x in range(40))
350
+
351
+ self.__log[key] = logger
352
+
353
+ if self.__default_logger is None :
354
+ self.__default_logger = key
355
+
356
+ return key
357
+
358
+
359
+ def enable_log( self , log_X=True , log_V=False , sim_time=None , log_max_size=0 ):
360
+ """
361
+ Eanble the X and V logging:
362
+
363
+ :param log_X=True: log the positions
364
+ :param log_V=False: log the velocity
365
+ :param log_max_size: max size of the log queue
366
+ """
367
+
368
+ if len( self.__log ) == 0 :
369
+ logg = log.Logger( self , log_max_size=log_max_size , log_X=log_X , log_V=log_V , sim_time=sim_time )
370
+ self.append_logger( logg )
371
+
372
+
373
+ def log(self):
374
+ """
375
+ | If the log is enabled, save the current status in the log queue.
376
+ | The last element of the queue will be removed if we reach the max allowed size
377
+ """
378
+ for key in self.__log.keys() :
379
+ self.__log[key].log()
380
+
381
+ def close_log(self):
382
+ """
383
+ This function must be called after the end of the simulation for closing the log procedure.
384
+ """
385
+ for key in self.__log.keys() :
386
+ self.__log[key].close_log()
387
+
388
+
389
+ def get_log_max_size( self ):
390
+ return self.__log[self.__default_logger].log_max_size
391
+
392
+ def set_log_max_size( self , log_max_size ):
393
+ self.__log[self.__default_logger].log_max_size = log_max_size
394
+
395
+ log_max_size = property( get_log_max_size , set_log_max_size , doc="set and get the max allowed size of the log")
396
+
397
+
398
+
399
+ def get_log_array( self , i , log_X=True , log_V=False ):
400
+ return self.__log[self.__default_logger].get_log_array( i , log_X , log_V )
401
+
402
+
403
+ def read_log_array( self , i , ta , log_X=True , log_V=False ):
404
+ return self.__log[self.__default_logger].read_log_array( i , ta , log_X , log_V )
405
+
406
+
407
+ def get_log_indices_segments( self , full=False ):
408
+ return self.__log[self.__default_logger].get_log_indices_segments( full )
409
+
410
+
411
+ def set_default_logger( self , key ):
412
+
413
+ if key not in self.__log.keys() :
414
+ raise ValueError("A log named %s do not exits" % key )
415
+
416
+ self.__default_logger = key
417
+
418
+
419
+
420
+ def get_log_size(self):
421
+ return self.__log[self.__default_logger].log_size
422
+
423
+ log_size = property( get_log_size )
424
+
425
+ def get_log_X_enabled(self):
426
+ if not self.log_enabled :
427
+ return False
428
+ else :
429
+ return self.__log[self.__default_logger].log_X_enabled
430
+
431
+ def get_log_V_enabled(self):
432
+ if not self.log_enabled :
433
+ return False
434
+ else :
435
+ return self.__log[self.__default_logger].log_V_enabled
436
+
437
+ def get_log_enabled(self):
438
+ return len( self.__log ) > 0
439
+
440
+ log_V_enabled = property( get_log_V_enabled , doc="return true if the logging of the position is enabled")
441
+ log_X_enabled = property( get_log_X_enabled , doc="return true if the logging of the velocity is enabled")
442
+
443
+ log_enabled = property( get_log_enabled , doc="return true if the logging of position or velocity is enabled")
444
+
445
+
446
+ def jump( self , indx ):
447
+ pass
448
+
449
+
450
+ def set_unit( self , u ):
451
+ self.__unit = u
452
+
453
+ def get_unit(self):
454
+ return self.__unit
455
+
456
+ unit = property( get_unit , set_unit , doc="set the unit length")
457
+
458
+
459
+ def set_mass_unit( self , u ):
460
+ self.__mass_unit = u
461
+
462
+ def get_mass_unit(self):
463
+ return self.__mass_unit
464
+
465
+ mass_unit = property( get_mass_unit , set_mass_unit , doc="set the unit mass" )
466
+
467
+
468
+ def update_centre_of_mass(self):
469
+ """
470
+ Compute and return the center of mass
471
+ """
472
+ self.__centre_mass = np.sum( self.__X * self.__mass , axis=0 ) / self.dtype( self.__size )
473
+ return self.__centre_mass
474
+
475
+ def centre_of_mass(self):
476
+ """
477
+ Return the stored center of mass.
478
+
479
+ .. note::
480
+
481
+ this function don't compute the center of mass, but simply return the stored value.
482
+ """
483
+ return self.__centre_mass
484
+
485
+ def get_dim(self):
486
+ return self.__dim
487
+
488
+ def get_size( self ):
489
+ return self.__size
490
+
491
+ dim = property( get_dim , doc="get the dim of the set" )
492
+
493
+ size = property( get_size , doc="get the size of the set" )
494
+
495
+ def add_clusters( self , Cs , n ):
496
+ i = 0
497
+ for c in Cs:
498
+ self.__X[n[i]:n[i]+c.shape[0]] = c
499
+ i = i + 1
@@ -0,0 +1,36 @@
1
+
2
+ # PyParticles : Particles simulation in python
3
+ # Copyright (C) 2012 Simone Riva
4
+ #
5
+ # This program is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU General Public License as published by
7
+ # the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # This program is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU General Public License
16
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ import numpy as np
19
+ import pyparticles.pset.boundary as bd
20
+
21
+
22
+ class PeriodicBoundary( bd.Boundary ):
23
+
24
+ def __init__( self , bound=(-1,1) , dim=3 ):
25
+ self.set_boundary( bound , dim )
26
+
27
+
28
+ def boundary( self , p_set ):
29
+ for i in range( self.dim ) :
30
+ delta = self.bound[i,1] - self.bound[i,0]
31
+
32
+ b_mi = p_set.X[:,i] < self.bound[i,0]
33
+ b_mx = p_set.X[:,i] > self.bound[i,1]
34
+
35
+ p_set.X[b_mi,i] = p_set.X[b_mi,i] + delta
36
+ p_set.X[b_mx,i] = p_set.X[b_mx,i] - delta