fargopy 0.2.1__py3-none-any.whl → 0.3.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.
fargopy/__init__.py CHANGED
@@ -1,10 +1,7 @@
1
1
  ###############################################################
2
- # Import montu modules
2
+ # Version
3
3
  ###############################################################
4
4
  from fargopy.version import *
5
- from fargopy.util import *
6
- from fargopy.sys import *
7
- from fargopy.fargo3d import *
8
5
 
9
6
  ###############################################################
10
7
  # External modules
@@ -23,88 +20,59 @@ import numpy as np
23
20
  DEG = np.pi/180
24
21
  RAD = 1/DEG
25
22
 
26
- FP_HOME = os.environ['HOME']
27
- FP_DOTDIR = f"{FP_HOME}/.fargopy"
28
- FP_RCFILE = f"{FP_DOTDIR}/fargopyrc"
29
-
30
- # Default configuration
31
- FP_CONFIGURATION = f"""# This is the configuration variables for FARGOpy
32
- # Package
33
- FP_VERSION = '{version}'
34
- # System
35
- FP_HOME = '{FP_HOME}'
36
- # Directories
37
- FP_DOTDIR = '{FP_DOTDIR}'
38
- FP_RCFILE = '{FP_RCFILE}'
39
- # Behavior
40
- FP_VERBOSE = False
41
- # FARGO3D variablles
42
- FP_FARGO3D_CLONECMD = 'git clone https://bitbucket.org/fargo3d/public.git'
43
- FP_FARGO3D_BASEDIR = './'
44
- FP_FARGO3D_PACKDIR = 'fargo3d/'
45
- FP_FARGO3D_BINARY = 'fargo3d'
46
- FP_FARGO3D_HEADER = 'src/fargo3d.h'
47
- """
48
-
49
- FP_INITIAL_SCRIPT = """
50
- import sys
51
- import fargopy as fp
52
- fp.initialize(' '.join(sys.argv))
53
- """
54
-
55
23
  ###############################################################
56
- # Base routines
24
+ # Base classes
57
25
  ###############################################################
58
26
  class Debug(object):
27
+ """The Debug class control the Debugging messages of the package.
28
+
29
+ Attribute:
30
+ VERBOSE: bool, default = False:
31
+ If True all the trace messages are shown.
32
+
33
+ Static methods:
34
+ trace(msg):
35
+ Show a debugging message if VERBOSE=True
36
+ Example:
37
+ >>> import fargopy as fp
38
+ >>> fp.Debug.VERBOSE = True
39
+ >>> fp.initialize('configure')
40
+ """
59
41
  VERBOSE = False
60
42
  @staticmethod
61
43
  def trace(msg):
62
44
  if Debug.VERBOSE:
63
45
  print("::"+msg)
64
46
 
65
- def initialize(options='', force=False):
66
- if ('configure' in options) or ('all' in options):
67
- # Create configuration directory
68
- if not os.path.isdir(FP_DOTDIR) or force:
69
- Debug.trace(f"Configuring FARGOpy...")
70
- # Create directory
71
- os.system(f"mkdir -p {FP_DOTDIR}")
72
- # Create configuration variables
73
- f = open(f"{FP_DOTDIR}/fargopyrc",'w')
74
- f.write(FP_CONFIGURATION)
75
- f.close()
76
- # Create initialization script
77
- f = open(f"{FP_DOTDIR}/ifargopy.py",'w')
78
- f.write(FP_INITIAL_SCRIPT)
79
- f.close()
80
-
81
- if ('download' in options) or ('all' in options):
82
- print("Downloading FARGOpy...")
83
- fargo_dir = f"{FP_FARGO3D_BASEDIR}/{FP_FARGO3D_PACKDIR}".replace('//','/')
84
- if not os.path.isdir(fargo_dir):
85
- fargopy.Sys.simple(f"{FP_FARGO3D_CLONECMD} {FP_FARGO3D_PACKDIR}")
86
- print(f"\tFARGO3D downloaded to {fargo_dir}")
87
- else:
88
- print(f"\tFARGO3D directory already present in '{fargo_dir}'")
89
-
90
- fargo_header = f"{fargo_dir}/{FP_FARGO3D_HEADER}"
91
- if not os.path.isfile(fargo_header):
92
- print(f"No header file for fargo found in '{fargo_header}'")
93
-
94
- if ('compile' in options) or ('all' in options):
95
- print("Test compilation")
96
- pass
97
-
98
47
  class Dictobj(object):
