class Params(object): """This class contains setters and getters for parameters""" def __init__(self, mesh, vars): self.mesh = mesh self.vars = vars self.fixed_vortices = FixedVortices(self.mesh, self.vars) self.solveA = False self.linear_coefficient = cfg.linear_coefficient # epsilon self.gl_parameter = cfg.gl_parameter # kappa self.normal_conductivity = cfg.normal_conductivity # sigma # homogeneous external magnetic field self._H = cfg.dtype(0.0) self.homogeneous_external_field_reset = cfg.homogeneous_external_field # x- and y- components of external vector potential for non-homogeneous external magnetic field self.ae, self.be = None, None # external and irregular vector potential # it should be kept self._vpei = (self.ae, self.be) + (ai, bi) self._vpei = None # non-homogeneous external magnetic field self.external_field = cfg.external_field self.order_parameter_Langevin_coefficient = cfg.order_parameter_Langevin_coefficient self.vector_potential_Langevin_coefficient = cfg.vector_potential_Langevin_coefficient def __del__(self): pass @property def linear_coefficient(self): """ Sets/gets epsilon (linear coefficient)""" if self._epsilon.size == 1: return np.full((cfg.Nx, cfg.Ny), self._epsilon.get_h(), dtype=cfg.dtype) else: return self._epsilon.get_h() @linear_coefficient.setter def linear_coefficient(self, linear_coefficient): if callable(linear_coefficient): xg, yg = mesh.xy_grid lc = linear_coefficient(xg, yg) else: lc = linear_coefficient if np.isscalar(lc): lc = lc * np.ones(1) else: assert lc.shape == (cfg.Nx, cfg.Ny) self._epsilon = GArray(like=lc.astype(cfg.dtype)) def linear_coefficient_h(self): if self._epsilon.size != 1: return self._epsilon.get_d_obj() return np.uintp(0) def linear_coefficient_scalar_h(self): if self._epsilon.size == 1: return self._epsilon.get_h() return cfg.dtype(0.0) @property def gl_parameter(self): """ Sets/gets GL parameter""" return self._kappa @gl_parameter.setter def gl_parameter(self, gl_parameter): if gl_parameter is None or np.isnan(gl_parameter) or np.isinf( gl_parameter): gl_parameter = np.inf assert isinstance(gl_parameter, (np.floating, float, np.integer, int)) and ( np.isposinf(gl_parameter) or gl_parameter > 0.0) self._kappa = cfg.dtype(gl_parameter) self.solveA = np.bool(not np.isposinf(self._kappa)) def gl_parameter_squared_h(self): if self.solveA: return cfg.dtype(self.gl_parameter**2) return cfg.dtype(-1.0) @property def normal_conductivity(self): """ Sets/gets normal conductivity""" return self._sigma @normal_conductivity.setter def normal_conductivity(self, normal_conductivity): assert isinstance(normal_conductivity, (np.floating, float, np.integer, int)) and normal_conductivity > 0.0 self._sigma = cfg.dtype(normal_conductivity) self._rho = cfg.dtype(1.0 / normal_conductivity) @property def homogeneous_external_field(self): """ Sets/gets homogeneous external field and does not update vector potential. """ return self._H @homogeneous_external_field.setter def homogeneous_external_field(self, homogeneous_external_field): self._H = cfg.dtype(homogeneous_external_field) def _update_vector_potential(self, homogeneous_external_field, reset): assert isinstance(homogeneous_external_field, (np.floating, float, np.integer, int)) if reset: self._H = cfg.dtype(homogeneous_external_field) # TODO: need a fill method in GArray # self.a.fill(0.0) # self.b.fill(0.0) a, b = self.vars._vp.get_vec_h() a.fill(0.0) b.fill(0.0) self.vars._vp.need_htod_sync() self.vars._vp.sync() delta_H = self._H else: delta_H = -self._H self._H = cfg.dtype(homogeneous_external_field) delta_H += self._H self.vars._vp.sync() # TODO: implement GPU version of ab initialization # Possible set of gauges, A = [g*y*H, (1-g)*x*H, 0] with any g, 0 <= g <= 1 g = 0.5 _, yg = self.mesh.xy_a_grid xg, _ = self.mesh.xy_b_grid a, b = self.vars._vp.get_vec_h() a -= g * (yg - 0.5 * cfg.Ly) * delta_H b += (1.0 - g) * (xg - 0.5 * cfg.Lx) * delta_H self.vars._vp.need_htod_sync() self.vars._vp.sync() def _homogeneous_external_field_delta(self, homogeneous_external_field): self._update_vector_potential(homogeneous_external_field, reset=False) homogeneous_external_field_delta = property( fset=_homogeneous_external_field_delta, doc="""Sets homogeneous external field, H, and adds to the vector potential deltaA, satisfying curl(deltaA) = deltaH, where deltaH = H - Hold and Hold is homogeneous external field before update.""") def _homogeneous_external_field_reset(self, homogeneous_external_field): self._update_vector_potential(homogeneous_external_field, reset=True) homogeneous_external_field_reset = property( fset=_homogeneous_external_field_reset, doc="""Sets homogeneous external field, H, and sets vector potential, A, satisfying curl(A) = H.""") def _update_gvpei(self): """Sets self.gvpei = (self.ae, self.be) + (ai, bi). To be executed in self.external_vector_potential and self.fixed_vortices setters.""" assert (self.ae is None) == (self.be is None) ai, bi = None, None if self.fixed_vortices is not None and self.fixed_vortices._vpi is not None: ai, bi = self.fixed_vortices._vpi.get_vec_h() assert (ai is None) == (bi is None) vpei = None if self.ae is not None: if ai is not None: vpei = (self.ae + ai, self.be + bi) else: vpei = (self.ae, self.be) else: vpei = (ai, bi) if self._vpei is not None and vpei is None: self._vpei.free() self._vpei = None else: #TODO: easier if GArray supports like for vector storage shapes = [vpei[0].shape, vpei[1].shape] self._vpei = GArray(shape=shapes, dtype=cfg.dtype) self._vpei.set_vec_h(vpei[0], vpei[1]) self._vpei.sync() @property def external_vector_potential(self): """Sets/gets external vector potential.""" assert (self.ae is None) == (self.be is None) if self.ae is not None: return self.ae, self.be return None @external_vector_potential.setter def external_vector_potential(self, external_vector_potential): if external_vector_potential is not None: Ax, Ay = external_vector_potential assert (Ax is None) == (Ay is None) else: Ax = None if Ax is not None: assert Ax.shape == (cfg.Nxa, cfg.Nya) assert Ay.shape == (cfg.Nxb, cfg.Nyb) self.ae = Ax self.be = Ay else: self.ae, self.be = None, None self._update_gvpei() @property def external_irregular_vector_potential(self): """ Sets/gets external irregular vector potential""" if self._vpei is not None: return self._vpei.get_vec_h() return None def external_irregular_vector_potential_h(self): if self._vpei is not None: return self._vpei.get_d_obj() return np.uintp(0) @property def external_field(self): """ Sets/gets external (non-homogeneous) magnetic field. Setter accepts only a number now. """ # TODO: return curl(A) for non-homogeneous external_field A = self.external_vector_potential if A is not None: Ax, Ay = A # TODO: check expression below return (-np.diff(Ax, axis=1) * cfg.idy + np.diff(Ay, axis=0) * cfg.idx) else: return None @external_field.setter def external_field(self, external_field): if external_field is not None: # NOTE: placeholder, accepts only a number now # TODO: solve equation curl(Aext) = Hext(r) for nonuniform field Hext(r) # Possible set of gauges, A = [g*y*H, (1-g)*x*H, 0] with any g, 0 <= g <= 1 g = 0.5 _, yg = self.mesh.xy_a_grid xg, _ = self.mesh.xy_b_grid Ax = -g * (yg - 0.5 * cfg.Ly) * external_field Ay = (1.0 - g) * (xg - 0.5 * cfg.Lx) * external_field self.external_vector_potential = (Ax, Ay) else: self.external_vector_potential = None @property def order_parameter_Langevin_coefficient(self): return self._psi_langevin_c @order_parameter_Langevin_coefficient.setter def order_parameter_Langevin_coefficient( self, order_parameter_Langevin_coefficient): assert isinstance(order_parameter_Langevin_coefficient, (np.floating, float, np.integer, int)) self._psi_langevin_c = cfg.dtype(order_parameter_Langevin_coefficient) @property def vector_potential_Langevin_coefficient(self): return self._ab_langevin_c @vector_potential_Langevin_coefficient.setter def vector_potential_Langevin_coefficient( self, vector_potential_Langevin_coefficient): assert isinstance(vector_potential_Langevin_coefficient, (np.floating, float, np.integer, int)) self._ab_langevin_c = cfg.dtype(vector_potential_Langevin_coefficient)
class FixedVortices(object): """This class contains methods to fix and release vortices, specify irregular vector potential""" def __init__(self, mesh, vars): # irregular vector potential self._vpi = None self._phase_lock_ns = None self._phase_lock_radius = cfg.phase_lock_radius self.mesh = mesh self.vars = vars if cfg.fixed_vortices_correction is None: cfg.fixed_vortices_correction = 'none' assert cfg.fixed_vortices_correction in ('none', 'cell centers', 'vertices') self.fixed_vortices_correction = cfg.fixed_vortices_correction self.fixed_vortices = cfg.fixed_vortices def __del__(self): pass # |psi| around isolated vortex def __vortex_psi_abs(self, x, y): r2 = x**2 + y**2 # (i) psi(0) = 0 and (ii) |psi(r)| = 1 - 1/(2*r^2) + O(1/r^4) for r to inf return (1.0 - np.exp(-r2)) / (1.0 + np.exp(-r2)) def order_parameter_add_vortices(self, vortices, phase=True, deep=False): """Adds (global) phase winding to order parameter around fixed vortex positions""" vx, vy, vv = self._vortices_format(vortices) xg, yg = self.mesh.xy_grid psi = self.vars._psi.get_h() for k in range(vx.size): if phase: psi *= np.exp(1.0j * vv[k] # add phase around vx[k], vy[k] * np.arctan2(yg - vy[k], xg - vx[k])) if deep: psi *= np.power( # add deep at vx[k], vy[k] self.__vortex_psi_abs(xg - vx[k], yg - vy[k]), np.abs(vv[k])) self.vars._psi.need_htod_sync() self.vars._psi.sync() def fixed_vortices_release(self): """Releases fixed vortices, i.e. creates natural vortices at positions (fixed_vortices_x,fixed_vortices_y) by adding phase to order parameter""" self.vars._psi.sync() psi = self.vars._psi.get_h() psi *= np.exp(-1.0j * self.fixed_vortices_phase) self.vars._psi.need_htod_sync() self.vars._psi.sync() self.fixed_vortices = None self.phase_lock_radius = None def _vortices_format(self, vortices): if vortices is None: vortices = [[], []] assert isinstance(vortices, (list, tuple, dict)) assert len(vortices) in [2, 3] if isinstance(vortices, (list, tuple)): vx, vy = vortices[0], vortices[1] vv = vortices[2] if len(vortices) == 3 else [] elif isinstance(vortices, dict): vx, vy = vortices['x'], vortices['y'] vv = vortices[2] if 'vorticity' in vortices else [] if vx is None: vx = [] if vy is None: vy = [] if vv is None: vv = [] vx, vy, vv = np.array(vx).flatten(), np.array(vy).flatten(), np.array( vv).flatten() vN = max(vx.size, vy.size, vv.size) if vx.size > 0 and vv.size == 0: vv = np.array([1]) assert vx.size in [1, vN] and vy.size in [1, vN] and vv.size in [1, vN] fvx, fvy, fvv = [], [], [] x, y, v = np.nan, np.nan, np.nan for i in range(vN): if i < vx.size: x = vx[i] if i < vy.size: y = vy[i] if i < vv.size: v = vv[i] if np.isnan(x) or np.isnan(y) or np.isnan(v): # no nans continue fvx.append(x) fvy.append(y) fvv.append(v) return (np.array(fvx).astype(cfg.dtype), np.array(fvy).astype(cfg.dtype), np.array(fvv).astype(cfg.dtype)) def _fixed_vortices_correct(self): # correction of vorticity; should be integer self.fixed_vortices_vorticity = np.round(self.fixed_vortices_vorticity) if self.fixed_vortices_correction == 'cell centers': # correction of vortex position; should be in centers of cells as magnetic field B[i,j] self.fixed_vortices_x = cfg.dx * ( np.round(self.fixed_vortices_x / cfg.dx + 0.5) - 0.5) self.fixed_vortices_y = cfg.dy * ( np.round(self.fixed_vortices_y / cfg.dy + 0.5) - 0.5) elif self.fixed_vortices_correction == 'vertices': # correction of vortex position; should be in grid vertices as order parameter psi[i,j] self.fixed_vortices_x = cfg.dx * np.round( self.fixed_vortices_x / cfg.dx) self.fixed_vortices_y = cfg.dy * np.round( self.fixed_vortices_y / cfg.dy) def _ab_phase(self, a, b): a = cfg.dx * np.r_[np.full( (1, cfg.Nya), 0.0, dtype=cfg.dtype), # reuse a and b a].cumsum(axis=0) b = cfg.dy * np.c_[np.full( (cfg.Nxb, 1), 0.0, dtype=cfg.dtype), b].cumsum(axis=1) return np.repeat(b[0:1, :], cfg.Nx, axis=0) + a # assume angle[0,0] = 0 # return b + np.repeat(a[:,0:1], cfg.Ny, axis=1) # equivalent expression @property def irregular_vector_potential(self): if self._vpi is None: return (np.zeros((cfg.Nxa, cfg.Nya), dtype=cfg.dtype), np.zeros((cfg.Nxb, cfg.Nyb), dtype=cfg.dtype)) self._vpi.sync() return self._vpi.get_vec_h() def irregular_vector_potential_h(self): if self._vpi is not None: return self._vpi.get_d_obj() return np.uintp(0) @property def fixed_vortices_phase(self): ai, bi = self.irregular_vector_potential return self._ab_phase(ai, bi) @property def fixed_vortices(self): return (self.fixed_vortices_x.copy(), self.fixed_vortices_y.copy(), self.fixed_vortices_vorticity.copy()) @fixed_vortices.setter def fixed_vortices(self, fixed_vortices): self.fixed_vortices_x, self.fixed_vortices_y, self.fixed_vortices_vorticity = self._vortices_format( fixed_vortices) self._fixed_vortices_correct() if self.fixed_vortices_x.size > 0: if self._vpi is None: shapes = [(cfg.Nxa, cfg.Nya), (cfg.Nxb, cfg.Nyb)] self._vpi = GArray(shape=shapes, dtype=cfg.dtype) ai, bi = self._vpi.get_vec_h() ai.fill(0.0) bi.fill(0.0) xg, yg = self.mesh.xy_grid for k in range(self.fixed_vortices_x.size): vangle = np.arctan2(yg - self.fixed_vortices_y[k], xg - self.fixed_vortices_x[k]) vangle -= vangle[0, 0] ai += self.fixed_vortices_vorticity[k] * cfg.idx * np.diff( vangle, axis=0) bi += self.fixed_vortices_vorticity[k] * cfg.idy * np.diff( vangle, axis=1) self._vpi.need_htod_sync() self._vpi.sync() else: if self._vpi is not None: self._vpi.free() self._vpi = None self.__update_phase_lock_ns() @property def phase_lock_radius(self): return self._phase_lock_radius def __set_phase_lock_radius(self, radius): assert radius is None or (isinstance( radius, (np.floating, float, np.integer, int)) and radius > 0.0) self._phase_lock_radius = radius #this was fixed_vortices.setter; I changed it to phase_lock_radius. @phase_lock_radius.setter def phase_lock_radius(self, radius): self.__set_phase_lock_radius(radius) self.__update_phase_lock_ns() # should update config too? #cfg.phase_lock_radius = self._phase_lock_radius def __update_phase_lock_ns(self): if self._phase_lock_ns is not None: self._phase_lock_ns.free() self._phase_lock_ns = None if self._phase_lock_radius is not None: xg, yg = self.mesh.xy_grid lock_grid = np.full((cfg.Nx, cfg.Ny), False, dtype=np.bool) for k in range(self.fixed_vortices_x.size): lock_grid = np.logical_or( lock_grid, np.square(xg - self.fixed_vortices_x[k]) + np.square(yg - self.fixed_vortices_y[k]) <= np.square( self._phase_lock_radius)) ns = np.where(lock_grid)[0].astype(np.int32) if ns.size > 0: self._phase_lock_ns = GArray(like=ns) def _phase_lock_ns_h(self): if self._phase_lock_ns is not None: return self._phase_lock_ns.get_d_obj() return np.uintp(0)
class Vars(object): """This class contains setters and getters for solution variables order parameter and vector potential""" def __init__(self, Par, mesh): self.par = Par self.mesh = mesh # Persistent storage: Primary variables that are solved for self._psi = None self._vp = None # TODO: this will be renamed to self._a eventually # Temp storage: Stored on cells, nodes, and edges. # Used in observables and other classes and fairly general purpose self._tmp_node_var = None self._tmp_edge_var = None self._tmp_cell_var = None # Temp Storage: Allocated only for reductions self._tmp_psi_real = None self._tmp_A_real = None # copied from variables/parameters.py self.solveA = np.bool(not np.isposinf(cfg.gl_parameter)) if cfg.order_parameter == 'random': self.order_parameter = 1.0 self.randomize_order_parameter(level = cfg.random_level, seed = cfg.random_seed) else: self.order_parameter = cfg.order_parameter # set order parameter manually # vector potential is set up here instead of its setter because # we don't plan on supporting setter for it shapes = [(cfg.Nxa, cfg.Nya), (cfg.Nxb, cfg.Nyb)] self._vp = GArray(shape = shapes, dtype = cfg.dtype) def __del__(self): pass @property def order_parameter(self): self._psi.sync() psi = self._psi.get_h().copy() return psi @order_parameter.setter def order_parameter(self, order_parameter): if isinstance(order_parameter, (np.complexfloating, complex, np.floating, float, np.integer, int)): order_parameter = cfg.dtype_complex(order_parameter) * np.ones((cfg.Nx, cfg.Ny), cfg.dtype_complex) assert order_parameter.shape == (cfg.Nx, cfg.Ny) if self._psi is None: self._psi = GArray(like = order_parameter) else: self._psi.set_h(order_parameter) self.set_order_parameter_to_zero_outside_material() self._psi.sync() def order_parameter_h(self): return self._psi.get_d_obj() def set_order_parameter_to_zero_outside_material(self): if self._psi is None or not self.mesh.have_material_tiling(): return mt_at_nodes = self.mesh._get_material_tiling_at_nodes() psi = self._psi.get_h() psi[~mt_at_nodes] = 0.0 self._psi.need_htod_sync() self._psi.sync() def randomize_order_parameter(self, level=1.0, seed=None): """Randomizes order parameter: absolute value *= 1 - level*rand phase += level*pi*(2.0*rand()-1.0), where rand is uniformly distributed in [0, 1] """ assert 0.0 <= level <= 1.0 self._psi.sync() if seed is not None: np.random.seed(seed) data = (1.0 - level*np.random.rand(cfg.N)) * np.exp(level * 1.0j*np.pi*(2.0*np.random.rand(cfg.N) - 1.0)) self._psi.set_h(data) self._psi.sync() @property def vector_potential(self): if self._vp is None: return (np.zeros((cfg.Nxa, cfg.Nya), dtype=cfg.dtype), np.zeros((cfg.Nxb, cfg.Nyb), dtype=cfg.dtype)) self._vp.sync() return self._vp.get_vec_h() @vector_potential.setter def vector_potential(self, vector_potential): a, b = vector_potential self._vp.set_vec_h(a, b) self._vp.sync() def vector_potential_h(self): if self._vp is not None: return self._vp.get_d_obj() return np.uintp(0) #-------------------------- # temporary arrays #-------------------------- def _tmp_node_var_h(self): if self._tmp_node_var is None: self._tmp_node_var = GArray(like = self._psi) return self._tmp_node_var.get_d_obj() def _tmp_edge_var_h(self): if self._tmp_edge_var is None: shapes = [(cfg.Nxa, cfg.Nya), (cfg.Nxb, cfg.Nyb)] self._tmp_edge_var = GArray(shape = shapes, dtype = cfg.dtype) return self._tmp_edge_var.get_d_obj() def _tmp_cell_var_h(self): if self._tmp_cell_var is None: self._tmp_cell_var = GArray(shape = (cfg.Nxc, cfg.Nyc), dtype = cfg.dtype) return self._tmp_cell_var.get_d_obj() def _tmp_psi_real_h(self): if self._tmp_psi_real is not None: return self._tmp_psi_real.get_d_obj() return np.uintp(0) def _tmp_A_real_h(self): if self._tmp_A_real is not None: return self._tmp_A_real.get_d_obj() return np.uintp(0) def _alloc_free_temporary_gpu_storage(self, action): assert action in ['alloc', 'free'] if action == 'alloc': if self._tmp_psi_real is None: self._tmp_psi_real = GArray(self.par.grid_size, on = GArray.on_device, dtype = cfg.dtype) if self._tmp_A_real is None and self.solveA: self._tmp_A_real = GArray(self.par.grid_size_A, on = GArray.on_device, dtype = cfg.dtype) else: if self._tmp_psi_real is not None: self._tmp_psi_real.free() self._tmp_psi_real = None if self._tmp_A_real is not None: self._tmp_A_real.free() self._tmp_A_real = None