OSCR

Dorsoventral gradient of theta sweeps in the medial entorhinal cortex.

Code ↔ Paper

9 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 9 matches
  1. [1] § MATERIALS AND METHODS › Computational modeling ↔ ComputationalModellingFig4/network_models.py, lines 248–268 · score 0.63 · offset length, phase offset, firing rate, conjunctive, GC, connectivity
  2. [2] § MATERIALS AND METHODS › Computational modeling ↔ ComputationalModellingFig4/Fig4_thetasweeps.ipynb, lines 510–623 · score 0.61 · phase offset, theta modulation, internal direction, theta phases, sensory, GC
  3. [3] § RESULTS › Efficient learning of hippocampal spatial maps via theta sweeps with varied angles ↔ ComputationalModellingFig4/network_models.py, lines 21–139 · score 0.60 · synaptic connectivity, bump center, Place cells, square, network, distance
  4. [4] § MATERIALS AND METHODS › Analyzing directional sweeps and locational sweeps across theta phase bins ↔ EmpiricalDataAnalysis/SI3_SweepAngle_across_phase_bins_forward_and_backward.ipynb, lines 601–722 · score 0.60 · phase bins, left sweeps, right sweep, theta phase, backward, locational
  5. [5] § MATERIALS AND METHODS › Statistical analysis ↔ EmpiricalDataAnalysis/Fig1_Bar_GridsweepAngle.ipynb, lines 14–60 · score 0.58 · likelihood ratio, full model, MixedLM, LMMs, fitted, sweep angle
  6. [6] § MATERIALS AND METHODS › Statistical analysis ↔ EmpiricalDataAnalysis/Fig3_Bar_IDmvl.ipynb, lines 8–51 · score 0.57 · likelihood ratio, full model, MixedLM, LMMs, fitted, animals
  7. [7] § MATERIALS AND METHODS › Decoding directional theta sweeps in tmDCs ↔ ComputationalModellingFig4/Fig4_tmDC_skipping.ipynb, lines 337–390 · score 0.54 · head direction bin, tuning curves, smoothed, circular, Gaussian, cell
  8. [8] § MATERIALS AND METHODS › Computational modeling ↔ ComputationalModellingFig4/Fig4_thetasweeps.ipynb, lines 168–237 · score 0.51 · angular speed, Theta modulation, oscillation, linearly, Computational
  9. [9] § MATERIALS AND METHODS › Computational modeling ↔ ComputationalModellingFig4/Fig4_tmDC_skipping.ipynb, lines 168–237 · score 0.51 · angular speed, Theta modulation, oscillation, linearly, Computational

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 504 lines · 20 KB · no license · 2 matches

  1. from dataclasses import dataclass
  2. import brainpy as bp
  3. import brainpy.math as bm
  4. import jax
  5. import numpy as np
  6. import networkx as nx
  7. @dataclass
  8. class PCParams:
  9. tau: float = 10.0
  10. tau_v: float = 100.0
  11. noise_strength: float = 0.0
  12. k: float = 0.2
  13. adaptation_strength: float = 15.0
  14. a: float = 0.2
  15. A: float = 5.0
  16. J0: float = 1.0
  17. g: float = 1.0
  18. conn_noise: float = 0.0
  19. class PCNet(bp.DynamicalSystem):
  20. """
  21. Graph-based continuous-attractor place cell network (linearized track version).
  22. Each node in the graph corresponds to a place cell.
  23. Synaptic connectivity is defined as a Gaussian function of the
  24. geodesic distance between nodes.
  25. If it is a linear track, the geodesic distance is the same as the Euclidean distance.
  26. If it is a T maze track or other more complex track, the geodesic distance is the shortest path distance along the track.
  27. This distance measurement is important for the network behaviours
  28. Parameters
  29. ----------
  30. Graph : networkx.Graph
  31. Graph where nodes represent place cells and edge weights represent distances.
  32. Must include 'dx' in Graph.graph.
  33. params : PCParams
  34. Parameter object containing model constants such as a, J0, tau, etc.
  35. Attributes
  36. ----------
  37. cell_num : int
  38. Number of place cells (nodes).
  39. D : brainpy.math array
  40. Matrix of geodesic distances between nodes.
  41. conn_mat : brainpy.math array
  42. Synaptic weight matrix.
  43. r, u, v : bm.Variable
  44. Neural rate, internal state, and adaptation state.
  45. center : bm.Variable
  46. Estimated bump centre position.
  47. """
  48. def __init__(self, Graph, params: PCParams = PCParams()):
  49. super().__init__()
  50. self.Graph = Graph
  51. self.params = params
  52. # number of cells = number of nodes in graph
  53. self.cell_num = len(Graph.nodes)
  54. self.node_list = list(Graph.nodes)
  55. dx = self.Graph.graph.get("dx", None)
  56. if dx is None:
  57. raise ValueError("Graph.graph['dx'] must be defined (spatial step size).")
  58. self.x = bm.asarray(np.arange(self.cell_num) * dx)
  59. # --- derived parameters ---
  60. self.m = params.adaptation_strength * params.tau / params.tau_v
  61. # --- compute geodesic distance matrix (cell_num×cell_num)
  62. geodist = dict(nx.all_pairs_dijkstra_path_length(Graph, weight='weight'))
  63. D = np.zeros((self.cell_num, self.cell_num))
  64. for i, ni in enumerate(self.node_list):
  65. for j, nj in enumerate(self.node_list):
  66. D[i, j] = geodist[ni][nj]
  67. self.D = bm.asarray(D)
  68. # --- build connectivity based on geodesic distance ---
  69. base_connection = self.make_connection(self.D)
  70. noise_connection = np.random.normal(0, params.conn_noise, size=(self.cell_num, self.cell_num))
  71. self.conn_mat = base_connection + noise_connection
  72. # --- state variables ---
  73. self.r = bm.Variable(bm.zeros(self.cell_num))
  74. self.u = bm.Variable(bm.zeros(self.cell_num))
  75. self.v = bm.Variable(bm.zeros(self.cell_num))
  76. self.center = bm.Variable(bm.zeros(1))
  77. # --- integrator ---
  78. self.integral = bp.odeint(method="exp_euler", f=self.derivative)
  79. @property
  80. def derivative(self):
  81. du = lambda u, t, input: (-u + input - self.v) / self.params.tau
  82. dv = lambda v, t: (-v + self.m * self.u) / self.params.tau_v
  83. return bp.JointEq([du, dv])
  84. # ===== connectivity based on geodesic distances =====
  85. def make_connection(self, D):
  86. """
  87. Gaussian weight kernel based on graph geodesic distances.
  88. """
  89. a = self.params.a
  90. J0 = self.params.J0
  91. W = J0 * bm.exp(-0.5 * (D / a) ** 2)
  92. # normalise or scale if desired
  93. W = W / (bm.sqrt(2 * bm.pi) * a)
  94. return W
  95. def get_bump_center(self, r, x):
  96. denom = bm.sum(r) + 1e-12
  97. center = bm.sum(r * x) / denom
  98. return center.reshape(-1,)
  99. # ===== external input based on animal position =====
  100. def input_bump(self, animal_pos_node_index):
  101. """
  102. Generate Gaussian bump centred on the node corresponding to the animal's current position.
  103. node_index: integer index into self.nodes
  104. """
  105. d = self.D[animal_pos_node_index]
  106. return self.params.A * bm.exp(-0.5 * (d / self.params.a) ** 2)
  107. # ===== update loop =====
  108. def update(self, animal_pos_node_index, ThetaInput):
  109. self.center.value = self.get_bump_center(r=self.r, x=self.x)
  110. Iext = ThetaInput * self.input_bump(animal_pos_node_index)
  111. Irec = bm.matmul(self.conn_mat, self.r)
  112. noise = bm.random.randn(self.cell_num) * self.params.noise_strength
  113. input_total = Iext + Irec + noise
  114. # integrate for current step
  115. u, v = self.integral(self.u, self.v, bp.share.load("t"), input_total)
  116. self.u.value = bm.where(u > 0, u, 0)
  117. self.v.value = v
  118. u_sq = bm.square(self.u)
  119. self.r.value = self.params.g * u_sq / (1.0 + self.params.k * bm.sum(u_sq))
  120. @dataclass
  121. class DCParams:
  122. tau: float = 10.0
  123. tau_v: float = 100.0
  124. noise_strength: float = 0.0
  125. k: float = 0.2
  126. adaptation_strength: float = 15.0
  127. a: float = 0.7
  128. A: float = 3.0
  129. J0: float = 1.0
  130. g: float = 1.0
  131. z_min: float = -bm.pi
  132. z_max: float = bm.pi
  133. conn_noise: float = 0.0
  134. class DCNet(bp.DynamicalSystem):
  135. """
  136. 1D continuous-attractor direction cell network
  137. """
  138. def __init__(self, cell_num: int, params: DCParams = DCParams()):
  139. super().__init__()
  140. self.cell_num = cell_num
  141. self.params = params
  142. # --- derived parameters ---
  143. self.m = params.adaptation_strength * params.tau / params.tau_v
  144. # --- feature space ---
  145. self.z_min = params.z_min
  146. self.z_max = params.z_max
  147. self.z_range = self.z_max - self.z_min
  148. x1 = bm.linspace(self.z_min, self.z_max, self.cell_num + 1)
  149. self.x = x1[:-1]
  150. # --- connectivity ---
  151. base_connection = self.make_connection()
  152. noise_connection = np.random.normal(0, params.conn_noise, size=(self.cell_num, self.cell_num))
  153. self.conn_mat = base_connection + noise_connection
  154. # --- state variables ---
  155. self.r = bm.Variable(bm.zeros(self.cell_num))
  156. self.u = bm.Variable(bm.zeros(self.cell_num))
  157. self.v = bm.Variable(bm.zeros(self.cell_num))
  158. self.center = bm.Variable(bm.zeros(1))
  159. # --- integrator ---
  160. self.integral = bp.odeint(method="exp_euler", f=self.derivative)
  161. @property
  162. def derivative(self):
  163. du = lambda u, t, input: (-u + input - self.v) / self.params.tau
  164. dv = lambda v, t: (-v + self.m * self.u) / self.params.tau_v
  165. return bp.JointEq([du, dv])
  166. # ===== utilities =====
  167. def handle_periodic_condition(self, A):
  168. B = bm.where(A > bm.pi, A - 2 * bm.pi, A)
  169. B = bm.where(B < -bm.pi, B + 2 * bm.pi, B)
  170. return B
  171. def calculate_dist(self, d):
  172. d = self.handle_periodic_condition(d)
  173. d = bm.where(d > 0.5 * self.z_range, d - self.z_range, d)
  174. return d
  175. # ===== connectivity =====
  176. def make_connection(self):
  177. @jax.vmap
  178. def get_J(xbins):
  179. d = self.calculate_dist(xbins - self.x)
  180. Jxx = (
  181. self.params.J0
  182. * bm.exp(-0.5 * bm.square(d / self.params.a))
  183. / (bm.sqrt(2 * bm.pi) * self.params.a)
  184. )
  185. return Jxx
  186. return get_J(self.x)
  187. # ===== dynamics =====
  188. def get_bump_center(self, r, x):
  189. exppos = bm.exp(1j * x)
  190. center = bm.angle(bm.sum(exppos * r))
  191. return center.reshape(-1,)
  192. def input_bump(self, head_direction):
  193. return self.params.A * bm.exp(
  194. -0.5 * bm.square(self.calculate_dist(self.x - head_direction) / self.params.a)
  195. )
  196. # ===== update loop =====
  197. def update(self, head_direction, ThetaInput):
  198. self.center.value = self.get_bump_center(r=self.r, x=self.x)
  199. Iext = ThetaInput * self.input_bump(head_direction)
  200. Irec = bm.matmul(self.conn_mat, self.r)
  201. noise = bm.random.randn(self.cell_num) * self.params.noise_strength
  202. input_total = Iext + Irec + noise
  203. # integrate for current step
  204. u, v = self.integral(self.u, self.v, bp.share.load("t"), input_total)
  205. self.u.value = bm.where(u > 0, u, 0)
  206. self.v.value = v
  207. u_sq = bm.square(self.u)
  208. self.r.value = self.params.g * u_sq / (1.0 + self.params.k * bm.sum(u_sq))
  209. @dataclass
  210. class GCParams:
  211. # dynamics
  212. tau: float = 10.0
  213. tau_v: float = 100.0
  214. noise_strength: float = 0.0 #activity noise
  215. conn_noise: float = 0.0 #connectivity noise
  216. k: float = 1.0
  217. adaptation_strength: float = 15.0 # (mbar)
  218. # connectivity / input
  219. a: float = 0.8
  220. A: float = 3.0
  221. J0: float = 5.0
  222. g: float = 1000.0 #scale the firing rate to make it reasonable, no biological meaning
  223. #controlling grid spacing, larger means smaller spacing
  224. mapping_ratio: float = 1
  225. #cntrolling offset length from conjunctive gc layer to gc layer, this is the key to drive the bump to move
  226. phase_offset: float = 1.0 / 20 #relative to -pi~pi range
  227. class GCNet(bp.DynamicalSystem):
  228. """
  229. 2D continuous-attractor grid cell network
  230. Grid size is num_gc_x x num_gc_x (total cells = num_gc_1side**2).
  231. """
  232. def __init__(self, num_dc: int = 100, num_gc_x: int = 100, envsize=1, params: GCParams = GCParams()):
  233. super().__init__()
  234. self.num_dc = num_dc
  235. self.num_gc_1side = num_gc_x
  236. self.envsize = envsize
  237. self.params = params
  238. # ----- derived parameters -----
  239. self.num = num_gc_x * num_gc_x
  240. self.m = params.adaptation_strength * params.tau / params.tau_v
  241. self.Lambda = 2 * bm.pi / params.mapping_ratio #grid spacing
  242. # ----- coordinate transforms (hex vs rect) -----
  243. # Note that coor_transform is to map a parallelogram with a 60-degree angle back to a square
  244. # The logic is to partition the 2D space into parallelograms, each of which contains one lattice of grid cells, and repeat the parallelogram to tile the whole space
  245. self.coor_transform = bm.array([[1.0, -1.0 / bm.sqrt(3.0)],
  246. [0.0, 2.0 / bm.sqrt(3.0)]])
  247. # inverse, which is bm.array([[1.0, 1.0 / 2],[0.0, bm.sqrt(3.0) / 2]])
  248. # Note that coor_transform_inv is to map a square to a parallelogram with a 60-degree angle
  249. self.coor_transform_inv = np.linalg.inv(np.array(self.coor_transform))
  250. # ----- feature space -----
  251. x_bins = bm.linspace(-bm.pi, bm.pi, num_gc_x + 1)
  252. x_grid, y_grid = bm.meshgrid(x_bins[:-1], x_bins[:-1])
  253. self.x_grid = x_grid.reshape(-1)
  254. self.y_grid = y_grid.reshape(-1)
  255. # positions in (x,y) space and transformed space
  256. self.value_grid = bm.stack([self.x_grid, self.y_grid], axis=1) # (N, 2)
  257. self.value_bump = self.value_grid * 4
  258. # ----- candidate centers (for center snapping) -----
  259. self.candidate_centers = self.make_candidate_centers(self.Lambda)
  260. # ----- connectivity -----
  261. base_connection = self.make_connection()
  262. noise_connection = np.random.normal(0.0, params.conn_noise, size=(self.num, self.num))
  263. self.conn_mat = base_connection + noise_connection
  264. # ----- state variables -----
  265. self.r = bm.Variable(bm.zeros(self.num))
  266. self.u = bm.Variable(bm.zeros(self.num))
  267. self.v = bm.Variable(bm.zeros(self.num))
  268. self.gc_bump = bm.Variable(bm.zeros(self.num))
  269. self.conj_input = bm.Variable(bm.zeros(self.num))
  270. self.sensory_input = bm.Variable(bm.zeros(self.num))
  271. self.center_phase = bm.Variable(bm.zeros(2))
  272. self.center_position = bm.Variable(bm.zeros(2))
  273. self.conj_center = bm.Variable(bm.zeros(2))
  274. self.sensory_center = bm.Variable(bm.zeros(2))
  275. # ----- integrator -----
  276. self.integral = bp.odeint(method="exp_euler", f=self.derivative)
  277. # ========================= Dynamics =========================
  278. @property
  279. def derivative(self):
  280. # pass total input (Irec + external + noise) as 'inp'
  281. du = lambda u, t, inp: (-u + inp - self.v) / self.params.tau
  282. dv = lambda v, t: (-v + self.m * self.u) / self.params.tau_v
  283. return bp.JointEq([du, dv])
  284. # ========================= Utilities =========================
  285. def handle_periodic_condition(self, d):
  286. d = bm.where(d > bm.pi, d - 2.0 * bm.pi, d)
  287. d = bm.where(d < -bm.pi, d + 2.0 * bm.pi, d)
  288. return d
  289. def calculate_dist(self, d):
  290. """
  291. d: (..., 2) displacement in original (x,y).
  292. Return Euclidean distance after transform (hex/rect).
  293. """
  294. #consider the periodic boundary condition
  295. d = self.handle_periodic_condition(d)
  296. # transform to lattice axes
  297. dist = (bm.matmul(self.coor_transform_inv, d.T)).T #This means the bump on the parallelogram lattice is a Gaussian, while in the square space it is a twisted Gaussian
  298. return bm.sqrt(dist[:, 0] ** 2 + dist[:, 1] ** 2)
  299. def make_candidate_centers(self, Lambda):
  300. #This will generate a massivly large number of candidate centers, so that cover the simulated environmental size sufficiently
  301. N_c = bm.int(bm.ceil(self.envsize/Lambda)+1) * 2 #number of candidates along one dimension
  302. cc = bm.zeros((N_c, N_c, 2))
  303. for i in range(N_c):
  304. for j in range(N_c):
  305. cc = cc.at[i, j, 0].set((-N_c // 2 + i) * Lambda)
  306. cc = cc.at[i, j, 1].set((-N_c // 2 + j) * Lambda)
  307. cc_tranformed = bm.dot(self.coor_transform_inv, cc.reshape(N_c * N_c, 2).T).T
  308. return cc_tranformed
  309. # ========================= Connectivity =========================
  310. def make_connection(self):
  311. @jax.vmap
  312. def kernel(v):
  313. # v: (2,) location in (x,y)
  314. d = self.calculate_dist(v - self.value_grid) # (N,)
  315. return (
  316. (self.params.J0 / self.params.g)
  317. * bm.exp(-0.5 * bm.square(d / self.params.a))
  318. / (bm.sqrt(2.0 * bm.pi) * self.params.a)
  319. )
  320. return kernel(self.value_grid) # (N, N)
  321. # ========================= Inputs =========================
  322. def position2phase(self, position):
  323. """
  324. map position->phase; phase is wrapped to [-pi, pi] per-axis
  325. """
  326. mapped_pos = position * self.params.mapping_ratio
  327. phase = bm.matmul(self.coor_transform, mapped_pos) + bm.pi
  328. px = bm.mod(phase[0], 2.0 * bm.pi) - bm.pi
  329. py = bm.mod(phase[1], 2.0 * bm.pi) - bm.pi
  330. return bm.array([px, py])
  331. def calculate_input_from_conjgc(self, animal_pos, direction_activity, theta_modulation):
  332. """Get input from conjunctive grid cell layer → grid cell layer; returns (N,) vector."""
  333. assert bm.size(animal_pos) == 2
  334. num_dc = self.num_dc
  335. num_gc = self.num
  336. direction_bin = bm.linspace(-bm.pi, bm.pi, num_dc)
  337. # # # lag relative to head direction
  338. # lagvec = -bm.array([bm.cos(head_direction), bm.sin(head_direction)]) * self.params.phase_offset * 0.0
  339. # offset = bm.array([bm.cos(direction_bin), bm.sin(direction_bin)]) * self.params.phase_offset + lagvec.reshape(-1, 1)
  340. offset = bm.array([bm.cos(direction_bin), bm.sin(direction_bin)]) * self.params.phase_offset
  341. center_conj = self.position2phase(animal_pos.reshape(-1, 1) + offset.reshape(-1, num_dc))
  342. conj_input = bm.zeros((num_dc, num_gc))
  343. for i in range(num_dc):
  344. d = self.calculate_dist(bm.asarray(center_conj[:, i]) - self.value_grid)
  345. conj_input = conj_input.at[i].set(self.params.A * bm.exp(-0.5 * bm.square(d / self.params.a)))
  346. # weighting by direction bump activity: keep top one-third (by max) then normalize, I thinking using all direction_activity should also be fine
  347. weight = bm.where(direction_activity > bm.max(direction_activity) / 3, direction_activity, 0.0)
  348. weight = weight / (bm.sum(weight) + 1e-12) # avoid div-by-zero, dim: (num_dc,)
  349. return (bm.matmul(conj_input.T, weight).reshape(-1) * theta_modulation) #dim: (num_gc, num_dc) x (num_dc,) -> (num_gc,)
  350. def calculate_sensory_input(self, animal_pos, theta_modulation):
  351. """Get sensory input to grid cell layer based on animal position; returns (N,) vector."""
  352. center_phase = self.position2phase(animal_pos)
  353. d = self.calculate_dist(center_phase - self.value_grid)
  354. sensory_input = self.params.A * bm.exp(-0.5 * bm.square(d / self.params.a))
  355. return sensory_input* theta_modulation #dim: (num_gc,)
  356. # ========================= Bump center (phase/pos) =========================
  357. def get_unique_activity_bump(self, network_activity, animal_position):
  358. """
  359. Estimate a unique bump (activity peak) from the current network state,
  360. given the animal's actual position.
  361. Returns
  362. -------
  363. center_phase : (2,) array
  364. Phase coordinates of bump center on the manifold.
  365. center_position : (2,) array
  366. Real-space position of the bump (nearest candidate).
  367. bump : (N,) array
  368. Gaussian bump template centered at center_position.
  369. """
  370. # --- find bump center in phase space ---
  371. exppos_x = bm.exp(1j * self.x_grid)
  372. exppos_y = bm.exp(1j * self.y_grid)
  373. activity_masked = bm.where(network_activity > bm.max(network_activity) * 0.1, network_activity, 0.0)
  374. center_phase = bm.zeros((2,))
  375. center_phase = center_phase.at[0].set(bm.angle(bm.sum(exppos_x * activity_masked)))
  376. center_phase = center_phase.at[1].set(bm.angle(bm.sum(exppos_y * activity_masked)))
  377. # --- map back to real space, snap to nearest candidate ---
  378. center_phase_residual = bm.matmul(self.coor_transform_inv, center_phase) / self.params.mapping_ratio
  379. candidate_pos_all = self.candidate_centers + center_phase_residual
  380. distances = bm.linalg.norm(candidate_pos_all - animal_position, axis=1)
  381. center_position = candidate_pos_all[bm.argmin(distances)]
  382. # --- build Gaussian bump template ---
  383. d = bm.asarray(center_position) - self.value_bump
  384. dist = bm.sqrt(d[:, 0] ** 2 + d[:, 1] ** 2)
  385. gc_bump = self.params.A * bm.exp(-bm.square(dist / self.params.a))
  386. return center_phase, center_position, gc_bump
  387. # one-step update (main)
  388. def update(self, animal_posistion, direction_activity, theta_modulation_conj2gc, theta_modulation_sensory2gc):
  389. # get bump activity in real space info from network activity on the manifold ---
  390. center_phase, center_position, gc_bump = self.get_unique_activity_bump(self.r, animal_posistion)
  391. self.center_phase.value = center_phase
  392. self.center_position.value = center_position
  393. self.gc_bump.value = gc_bump
  394. # get external input to grid cell layer from conjunctive grid cell layer
  395. # note that this conjunctive input will be theta modulated. When speed is high, theta modulation is high, thus input is stronger
  396. # This is how we get longer theta sweeps when speed is high
  397. conj_input = self.calculate_input_from_conjgc(animal_posistion, direction_activity, theta_modulation_conj2gc)
  398. sensory_input = self.calculate_sensory_input(animal_posistion, theta_modulation_sensory2gc)
  399. self.conj_input.value = conj_input
  400. self.sensory_input.value = sensory_input
  401. #get conj_input center and sensory_input center for monitoring
  402. _, conj_center_pos, _ = self.get_unique_activity_bump(conj_input, animal_posistion)
  403. _, sensory_center_pos, _ = self.get_unique_activity_bump(sensory_input, animal_posistion)
  404. self.conj_center.value = conj_center_pos
  405. self.sensory_center.value = sensory_center_pos
  406. # recurrent + noise
  407. Irec = bm.matmul(self.conn_mat, self.r)
  408. input_noise = bm.random.randn(self.num) * self.params.noise_strength
  409. total_net_input = Irec + conj_input + sensory_input + input_noise
  410. # integrate
  411. u, v = self.integral(self.u, self.v, bp.share.load("t"), total_net_input)
  412. self.u.value = bm.where(u > 0.0, u, 0.0)
  413. self.v.value = v
  414. # get neuron firing by global inhibition
  415. u_sq = bm.square(self.u)
  416. self.r.value = self.params.g * u_sq / (1.0 + self.params.k * bm.sum(u_sq))

network_models.py at commit 8c1af28, no license · at the source

Overview

Authors: Zilong Ji1,2, Huiwen Zhang1, Callum Marshall1, Rokas Stonis3, Neil Burgess1,2,3
  1. UCL Institute of Cognitive Neuroscience, University College London, London, UK
  2. UCL Queen Square Institute of Neurology, University College London, London, UK
  3. UCL Sainsbury Wellcome Centre for Neural Circuits and Behaviour, University College London, London, UK
Institutions: UCL Queen Square Institute of Neurology (United Kingdom); University College London (United Kingdom); Sainsbury Wellcome Centre (United Kingdom)
Journal: Science advances, volume 12, issue 36, article eaeg6797
Dates: received 24 February 2026; accepted 20 July 2026; published online 4 September 2026; in print September 2026
Type: Research article · Language: English
License: CC BY-NC
Identifiers: DOI 10.1126/sciadv.aeg6797 · PMID 42696579 · PMCID PMC13544234 · OpenAlex W7134250837
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: none (in silico) (organism), systems (subfield)
Methods: Spectral & time-frequency, Smoothing, state filtering, decompositions, Machine learning, Connectivity, Statistics, Single-unit activity, calcium imaging
MeSH: Entorhinal Cortex*, Theta Rhythm*, Action Potentials, Animals, Computer Simulation, Grid Cells, Models, Neurological (* major topic)
Journal subjects: Neuroscience, Neurophysiology
Topic: Memory and Neural Mechanisms (Cognitive Neuroscience, Neuroscience), according to OpenAlex
Funding: Wellcome Principle Research Fellowship (222457/Z/21/Z)
Citations: not cited yet (Europe PMC); 36 references in the paper

Abstract

During random foraging, the positional signal decoded from entorhinal grid cells exhibits left-right theta sweeps, alternating from one side of the head direction to the other across successive theta cycles. Here, we report that theta sweeps are topographically organized along the dorsoventral axis of the medial entorhinal cortex, with the angular deviation from head direction increasing gradually from dorsal (smaller scale) to ventral (larger scale) modules. This gradient coexists with a corresponding dorsoventral increase in angular deviation decoded from theta-modulated direction cells, which drive grid cell theta sweeps. These phenomena parallel a broadening of head direction tuning and increasing occurrence of theta cycle skipping in single-cell firing along the dorsoventral axis. Computational modeling demonstrates that these patterns are consistent with continuous attractor dynamics and a dorsoventral gradient in firing rate adaptation. These results highlight how theta sweeps can simultaneously represent multiple potential future locations and reveal a clear neural mechanism underlying this process.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 9 matches between paragraphs and lines of code.

Zenodo 21427119

License: CC-BY-4.0
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: “Data, code, and materials availability:”
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 26 September 2026: the link answers (HTTP 200)
  • 26 September 2026: the link answers (HTTP 200)

zilongji/topographythetasweeps

License: none: the authors keep all their rights
State: the link answers, verified on 26 September 2026
Evidence: files inventoried
Commit: 8c1af287e1110d396ca3a20afa107343d15697f6, 31 May 2026
Languages: Jupyter (36), Python (2)
Size: 250 files, 38 scripts
Software Heritage: not archived
Found in: the Zenodo archive record
Holds: README, 36 notebooks
Not found: license file, CITATION.cff, environment file, tests, continuous integration, documentation
Tools: NumPy (38 files), Matplotlib (37 files), SciPy (26 files), pandas (14 files), seaborn (14 files), statsmodels (10 files), JAX (3 files), scikit-learn (2 files), UMAP (2 files), h5py (1 file), NetworkX (1 file)
Availability: 1 check, the latest on 26 September 2026: the link answers
  • 26 September 2026: the link answers
39 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 38 scripts, each with its path and the digest of its content;
  • 9 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

Datasets cited

Data, code, and materials availability

All data and code needed to evaluate and reproduce the results in the paper are present in the paper and/or the Supplementary Materials. This study did not generate new materials. Data are publicly available at: https://doi.org/10.25493/R5FR-EDG. Code for reproducing all the results in the main text is available at: https://doi.org/10.5281/zenodo.21427119.

Reproduced under the paper's license (CC BY-NC), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 5 authors, 7 MeSH terms, 1 funder, 35 references.

Cite

This paper

Ji, Z., Zhang, H., Marshall, C., Stonis, R., & Burgess, N. (2026). Dorsoventral gradient of theta sweeps in the medial entorhinal cortex. Science advances, 12(36), eaeg6797. https://doi.org/10.1126/sciadv.aeg6797

BibTeX

@article{ji2026dorsoventral,
author = {Ji, Zilong and Zhang, Huiwen and Marshall, Callum and Stonis, Rokas and Burgess, Neil},
title = {{Dorsoventral gradient of theta sweeps in the medial entorhinal cortex}},
journal = {Science advances},
year = {2026},
month = sep,
volume = {12},
number = {36},
pages = {eaeg6797},
publisher = {American Association for the Advancement of Science},
issn = {2375-2548},
doi = {10.1126/sciadv.aeg6797},
url = {https://doi.org/10.1126/sciadv.aeg6797},
pmid = {42696579},
pmcid = {PMC13544234}
}

RIS

TY - JOUR
AU - Ji, Zilong
AU - Zhang, Huiwen
AU - Marshall, Callum
AU - Stonis, Rokas
AU - Burgess, Neil
TI - Dorsoventral gradient of theta sweeps in the medial entorhinal cortex
T2 - Science advances
J2 - Sci Adv
PY - 2026
DA - 2026/09/04
VL - 12
IS - 36
SP - eaeg6797
SN - 2375-2548
PB - American Association for the Advancement of Science
DO - 10.1126/sciadv.aeg6797
UR - https://doi.org/10.1126/sciadv.aeg6797
LA - en
ER -

CSL-JSON

{
"id": "10.1126/sciadv.aeg6797",
"type": "article-journal",
"title": "Dorsoventral gradient of theta sweeps in the medial entorhinal cortex",
"container-title": "Science advances",
"author": [
{
"family": "Ji",
"given": "Zilong"
},
{
"family": "Zhang",
"given": "Huiwen"
},
{
"family": "Marshall",
"given": "Callum"
},
{
"family": "Stonis",
"given": "Rokas"
},
{
"family": "Burgess",
"given": "Neil"
}
],
"container-title-short": "Sci Adv",
"volume": "12",
"issue": "36",
"page": "eaeg6797",
"DOI": "10.1126/sciadv.aeg6797",
"PMID": "42696579",
"PMCID": "PMC13544234",
"ISSN": "2375-2548",
"publisher": "American Association for the Advancement of Science",
"URL": "https://doi.org/10.1126/sciadv.aeg6797",
"language": "en",
"issued": {
"date-parts": [
[
2026,
9,
4
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1002/hipo.70131 [code]
Decoding Medial Entorhinal Cortical Dynamics Produces Planning-Like Alternations in Hippocampal theta Sequences.
Journal: Hippocampus
In common: JAX, h5py, scikit-learn, 4 other tools, DOI 10.25493/r5fr-edg, systems, 13 references
[2] doi:10.1038/s41593-026-02365-2 [code]
Hippocampal theta sweeps indicate goal direction during navigation.
Journal: Nature neuroscience
In common: JAX, SciPy, Matplotlib, 1 other tool, 9 references, 2 authors
[3] doi:10.1038/s41467-026-74357-6 [code]
Hippocampo-neocortical interaction as compressive retrieval-augmented generation.
Journal: Nature communications
In common: UMAP, NetworkX, statsmodels, 6 other tools, author Neil Burgess
[4] doi: [code]
Naturalistic behavior and self-generated neural activity predictive of self-correction
Journal: bioRxiv : the preprint server for biology
In common: JAX, NetworkX, statsmodels, 6 other tools, 3 references
[5] doi:10.1038/s41593-026-02232-0 [code]
Entorhinal cortex represents task-relevant remote locations independently of CA1.
Journal: Nature neuroscience
In common: NetworkX, h5py, statsmodels, 6 other tools, systems, 3 references
[6] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: JAX, UMAP, NetworkX, 7 other tools
[7] doi:10.1016/j.isci.2026.116825 [code]
Social hierarchy shapes behavioral and transcriptional responses to chronic stress and ketamine in male mice.
Journal: iScience
In common: UMAP, NetworkX, h5py, 7 other tools, systems
[8] doi:10.3389/fncom.2026.1786996 [code]
Schumann-anchored golden ratio organization of human neural oscillations.
Journal: Frontiers in computational neuroscience
In common: UMAP, NetworkX, h5py, 7 other tools, systems
[9] doi:10.1038/s41467-026-75959-w [code]
Charting higher-order models of brain function beyond pairwise interactions.
Journal: Nature communications
In common: JAX, NetworkX, h5py, 7 other tools
[10] doi:10.1093/nar/gkag706 [code]
scDifformer: diffusion-based post-training for virtual cell modeling across large-scale single-cell data.
Journal: Nucleic acids research
In common: UMAP, NetworkX, h5py, 7 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.