99
48
  """Convert a dictionary to an object
100
49
 
50
+ Initialization attributes:
51
+ dict: dictionary:
52
+ Dictionary containing the attributes.
53
+
54
+ Attributes:
55
+ All the keys in the initialization dictionary.
56
+
101
57
  Examples:
102
- ob = Dictobj(a=2,b=3)
103
- print(ob.a,ob.b)
104
- ob = Dictobj(dict=dict(a=2,b=3))
105
- print(ob.a,ob.b)
106
- ob = Dictobj(dict={'a':2,'b':3})
107
- print(ob.a,ob.b)
58
+ Three ways to initialize the same `Dictobj`:
59
+ >>> ob = Dictobj(a=2,b=3)
60
+ >>> ob = Dictobj(dict=dict(a=2,b=3))
61
+ >>> ob = Dictobj(dict={'a':2,'b':3})
62
+
63
+ In the three cases you may access the attributes using:
64
+ >>> print(ob.a,ob.b)
65
+
66
+ Methods:
67
+ keys():
68
+ It works like the keys() method of a dictionary.
69
+ item(key):
70
+ Recover the value of an attribute as it was a dictionary.
71
+ Example:
72
+ >>> ob.item('a')
73
+ print_keys():
74
+ Print a list of keys
75
+
108
76
  """
109
77
 
110
78
  def __init__(self, **kwargs):
@@ -115,6 +83,10 @@ class Dictobj(object):
115
83
  setattr(self, key, value)
116
84
 
117
85
  def keys(self):
86
+ """Show the list of attributes of Dictobj
87
+
88
+ This method works as the keys() method of a regular dictionary.
89
+ """
118
90
  props = []
119
91
  for i,prop in enumerate(self.__dict__.keys()):
120
92
  if '__' in prop:
@@ -122,7 +94,16 @@ class Dictobj(object):
122
94
  props += [prop]
123
95
  return props
124
96
 
97
+ def item(self,key):
98
+ """Get the value of an item of a Dictobj.
99
+ """
100
+ if key not in self.keys():
101
+ raise ValueError(f"Key 'key' not in Dictobj")
102
+ return self.__dict__[key]
103
+
125
104
  def print_keys(self):
105
+ """Print all the keys of a Dictobj.
106
+ """
126
107
  prop_list=''
127
108
  for i,prop in enumerate(self.keys()):
128
109
  prop_list += f"{prop}, "
@@ -130,17 +111,135 @@ class Dictobj(object):
130
111
  prop_list += '\n'
131
112
  print(prop_list.strip(', '))
132
113
 
133
- def item(self,key):
134
- if key not in self.keys():
135
- raise ValueError(f"Key 'key' not in Dictobj")
136
- return self.__dict__[key]
137
-
138
114
  def __str__(self):
139
115
  return str(self.__dict__)
140
116
 
141
117
  def __repr__(self):
142
118
  return self.__str__()
143
119
 
