def show_L1_eigfuns(G, n=np.inf, **kwargs): ''' Show eigenfunctions of 0-Laplacian ''' obs = gds.edge_gds(G) # L = np.array(nx.laplacian_matrix(G).todense()) L = -obs.laplacian(np.eye(obs.ndim)) # vals, vecs = sp.sparse.linalg.eigs(-L, k=n, which='SM') vals, vecs = np.linalg.eigh(L) vals, vecs = np.real(vals), np.real(vecs) vals = np.round(vals, 6) # pdb.set_trace() sys = dict() sys['Surface'] = gds.face_gds(G) sys['Surface'].set_evolution(nil=True) canvas = dict() canvas['Surface'] = [[[sys['Surface']]]] for i, (ev, vec) in enumerate(sorted(zip(vals, vecs.T), key=lambda x: np.abs(x[0]))): obs = gds.edge_gds(G) obs.set_evolution(nil=True) obs.set_initial(y0=lambda x: vec[obs.X[x]]) if ev in canvas: sys[f'eigval_{ev} eigfun_{len(canvas[ev])}'] = obs canvas[ev].append([[obs]]) else: sys[f'eigval_{ev} eigfun_{0}'] = obs canvas[ev] = [[[obs]]] if i == n: break # canvas = sorted(list(canvas.values()), key=len) canvas = list(canvas.values()) sys = gds.couple(sys) gds.render(sys, canvas=canvas, n_spring_iters=1200, title=f'L1-eigenfunctions', **kwargs)
def test_curl(): G1 = nx.Graph() G1.add_nodes_from([1, 2, 3, 4]) G1.add_edges_from([(1, 2), (2, 3), (3, 4), (4, 1)]) v1 = gds.edge_gds(G1) v1.set_evolution(nil=True) v1.set_initial(y0=lambda e: 1 if e == (1, 2) else 0) G2 = nx.Graph() G2.add_nodes_from([1, 2, 3, 4, 5, 6]) G2.add_edges_from([(1, 2), (2, 3), (3, 4), (4, 1), (1, 5), (5, 6), (6, 2)]) v2 = gds.edge_gds(G2) v2.set_evolution(nil=True) v2.set_initial(y0=lambda e: 1 if e == (1, 2) else 0) sys = gds.couple({ 'velocity1': v1, 'curl1': v1.project(gds.GraphDomain.faces, lambda v: v.curl()), 'curl*curl1': v1.project(gds.GraphDomain.edges, lambda v: v.curl_face.T @ v.curl_face @ v.y), 'velocity2': v2, 'curl2': v2.project(gds.GraphDomain.faces, lambda v: v.curl()), 'curl*curl2': v2.project(gds.GraphDomain.edges, lambda v: v.curl_face.T @ v.curl_face @ v.y) }) gds.render(sys, edge_max=0.5, canvas=gds.grid_canvas(sys.observables.values(), 3), dynamic_ranges=True)
def initial_flow(G: nx.Graph, KE: float = 1., scale_distribution: Callable = None): ''' Construct divergence-free initial conditions with energies at specified length-scales. scale_distribution: probability measure on [0, 1] (default uniform) ''' assert KE >= 0, 'Specify nonnegative kinetic energy' if scale_distribution is None: scale_distribution = lambda x: 1. N = len(G.edges()) P = gds.edge_gds( G).leray_projector # TODO: assumes determinism of construction freqs, spec_fun = edge_power_spectrum(G) dist = np.array( list( map(scale_distribution, (freqs - freqs.min()) / (freqs.max() - freqs.min())))) def f(x): return np.linalg.norm(spec_fun(P @ x) - dist) x0 = np.random.uniform(size=N) sol = minimize(f, x0) u = P @ sol.x u *= np.sqrt(KE / np.dot(u, u)) return u
def edge_fourier_transform(G, method='hodge_faces'): ''' Diagonalization of 1-form laplacian with varying 2-form definitions ''' if method == 'hodge_faces': # 2-forms defined on planar faces (default) pass elif method == 'hodge_cycles': # 2-forms defined on cycle basis G.faces = [tuple(f) for f in nx.cycle_basis(G)] elif method == 'dual': raise NotImplementedError() else: raise ValueError(f'Method {method} undefined') v = gds.edge_gds( G) # TODO: assumes determinism of edge index assignment -- fix! L1 = -v.laplacian(np.eye(v.ndim)) eigvals, eigvecs = np.linalg.eigh( L1 ) # Important to use eigh() rather than eig() -- otherwise non-unitary eigenvectors eigvecs = np.asarray(eigvecs) # Unitary check assert (np.round(eigvecs @ eigvecs.T, 6) == np.eye(v.ndim)).all(), 'VV^T != I' assert (np.round(eigvecs.T @ eigvecs, 6) == np.eye(v.ndim)).all(), 'V^TV != I' return eigvals, eigvecs
def square_edge_diffusion(): m, n = 10, 10 G, (l, r, t, b) = gds.square_lattice(n, m, with_boundaries=True) faces, outer_face = gds.embedded_faces(G) for j in range(m): G.add_edge((n - 1, j), (0, j)) aux_faces = [((n - 1, j), (0, j), (0, j + 1), (n - 1, j + 1)) for j in range(m - 1)] G.faces = faces + aux_faces # Hacky G.rendered_faces = np.array(range(len(faces)), dtype=np.intp) # Hacky v = gds.edge_gds(G) v.set_evolution(dydt=lambda t, y: v.laplacian(y)) velocity = 1.0 def boundary(e): if e in b.edges: return velocity elif e == ((0, 0), (m - 1, 0)): return -velocity elif e in t.edges or e == ((0, n - 1), (m - 1, n - 1)): return 0.0 return None v.set_constraints(dirichlet=boundary) return v
def edge_diffusion(m, n, constr, periodic=False): G, (l, r, t, b) = constr(m, n, with_boundaries=True) if periodic: for x in l.nodes: for y in r.nodes: if x[1] == y[1]: G.add_edge(x, y) v = gds.edge_gds(G) v.set_evolution(dydt=lambda t, y: v.laplacian(y)) velocity = -1.0 def boundary(e): if e[0] in l.nodes and e[1] in r.nodes and e[0][1] == 0: return -velocity if e in b.edges: if e[1][1] > e[0][1] and e[0][0] % 2 == 0: # Hack to fix hexagonal bcs return -velocity return velocity elif e in t.edges: return 0.0 return None v.set_constraints(dirichlet=boundary) return v
def euler_cycles(G: nx.Graph, density=1.0, integrator=Integrators.lsoda): velocity = gds.edge_gds(G) cycles = gds.cycle_basis(G) print(cycles) ops = dict() edge_set = set(G.edges()) for i, cycle in enumerate(cycles): print(cycle) n = len(cycle) D = np.zeros((n, n)) P = np.zeros((n, velocity.ndim)) # edge_pairs = zip(zip(chain([cycle[-2], cycle[-1]], cycle[:-2]), chain([cycle[-1]], cycle[:-1])), zip(chain([cycle[-1]], cycle[:-1]), cycle)) edges = zip(chain([cycle[-1]], cycle[:-1]), cycle) for idx, e_i in enumerate(edges): D[idx, idx] = -1 D[idx, (idx + 1) % n] = 1 i = velocity.X[e_i] P[idx, i] = 1 if e_i in edge_set else -1 # Re-orient Dm = relu(-D) Dp = relu(D) F = Dm.T @ Dp - Dp.T @ Dm ops[i] = {'c': cycle, 'D': D, 'P': P, 'F': F} def dvdt(t, v): ret = 0 for i in ops: F, P = ops[i]['F'], ops[i]['P'] pv = P @ v ret -= P.T @ (np.multiply(F.T, pv).T + np.multiply(F, pv)) @ pv return velocity.leray_project(ret) # dvdt(0, velocity.y) velocity.set_evolution(dydt=dvdt, integrator=integrator) return velocity
def navier_stokes(G: nx.Graph, viscosity=1e-3, density=1.0, v_free=[], body_force=None, advect=None, integrator=Integrators.lsoda, **kwargs) -> (gds.node_gds, gds.edge_gds): if body_force is None: body_force = lambda t, y: 0 if advect is None: advect = lambda v: v.advect() pressure = gds.node_gds(G, **kwargs) pressure.set_evolution(lhs=lambda t, p: pressure.laplacian(p) / density, refresh_cvx=False) v_free = np.array([pressure.X[x] for x in set(v_free)], dtype=np.intp) velocity = gds.edge_gds(G, v_free=v_free, **kwargs) velocity.set_evolution( dydt=lambda t, u: velocity. leray_project(-advect(velocity) + body_force(t, u) + (0 if viscosity == 0 else velocity.laplacian() * viscosity / density)) - pressure.grad() / density, max_step=1e-3, integrator=integrator, ) return velocity, pressure
def vector_advection_test(flows=[1,1,1,1,1], **kwargs): G = nx.Graph() G.add_nodes_from([0, 1, 2, 3, 4, 5]) G.add_edges_from([(2, 0), (3, 0), (0, 1), (1, 5), (1, 4)]) flow = gds.edge_gds(G) def field(e): ret = 1 if e == (0, 2): ret = -1 if e == (0, 3): ret = -1 return flows[flow.edges[e]] * ret flow.set_evolution(dydt=lambda t, y: np.zeros_like(y)) flow.set_initial(y0=field) obs = gds.edge_gds(G) obs.set_evolution(dydt=lambda t, y: -obs.advect(**kwargs)) obs.set_initial(y0=field) return flow, obs
def vector_advection_circle_2(): n = 10 G = nx.Graph() G.add_nodes_from(list(range(n))) G.add_edges_from(list(zip(range(n), [n-1] + list(range(n-1))))) flow = gds.edge_gds(G) obs = gds.edge_gds(G) flow.set_evolution(dydt=lambda t, y: np.zeros_like(y)) obs.set_evolution(dydt=lambda t, y: -obs.advect(flow, vectorized=False)) def init_obs(e): if e == (2,3): return 1.5 elif e == (0,n-1): return -1.0 return 1.0 obs.set_initial(y0=init_obs) def init_flow(e): if e == (0,n-1): return -1.0 return 1.0 flow.set_initial(y0=init_flow) # flow.set_constraints(dirichlet=dict_fun({(2,3): 1.0})) return flow, obs
def advection(G, v_field, kind=None): flow_diff = np.zeros(len(G.edges())) flow = gds.edge_gds(G) flow.set_evolution(dydt=lambda t, y: flow_diff) flow.set_initial(y0 = v_field) conc = gds.node_gds(G) if kind == None: conc.set_evolution(dydt=lambda t, y: -conc.advect(flow)) elif kind == 'lie': conc.set_evolution(dydt=lambda t, y: -conc.lie_advect(flow)) else: raise Exception('unrecognized kind') return conc, flow
def self_advection_2(): G = nx.Graph() G.add_nodes_from(list(range(1,9))) v_field = { (1,2): 2, (2,3): 1, (3,4): 2, (1,4): -3, (5,6): 2, (6,7): 3, (7,8): 2, (5,8): -1, (1,5): 1, (2,6): 1, (3,7): -1, (4,8): -1, } G.add_edges_from(v_field.keys()) u = gds.edge_gds(G) u.set_evolution(dydt=lambda t, y: -u.advect()) u.set_initial(y0=lambda e: v_field[e]) return u
def ns_cycle_test(): n = 30 G = gds.directed_cycle_graph(n) velocity = gds.edge_gds(G) mu = 0 # viscosity print(velocity.X.keys()) D = np.zeros((velocity.ndim, velocity.ndim)) L = -D.T @ D IV = np.zeros((velocity.ndim, velocity.ndim)) edge_pairs = zip( zip(chain([n - 2, n - 1], range(n - 2)), chain([n - 1], range(n - 1))), zip(chain([n - 1], range(n - 1)), range(n))) for idx, (e_i, e_j) in enumerate(edge_pairs): print(e_i, e_j) i, j = velocity.X[e_i], velocity.X[e_j] D[i, i] = -1 D[i, j] = 1 IV[j, idx] = 1 # print(D) # D = -velocity.incidence # Either incidence or dual derivative seems to work Dm = relu(-D) Dp = relu(D) F = Dm.T @ Dp - Dp.T @ Dm # pdb.set_trace() def dvdt(t, v): A = np.multiply(F.T, v).T + np.multiply(F, v) return -A @ v + mu * L @ v velocity.set_evolution(dydt=dvdt) bump = 2 * stats.norm().pdf(np.linspace(-4, 4, n)) # v0 = -IV @ bump # v0 = IV @ rotate(bump, 10) v0 = IV @ (bump - rotate(bump, n // 2)) velocity.set_initial(y0=lambda e: v0[velocity.X[e]]) sys = gds.couple({ 'velocity': velocity, # 'gradient': velocity.project(gds.GraphDomain.edges, lambda v: D @ v.y), # 'laplacian': velocity.project(gds.GraphDomain.edges, lambda v: -D.T @ D @ v.y), # 'dual': velocity.project(gds.GraphDomain.nodes, lambda v: v.y), # 'L1': velocity.project(PointObservable, lambda v: np.abs(v.y).sum()), # 'L2': velocity.project(PointObservable, lambda v: np.linalg.norm(v.y)), # 'min': velocity.project(PointObservable, lambda v: v.y.min()), }) gds.render(sys, canvas=gds.grid_canvas(sys.observables.values(), 3), edge_max=3, dynamic_ranges=False)
def self_advection_test_2(flows=[1,1,1,1,1], interactions=[1,0,1,0]): G = nx.Graph() G.add_nodes_from([0, 1, 2, 3, 4, 5]) G.add_edges_from([(2, 0), (3, 0), (0, 1), (1, 5), (1, 4)]) flow = gds.edge_gds(G) def field(e): ret = 1 if e == (0, 2): ret = -1 if e == (0, 3): ret = -1 return flows[flow.edges[e]] * ret flow.set_evolution(dydt=lambda t, y: -flow.advect2(vectorized=False, interactions=interactions)) flow.set_initial(y0=field) return flow
def solve_beltrami(G: nx.Graph): flow = gds.edge_gds(G) def f(x): return np.linalg.norm( flow.leray_project(flow.advect(flow.leray_project(x)))) x0 = np.random.uniform(low=-10, high=10, size=flow.ndim) sol = basinhopping(f, x0) if sol.fun >= 1e-6: print('Unsuccessful solve') pdb.set_trace() u = flow.leray_project(sol.x) flow.set_evolution(nil=True) flow.set_initial(y0=lambda x: u[flow.X[x]]) return flow
def self_advection_1(): G = nx.Graph() G.add_nodes_from(list(range(1,7))) G.add_edges_from([ (1,2),(2,3),(3,4),(4,1), (4,5),(5,6),(6,3), ]) negated = set([(1,4),(3,6),]) def v_field(e): ret = 1.0 if e in negated: ret *= -1 if e == (3,4): ret *= 2 return ret u = gds.edge_gds(G) u.set_evolution(dydt=lambda t, y: -u.advect()) u.set_initial(y0=v_field) return u
def show_leray(G, v: Callable = None, **kwargs): ''' Show Leray decomposition of a vector field. ''' if v is None: v = lambda x: np.random.uniform(1, 2) orig = gds.edge_gds(G) orig.set_evolution(nil=True) orig.set_initial(y0=v) div_free = orig.project(GraphDomain.edges, lambda u: u.leray_project()) curl_free = orig.project(GraphDomain.edges, lambda u: u.y - u.leray_project()) sys = gds.couple({ 'original': orig, 'original (div)': orig.project(GraphDomain.nodes, lambda u: u.div()), 'original (curl)': orig.project(GraphDomain.faces, lambda u: u.curl()), 'div-free': div_free, 'div-free (div)': div_free.project( GraphDomain.nodes, lambda u: orig.div(u.y)), # TODO: chaining projections? 'div-free (curl)': div_free.project(GraphDomain.faces, lambda u: orig.curl(u.y)), 'curl-free': curl_free, 'curl-free (div)': curl_free.project(GraphDomain.nodes, lambda u: orig.div(u.y)), 'curl-free (curl)': curl_free.project(GraphDomain.faces, lambda u: orig.curl(u.y)), }) gds.render(sys, n_spring_iters=1000, canvas=gds.grid_canvas(sys.observables.values(), 3), title='Leray decomposition', min_rng_size=1e-3, **kwargs)
def self_advection_circle(): n = 10 G = nx.Graph() G.add_nodes_from(list(range(n))) G.add_edges_from(list(zip(range(n), [n-1] + list(range(n-1))))) flow = gds.edge_gds(G) flow.set_evolution(dydt=lambda t, y: -flow.advect()) # flow.set_initial(y0=dict_fun({(2,3): 1.0, (3,4): 1.0}, def_val=0.)) def init_flow(e): if e == (2,3): return 1.5 elif e == (0,n-1): return -1.0 return 1.0 flow.set_initial(y0=init_flow) flow.advect() sys = gds.couple({ 'flow': flow, 'advective': flow.project(gds.GraphDomain.edges, lambda y: -y.advect()), }) gds.render(sys, edge_max=0.5)
def navier_stokes(G: nx.Graph, viscosity=1e-3, density=1.0, v_free=[], e_free=[], e_normal=[], advect=None, integrator=Integrators.lsoda, **kwargs) -> (gds.node_gds, gds.edge_gds): if advect is None: advect = lambda v: v.advect() pressure = gds.node_gds(G, **kwargs) velocity = gds.edge_gds(G, **kwargs) v_free = np.array( [pressure.X[x] for x in set(v_free)], dtype=np.intp) # Inlet/outlet nodes (typically, pressure boundaries) e_free = np.array([velocity.X[x] for x in set(e_free)], dtype=np.intp) # Free-slip surface e_normal = np.array([velocity.X[x] for x in set(e_normal)], dtype=np.intp) # Inlets/outlet edges normal to surface min_step = 1e-3 def pressure_f(t, y): dt = max(min_step, velocity.dt) lhs = velocity.div(velocity.y / dt - advect(velocity) + velocity.laplacian(free=e_free, normal=e_normal) * viscosity / density) lhs[v_free] = 0. lhs -= pressure.laplacian(y) / density return lhs def velocity_f(t, y): return -advect( velocity) - pressure.grad() / density + velocity.laplacian( free=e_free, normal=e_normal) * viscosity / density pressure.set_evolution(lhs=pressure_f) velocity.set_evolution(dydt=velocity_f, integrator=integrator) return velocity, pressure
def plot_beltrami(): if not os.path.exists(save_path): raise Exception('no data') sys = dict() fig_N, fig_M = 0, 0 with open(save_path, 'rb') as f: data = cloudpickle.load(f) fig_N, fig_M = len(data), len(data[next(iter(data))]) for N, subdata in sorted(data.items(), key=lambda x: x[0]): for i in subdata: print((N, i)) u = subdata[i] G = gds.triangular_lattice(m=1, n=N) flow = gds.edge_gds(G) flow.set_evolution(nil=True) flow.set_initial(y0=lambda x: u[flow.X[x]]) sys[f'{N}_{i}'] = flow sys = gds.couple(sys) canvas = gds.grid_canvas(sys.observables.values(), fig_M) gds.render(sys, canvas=canvas, edge_max=0.6, dynamic_ranges=True)
def hex_edge_diffusion(): m, n = 10, 20 G, (l, r, t, b) = gds.hexagonal_lattice(m, n, with_boundaries=True) faces, outer_face = gds.embedded_faces(G) contractions = {} for j in range(1, 2 * m + 1): G = nx.algorithms.minors.contracted_nodes(G, (0, j), (n, j)) contractions[(n, j)] = (0, j) nx.set_node_attributes(G, None, 'contraction') rendered_faces = set() for i, face in enumerate(faces): face = list(face) modified = False for j, node in enumerate(face): if node in contractions: n_l = contractions[node] # identified face[j] = n_l faces[i] = tuple(face) modified = True if not modified: rendered_faces.add(i) G.faces = faces G.rendered_faces = np.array(sorted(list(rendered_faces)), dtype=np.intp) # Hacky # pdb.set_trace() v = gds.edge_gds(G) v.set_evolution(dydt=lambda t, y: v.laplacian(y)) velocity = 1.0 def boundary(e): if (e[0][1] == e[1][1] == 0) and (e[0][0] == e[1][0] - 1): return velocity elif e in t.edges or e == ((0, 2 * m), (n, 2 * m + 1)): return 0.0 return None v.set_constraints(dirichlet=boundary) return v
def foreach(G, data, N, KE): v = gds.edge_gds(G) P = v.leray_projector data = torch.from_numpy(data).float() spectra = 0 du_t = ortho_group.rvs( data.shape[1]) # Initial orthonormal perturbation matrix for i in range(window): u_t = data[transient + i] J_t = P @ v.advect_jac(u_t).numpy() du_t += dt * J_t @ du_t if i % interval == 0: Q, R = np.linalg.qr(du_t, mode='complete') spectra += np.log(np.abs(np.diag(R))) du_t = Q spectra /= (window / interval) spectra = spectra[spectra >= floor] if not (N in plot_data): plot_data[N] = {'x': [], 'y': [], 'e_x': [], 'e_y': []} plot_data[N]['x'].extend([KE] * spectra.size) plot_data[N]['y'].extend(spectra.tolist()) plot_data[N]['e_x'].append(KE) plot_data[N]['e_y'].append(spectra.max())
def tri_edge_diffusion(): m, n = 10, 20 G, (l, r, t, b) = gds.triangular_lattice(m, n, with_boundaries=True) faces, outer_face = gds.embedded_faces(G) for j in range(m + 1): G = nx.algorithms.minors.contracted_nodes(G, (0, j), ((n + 1) // 2, j)) rendered_faces = set() r_nodes = set(r.nodes()) for i, face in enumerate(faces): face = list(face) modified = False for j, node in enumerate(face): if node in r_nodes: n_l = (0, node[1]) # identified face[j] = n_l faces[i] = tuple(face) modified = True if not modified: rendered_faces.add(i) G.faces = faces G.rendered_faces = np.array(sorted(list(rendered_faces)), dtype=np.intp) # Hacky v = gds.edge_gds(G) v.set_evolution(dydt=lambda t, y: v.laplacian(y)) velocity = 1.0 def boundary(e): if e in b.edges: return velocity elif e == ((0, 0), (n // 2 - 1, 0)): return -velocity elif e in t.edges or e == ((0, m), (n // 2 - 1, m)): return 0.0 return None v.set_constraints(dirichlet=boundary) return v
def foreach(G, data, ax): v = gds.edge_gds(G) values = [] for row in data: values.append(np.dot(row, v.leray_project(-v.advect(row)))) ax.plot(values)