120
+ class Fargobj(object):
121
+ def __init__(self,**kwargs):
122
+ self.fobject = True
123
+ self.kwargs = kwargs
124
+
125
+ def set_property(self,property,default,method=lambda prop:prop):
126
+ """Set a property of object using a given method
127
+
128
+ Examples:
129
+ >>> obj = Fargobj()
130
+ >>> obj.set_property('a',1)
131
+ >>> print(obj.a)
132
+ 1
133
+ """
134
+ if property in self.kwargs.keys():
135
+ method(self.kwargs[property])
136
+ self.__dict__[property] = self.kwargs[property]
137
+ return True
138
+ else:
139
+ method(default)
140
+ self.__dict__[property] = default
141
+ return False
142
+
143
+ def has(self,key):
144
+ """Check if a key is an attribute of Fargobj object
145
+
146
+ Examples:
147
+ >>> obj = Fargobj(a=1)
148
+ >>> print(obj.has('a'))
149
+ True
150
+ """
151
+ if key in self.__dict__.keys():
152
+ return True
153
+ else:
154
+ return False
155
+
156
+ ###############################################################
157
+ # Package configuration
158
+ ###############################################################
159
+ # Basic (unmodifiable) variables
160
+ Conf = Dictobj()
161
+ Conf.FP_HOME = os.environ['HOME']
162
+ Conf.FP_DOTDIR = f"{Conf.FP_HOME}/.fargopy"
163
+ Conf.FP_RCFILE = f"{Conf.FP_DOTDIR}/fargopyrc"
164
+
165
+ # Default configuration file content
166
+ Conf.FP_CONFIGURATION = f"""# This is the configuration variables for FARGOpy
167
+ # Package
168
+ FP_VERSION = '{version}'
169
+ # System
170
+ FP_HOME = '{Conf.FP_HOME}/'
171
+ # Directories
172
+ FP_DOTDIR = '{Conf.FP_DOTDIR}'
173
+ FP_RCFILE = '{Conf.FP_RCFILE}'
174
+ # Behavior
175
+ FP_VERBOSE = False
176
+ # FARGO3D variablles
177
+ FP_FARGO3D_CLONECMD = 'git clone https://bitbucket.org/fargo3d/public.git'
178
+ FP_FARGO3D_BASEDIR = '{Conf.FP_HOME}'
179
+ FP_FARGO3D_PACKDIR = 'fargo3d/'
180
+ FP_FARGO3D_BINARY = 'fargo3d'
181
+ FP_FARGO3D_HEADER = 'src/fargo3d.h'
182
+ """
183
+
184
+ # Default initialization script
185
+ Conf.FP_INITIAL_SCRIPT = """
186
+ import sys
187
+ import fargopy as fp
188
+ fp.initialize(' '.join(sys.argv))
189
+ """
190
+
191
+ def initialize(options='', force=False):
192
+ """Initialization routine
193
+
194
+ Args:
195
+ options: string, default = '':
196
+ Action(s) to be performed. Valid actions include:
197
+ 'configure': configure the package.
198
+ 'download': download FARGO3D directory.
199
+ 'compile': attempt to compile FARGO3D in the machine.
200
+ 'all': all actions.
201
+
202
+ force: bool, default = False:
203
+ If True, force any action that depends on a previous condition.
204
+ For instance if options = 'configure' and force = True it will
205
+ override FARGOpy directory.
206
+ """
207
+ if ('configure' in options) or ('all' in options):
208
+ # Create configuration directory
209
+ if not os.path.isdir(Conf.FP_DOTDIR) or force:
210
+ Debug.trace(f"Configuring FARGOpy at {Conf.FP_DOTDIR}...")
211
+ # Create directory
212
+ os.system(f"mkdir -p {Conf.FP_DOTDIR}")
213
+ # Create configuration variables
214
+ f = open(f"{Conf.FP_DOTDIR}/fargopyrc",'w')
215
+ f.write(Conf.FP_CONFIGURATION)
216
+ f.close()
217
+ # Create initialization script
218
+ f = open(f"{Conf.FP_DOTDIR}/ifargopy.py",'w')
219
+ f.write(Conf.FP_INITIAL_SCRIPT)
220
+ f.close()
221
+ else:
222
+ Debug.trace(f"Configuration already in place.")
223
+
224
+ if ('download' in options) or ('all' in options):
225
+ print("Downloading FARGOpy...")
226
+ fargo_dir = f"{Conf.FP_FARGO3D_BASEDIR}/{Conf.FP_FARGO3D_PACKDIR}".replace('//','/')
227
+ if not os.path.isdir(fargo_dir) or force:
228
+ fargopy.Sys.simple(f"cd {Conf.FP_FARGO3D_BASEDIR};{Conf.FP_FARGO3D_CLONECMD} {Conf.FP_FARGO3D_PACKDIR}")
229
+ print(f"\tFARGO3D downloaded to {fargo_dir}")
230
+ else:
231
+ print(f"\tFARGO3D directory already present in '{fargo_dir}'")
232
+
233
+ fargo_header = f"{fargo_dir}/{Conf.FP_FARGO3D_HEADER}"
234
+ if not os.path.isfile(fargo_header):
235
+ print(f"No header file for fargo found in '{fargo_header}'")
236
+ else:
237
+ print(f"Header file for FARGO3D is in the fargo directory {fargo_dir}")
238
+
239
+ if ('compile' in options) or ('all' in options):
240
+ print("Test compilation")
241
+ pass
242
+
144
243
  ###############################################################
145
244
  # Initialization
146
245
  ###############################################################
@@ -148,23 +247,39 @@ class Dictobj(object):
148
247
  warnings.filterwarnings("ignore")
149
248
 
150
249
  # Read FARGOpy configuration variables
151
- if not os.path.isdir(FP_DOTDIR):
152
- Debug.trace(f"Configuring for the first time")
250
+ if not os.path.isdir(Conf.FP_DOTDIR):
251
+ print(f"Configuring FARGOpy for the first time")
153
252
  initialize('configure')
154
253
  Debug.trace(f"::Reading configuration variables")
155
- exec(open(f"{FP_RCFILE}").read())
156
- Debug.VERBOSE = FP_VERBOSE
254
+
255
+ # Load configuration variables into Conf
256
+ conf_dict = dict()
257
+ exec(open(f"{Conf.FP_RCFILE}").read(),dict(),conf_dict)
258
+ Conf.__dict__.update(conf_dict)
259
+
260
+ # Derivative configuration variables
261
+ Debug.VERBOSE = Conf.FP_VERBOSE
262
+ Conf.FP_FARGO3D_DIR = (Conf.FP_FARGO3D_BASEDIR + '/' + Conf.FP_FARGO3D_PACKDIR).replace('//','/')
263
+ Conf.FP_FARGO3D_LOCKFILE = f"{Conf.FP_DOTDIR}/fargopy.lock"
157
264
 
158
265
  # Check if version in RCFILE is different from installed FARGOpy version
159
- if FP_VERSION != version:
160
- print(f"Your configure file version '{FP_VERSION}' it is different than the installed version of FARGOpy '{version}'")
161
- ans = input(f"Do you want to update configuration file '{FP_RCFILE}'? [Y/n]: ")
266
+ if Conf.FP_VERSION != version:
267
+ print(f"Your configuration file version '{Conf.FP_VERSION}' it is different than the installed version of FARGOpy '{version}'")
268
+ ans = input(f"Do you want to update configuration file '{Conf.FP_RCFILE}'? [Y/n]: ")
162
269
  if ans and ('Y' not in ans.upper()):
163
270
  if 'N' in ans.upper():
164
- print("We will keeping asking you until you update it, sorry!")
271
+ print("We will keeping asking you this until you update it, sorry!")
165
272
  else:
166
- os.system(f"cp -rf {FP_RCFILE} {FP_RCFILE}.save")
273
+ os.system(f"cp -rf {Conf.FP_RCFILE} {Conf.FP_RCFILE}.save")
167
274
  initialize('configure',force=True)
168
275
 
276
+ ###############################################################
277
+ # Import package modules
278
+ ###############################################################
279
+ from fargopy.util import *
280
+ from fargopy.sys import *
281
+ from fargopy.fields import *
282
+ from fargopy.simulation import *
283
+
169
284
  # Showing version
170
285
  print(f"Running FARGOpy version {version}")
fargopy/fields.py ADDED
@@ -0,0 +1,221 @@
1
+ ###############################################################
2
+ # FARGOpy interdependencies
3
+ ###############################################################
4
+ import fargopy
5
+
6
+ ###############################################################
7
+ # Required packages
8
+ ###############################################################
9
+ import numpy as np
10
+ import re
11
+
12
+ ###############################################################
13
+ # Constants
14
+ ###############################################################
15
+ # Map of coordinates into FARGO3D coordinates
16
+ COORDS_MAP = dict(
17
+ cartesian = dict(x='x',y='y',z='z'),
18
+ cylindrical = dict(phi='x',r='y',z='z'),
19
+ spherical = dict(phi='x',r='y',theta='z'),
20
+ )
21
+
22
+ ###############################################################
23
+ # Classes
24
+ ###############################################################
25
+ class Field(fargopy.Fargobj):
26
+ """Fields:
27
+
28
+ Attributes:
29
+ coordinates: type of coordinates (cartesian, cylindrical, spherical)
30
+ data: numpy arrays with data of the field
31
+
32
+ Methods:
33
+ slice: get an slice of a field along a given spatial direction.
34
+ Examples:
35
+ density.slice(r=0.5) # Take the closest slice to r = 0.5
36
+ density.slice(ir=20) # Take the slice through the 20 shell
37
+ density.slice(phi=30*RAD,interp='nearest') # Take a slice interpolating to the nearest
38
+ """
39
+
40
+ def __init__(self,data=None,coordinates='cartesian',domains=None,type='scalar',**kwargs):
41
+ super().__init__(**kwargs)
42
+ self.data = data
43
+ self.coordinates = coordinates
44
+ self.domains = domains
45
+ self.type = type
46
+
47
+ def meshslice(self,slice=None,component=0):
48
+ """Perform a slice on a field and produce as an output the
49
+ corresponding field slice and the associated matrices of
50
+ coordinates for plotting.
51
+ """
52
+ # Analysis of the slice
53
+ if slice is None:
54
+ raise ValueError("You must provide a slice option.")
55
+
56
+ # Perform the slice
57
+ slice_cmd = f"self.slice({slice},pattern=True)"
58
+ slice,pattern = eval(slice_cmd)
59
+
60
+ # Create the mesh
61
+ if self.coordinates == 'cartesian':
62
+ z,y,x = np.meshgrid(self.domains.z,self.domains.y,self.domains.x,indexing='ij')
63
+ x = eval(f"x[{pattern}]")
64
+ y = eval(f"y[{pattern}]")
65
+ z = eval(f"z[{pattern}]")
66
+
67
+ mesh = fargopy.Dictobj(dict=dict(x=x,y=y,z=z))
68
+
69
+ if self.coordinates == 'cylindrical':
70
+ z,r,phi = np.meshgrid(self.domains.z,self.domains.r,self.domains.phi,indexing='ij')
71
+ x,y,z = r*np.cos(phi),r*np.sin(phi),z
72
+
73
+ x = eval(f"x[{pattern}]")
74
+ y = eval(f"y[{pattern}]")
75
+ z = eval(f"z[{pattern}]")
76
+ r = eval(f"r[{pattern}]")
77
+ phi = eval(f"phi[{pattern}]")
78
+
79
+ mesh = fargopy.Dictobj(dict=dict(r=r,phi=phi,x=x,y=y,z=z))
80
+
81
+ if self.coordinates == 'spherical':
82
+ theta,r,phi = np.meshgrid(self.domains.theta,self.domains.r,self.domains.phi,indexing='ij')
83
+ x,y,z = r*np.sin(theta)*np.cos(phi),r*np.sin(theta)*np.sin(phi),r*np.cos(theta)
84
+
85
+ x = eval(f"x[{pattern}]")
86
+ y = eval(f"y[{pattern}]")
87
+ z = eval(f"z[{pattern}]")
88
+ r = eval(f"r[{pattern}]")
89
+ phi = eval(f"phi[{pattern}]")
90
+ theta = eval(f"theta[{pattern}]")
91
+
92
+ mesh = fargopy.Dictobj(dict=dict(r=r,phi=phi,theta=theta,x=x,y=y,z=z))
93
+
94
+ return slice,mesh
95
+
96
+ def slice(self,quiet=True,pattern=False,**kwargs):
97
+ """Extract an slice of a 3-dimensional FARGO3D field
98
+
99
+ Parameters:
100
+ quiet: boolean, default = False:
101
+ If True extract the slice quietly.
102
+ Else, print some control messages.
103
+
104
+ pattern: boolean, default = False:
105
+ If True return the pattern of the slice, eg. [:,:,:]
106
+
107
+ ir, iphi, itheta, ix, iy, iz: string or integer:
108
+ Index or range of indexes of the corresponding coordinate.
109
+
110
+ r, phi, theta, x, y, z: float:
111
+ Value for slicing. The slicing search for the closest
112
+ value in the domain.
113
+
114
+ Returns:
115
+ slice: sliced field.
116
+
117
+ Examples:
118
+ # 0D: Get the value of the field in iphi = 0, itheta = -1 and close to r = 0.82
119
+ gasvz.slice(iphi=0,itheta=-1,r=0.82)
120
+
121
+ # 1D: Get all values of the field in radial direction at iphi = 0, itheta = -1
122
+ gasvz.slice(iphi=0,itheta=-1)
123
+
124
+ # 2D: Get all values of the field for values close to phi = 0
125
+ gasvz.slice(phi=0)
126
+ """
127
+ # By default slice
128
+ ivar = dict(x=':',y=':',z=':')
129
+
130
+ if len(kwargs.keys()) == 0:
131
+ pattern_str = f"{ivar['z']},{ivar['y']},{ivar['x']}"
132
+ if pattern:
133
+ return self.data, pattern_str
134
+ return self.data
135
+
136
+ # Check all conditions
137
+ for key,item in kwargs.items():
138
+ match = re.match('^i(.+)',key)
139
+ if match:
140
+ index = item
141
+ coord = match.group(1)
142
+ if not quiet:
143
+ print(f"Index condition {index} for coordinate {coord}")
144
+ ivar[COORDS_MAP[self.coordinates][coord]] = index
145
+ else:
146
+ if not quiet:
147
+ print(f"Numeric condition found for coordinate {key}")
148
+ if key in self.domains.keys():
149
+ # Check if value provided is in range
150
+ domain = self.domains.item(key)
151
+ extrema = self.domains.extrema[key]
152
+ min, max = extrema[0][1], extrema[1][1]
153
+ if (item<min) or (item>max):
154
+ raise ValueError(f"You are attempting to get a slice in {key} = {item}, but the valid range for this variable is [{min},{max}]")
155
+ find = abs(self.domains.item(key) - item)
156
+ ivar[COORDS_MAP[self.coordinates][key]] = find.argmin()
157
+
158
+ pattern_str = f"{ivar['z']},{ivar['y']},{ivar['x']}"
159
+
160
+ if self.type == 'scalar':
161
+ slice_cmd = f"self.data[{pattern_str}]"
162
+ if not quiet:
163
+ print(f"Slice: {slice_cmd}")
164
+ slice = eval(slice_cmd)
165
+
166
+ elif self.type == 'vector':
167
+ slice = np.array(
168
+ [eval(f"self.data[0,{pattern_str}]"),
169
+ eval(f"self.data[1,{pattern_str}]"),
170
+ eval(f"self.data[2,{pattern_str}]")]
171
+ )
172
+
173
+ if pattern:
174
+ return slice,pattern_str
175
+ return slice
176
+
177
+ def to_cartesian(self):
178
+ if self.type == 'scalar':
179
+ # Scalar fields are invariant under coordinate transformations
180
+ return self
181
+ elif self.type == 'vector':
182
+ # Vector fields must be transformed according to domain
183
+ if self.coordinates == 'cartesian':
184
+ return self
185
+
186
+ if self.coordinates == 'cylindrical':
187
+ z,r,phi = np.meshgrid(self.domains.z,self.domains.r,self.domains.phi,indexing='ij')
188
+ vphi = self.data[0]
189
+ vr = self.data[1]
190
+ vz = self.data[2]
191
+ vx = vr*np.cos(phi)
192
+ vy = vr*np.sin(phi)
193
+
194
+ return (Field(vx,coordinates=self.coordinates,domains=self.domains,type='scalar'),
195
+ Field(vy,coordinates=self.coordinates,domains=self.domains,type='scalar'),
196
+ Field(vz,coordinates=self.coordinates,domains=self.domains,type='scalar'))
197
+
198
+ if self.coordinates == 'spherical':
199
+
200
+ theta,r,phi = np.meshgrid(self.domains.theta,self.domains.r,self.domains.phi,indexing='ij')
201
+ vphi = self.data[0]
202
+ vr = self.data[1]
203
+ vtheta = self.data[2]
204
+
205
+ vx = vr*np.sin(theta)*np.cos(phi) + vtheta*np.cos(theta)*np.cos(phi) - vphi*np.sin(phi)
206
+ vy = vr*np.sin(theta)*np.sin(phi) + vtheta*np.cos(theta)*np.sin(phi) + vphi*np.cos(phi)
207
+ vz = vr*np.cos(theta) - vtheta*np.sin(theta)
208
+
209
+ return (Field(vx,coordinates=self.coordinates,domains=self.domains,type='scalar'),
210
+ Field(vy,coordinates=self.coordinates,domains=self.domains,type='scalar'),
211
+ Field(vz,coordinates=self.coordinates,domains=self.domains,type='scalar'))
212
+
213
+ def get_size(self):
214
+ return self.data.nbytes/1024**2
215
+
216
+ def __str__(self):
217
+ return str(self.data)
218
+
219
+ def __repr__(self):
220
+ return str(self.data)
221
+