dendrotweaks 0.4.1__py3-none-any.whl → 0.4.3__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.
dendrotweaks/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "0.4.1"
1
+ __version__ = "0.4.3"
2
2
 
3
3
  from dendrotweaks.model import Model
4
4
  from dendrotweaks.simulators import NeuronSimulator
@@ -24,7 +24,8 @@ def get_somatic_data(model):
24
24
  tuple
25
25
  A tuple containing the voltage, time, time step, and injected current.
26
26
  """
27
- seg = model.seg_tree.root
27
+ soma = model.sec_tree.root
28
+ seg = soma(0.5)
28
29
  iclamp = model.iclamps[seg]
29
30
 
30
31
  v = np.array(model.simulator.recordings['v'][seg])
@@ -163,7 +164,8 @@ def detect_somatic_spikes(model, **kwargs):
163
164
  Returns:
164
165
  dict: A dictionary containing spike metrics.
165
166
  """
166
- seg = model.seg_tree.root
167
+ soma = model.sec_tree.root
168
+ seg = soma(0.5)
167
169
 
168
170
  v = np.array(model.simulator.recordings['v'][seg])
169
171
  t = np.array(model.simulator.t)
@@ -20,7 +20,7 @@ def constant(position, value=0):
20
20
  The value of the constant function at the given position.
21
21
  """
22
22
  if isinstance(position, ndarray):
23
- return full_like(position, value)
23
+ return full_like(position, value, dtype=float)
24
24
  else:
25
25
  return value
26
26
 
@@ -42,7 +42,7 @@ def uniform(position, value=0):
42
42
  The value of the constant function at the given position.
43
43
  """
44
44
  if isinstance(position, ndarray):
45
- return full_like(position, value)
45
+ return full_like(position, value, dtype=float)
46
46
  else:
47
47
  return value
48
48
 
@@ -71,7 +71,7 @@ class Mechanism():
71
71
  }
72
72
 
73
73
  def __repr__(self):
74
- return f"<Mechnaism({self.name})>"
74
+ return f"<Mechanism({self.name})>"
75
75
 
76
76
 
77
77
 
dendrotweaks/model.py CHANGED
@@ -473,6 +473,25 @@ class Model():
473
473
  # SEGMENTATION
474
474
  # ========================================================================
475
475
 
476
+ # TODO Make a context manager for this
477
+ def _temp_clear_stimuli(self):
478
+ """
479
+ Temporarily save and clear stimuli.
480
+ """
481
+ self.export_stimuli(file_name='_temp_stimuli')
482
+ self.remove_all_stimuli()
483
+ self.remove_all_recordings()
484
+
485
+ def _temp_reload_stimuli(self):
486
+ """
487
+ Load stimuli from a temporary file and clean up.
488
+ """
489
+ self.load_stimuli(file_name='_temp_stimuli')
490
+ for ext in ['json', 'csv']:
491
+ temp_path = self.path_manager.get_file_path('stimuli', '_temp_stimuli', extension=ext)
492
+ if os.path.exists(temp_path):
493
+ os.remove(temp_path)
494
+
476
495
  def set_segmentation(self, d_lambda=0.1, f=100):
477
496
  """
478
497
  Set the number of segments in each section based on the geometry.
@@ -486,22 +505,26 @@ class Model():
486
505
  """
487
506
  self.d_lambda = d_lambda
488
507
 
508
+ # Temporarily save and clear stimuli
509
+ self._temp_clear_stimuli()
510
+
489
511
  # Pre-distribute parameters needed for lambda_f calculation
490
512
  for param_name in ['cm', 'Ra']:
491
513
  self.distribute(param_name)
492
514
 
493
- # Calculate lambda_f for each section and set nseg
515
+ # Calculate lambda_f and set nseg for each section
494
516
  for sec in self.sec_tree.sections:
495
517
  lambda_f = calculate_lambda_f(sec.distances, sec.diameters, sec.Ra, sec.cm, f)
496
- nseg = int((sec.L / (d_lambda * lambda_f) + 0.9) / 2) * 2 + 1
497
- # TODO: Set sec._nseg instead
498
- sec._ref.nseg = nseg
499
- # Rebuild the segment tree
500
- self.seg_tree = create_segment_tree(self.sec_tree)
518
+ nseg = max(1, int((sec.L / (d_lambda * lambda_f) + 0.9) / 2) * 2 + 1)
519
+ sec._nseg = sec._ref.nseg = nseg
501
520
 
502
- # Redistribute parameters
521
+ # Rebuild the segment tree and redistribute parameters
522
+ self.seg_tree = create_segment_tree(self.sec_tree)
503
523
  self.distribute_all()
504
524
 
525
+ # Reload stimuli and clean up temporary files
526
+ self._temp_reload_stimuli()
527
+
505
528
 
506
529
  # ========================================================================
507
530
  # MECHANISMS
@@ -674,17 +697,26 @@ class Model():
674
697
 
675
698
  def define_domain(self, domain_name: str, sections, distribute=True):
676
699
  """
677
- Adds a new domain to the tree and ensures correct partitioning of
678
- the section tree graph.
700
+ Adds a new domain to the cell and ensures proper partitioning
701
+ of the section tree graph.
702
+
703
+ This method does not automatically insert mechanisms into the newly
704
+ created domain. It is the user's responsibility to insert mechanisms
705
+ into the domain after its creation. However, if the domain already
706
+ exists and is being extended, mechanisms will be inserted automatically
707
+ into the newly added sections.
679
708
 
680
709
  Parameters
681
710
  ----------
682
711
  domain_name : str
683
- The name of the domain.
712
+ The name of the domain to be added or extended.
684
713
  sections : list[Section] or Callable
685
- The sections to include in the domain. If a callable is provided,
686
- it should be a filter function applied to the list of all sections
687
- of the cell.
714
+ The sections to include in the domain. If a callable is provided,
715
+ it should be a filter function applied to the list of all sections
716
+ in the model.
717
+ distribute : bool, optional
718
+ Whether to re-distribute the parameters after defining the domain.
719
+ Default is True.
688
720
  """
689
721
  if isinstance(sections, Callable):
690
722
  sections = self.get_sections(sections)
@@ -714,14 +746,22 @@ class Model():
714
746
  sec.uninsert_mechanism(mech)
715
747
 
716
748
 
749
+ # Add sections to the new domain
717
750
  for sec in sections_to_move:
718
751
  domain.add_section(sec)
752
+ # Important: here we insert mechanisms only if we extend the domain,
753
+ # i.e. the domain already exists and has mechanisms.
754
+ # If the domain is new, we DO NOT insert mechanisms automatically
755
+ # and leave it to the user to do so.
719
756
  for mech_name in self.domains_to_mechs.get(domain.name, set()):
720
757
  mech = self.mechanisms[mech_name]
721
- sec.insert_mechanism(mech, distribute=distribute)
758
+ sec.insert_mechanism(mech)
722
759
 
723
760
  self._remove_empty()
724
761
 
762
+ if distribute:
763
+ self.distribute_all()
764
+
725
765
 
726
766
  def _add_domain_groups(self, domain_name):
727
767
  """
@@ -1023,7 +1063,6 @@ class Model():
1023
1063
  distribution = Distribution(distr_type, **distr_params)
1024
1064
  self.params[param_name][group_name] = distribution
1025
1065
 
1026
-
1027
1066
  def distribute_all(self):
1028
1067
  """
1029
1068
  Distribute all parameters to the segments.
@@ -1065,8 +1104,9 @@ class Model():
1065
1104
  value = seg.parent.get_param_value(param_name)
1066
1105
  seg.set_param_value(param_name, value)
1067
1106
  else:
1068
- for seg in filtered_segments:
1069
- value = distribution(seg.path_distance())
1107
+ dists = np.array([seg.path_distance() for seg in filtered_segments])
1108
+ values = distribution(dists)
1109
+ for seg, value in zip(filtered_segments, values):
1070
1110
  seg.set_param_value(param_name, value)
1071
1111
 
1072
1112
 
@@ -1470,6 +1510,8 @@ class Model():
1470
1510
 
1471
1511
 
1472
1512
  # Reinsert active mechanisms after creating the new domain
1513
+ # The new domain by default has no mechanisms. Here we re-insert the
1514
+ # exact same mechanisms as in the original domain of the root section.
1473
1515
  for mech_name in inserted_mechs:
1474
1516
  mech = self.mechanisms[mech_name]
1475
1517
  root.insert_mechanism(mech)
@@ -1888,10 +1930,10 @@ class Model():
1888
1930
  segments = [self.sec_tree.sections[sec_idx](loc)
1889
1931
  for sec_idx, loc in zip(df_pop['sec_idx'], df_pop['loc'])]
1890
1932
 
1891
- pop = Population(i,
1892
- segments,
1893
- pop_data['N'],
1894
- 'AMPA')
1933
+ pop = Population(idx=i,
1934
+ segments=segments,
1935
+ N=pop_data['N'],
1936
+ syn_type=syn_type)
1895
1937
 
1896
1938
  syn_locs = [(self.sec_tree.sections[sec_idx], loc) for sec_idx, loc in zip(df_pop['sec_idx'].tolist(), df_pop['loc'].tolist())]
1897
1939
 
@@ -85,6 +85,9 @@ class Domain:
85
85
  return
86
86
  sec.domain = None
87
87
  sec.domain_idx = None
88
+ if hasattr(sec, 'path_distance_within_domain'):
89
+ # Remove cached property if it exists
90
+ del sec.path_distance_within_domain
88
91
  self._sections.remove(sec)
89
92
 
90
93
 
@@ -11,6 +11,7 @@ from dendrotweaks.morphology.trees import Node, Tree
11
11
  from dendrotweaks.morphology.domains import Domain
12
12
  from dataclasses import dataclass, field
13
13
  from bisect import bisect_left
14
+ from functools import cached_property
14
15
 
15
16
  import warnings
16
17
 
@@ -271,11 +272,55 @@ class Section(Node):
271
272
  seg_values = [seg.get_param_value(param_name) for seg in self.segments]
272
273
  return round(np.mean(seg_values), 16)
273
274
 
275
+ @cached_property
276
+ def path_distance_to_root(self) -> float:
277
+ """
278
+ Calculate the total distance from the section start to the root.
279
+
280
+ Returns
281
+ -------
282
+ float
283
+ The distance from the section start to the root.
284
+ """
285
+ distance = 0
286
+ node = self.parent # Start from the parent node
287
+
288
+ if node is None:
289
+ return 0
290
+
291
+ while node.parent:
292
+ distance += node.length
293
+ node = node.parent
294
+
295
+ return distance
296
+
297
+ @cached_property
298
+ def path_distance_within_domain(self) -> float:
299
+ """
300
+ Calculate the distance from the section start to the root within the same domain.
301
+
302
+ Returns
303
+ -------
304
+ float
305
+ The distance from the section start to the root within the same domain.
306
+ """
307
+ distance = 0
308
+ node = self.parent # Start from the parent node
309
+
310
+ if node is None:
311
+ return 0
274
312
 
275
- def path_distance(self, relative_position: float = 0,
276
- within_domain: bool = False) -> float:
313
+ while node.parent:
314
+ if node.domain != self.domain:
315
+ break
316
+ distance += node.length
317
+ node = node.parent
318
+
319
+ return distance
320
+
321
+ def path_distance(self, relative_position: float = 0, within_domain: bool = False) -> float:
277
322
  """
278
- Calculate the distance from the section to the root at a given relative position.
323
+ Get the distance from the section to the root at a given relative position.
279
324
 
280
325
  Parameters
281
326
  ----------
@@ -288,29 +333,16 @@ class Section(Node):
288
333
  -------
289
334
  float
290
335
  The distance from the section to the root.
291
-
292
- Important
293
- ---------
294
- Assumes that we always attach the 0 end of the child.
295
336
  """
296
337
  if not (0 <= relative_position <= 1):
297
338
  raise ValueError('Relative position must be between 0 and 1.')
298
339
 
299
- distance = 0
300
- factor = relative_position
301
- node = self
302
-
303
- while node.parent:
340
+ if self.parent is None: # Soma section
341
+ # relative_position = abs(relative_position - 0.5)
342
+ return 0
304
343
 
305
- distance += factor * node.length
306
-
307
- if within_domain and node.parent.domain != node.domain:
308
- break
309
-
310
- node = node.parent
311
- factor = 1
312
-
313
- return distance
344
+ base_distance = self.path_distance_within_domain if within_domain else self.path_distance_to_root
345
+ return base_distance + relative_position * self.length
314
346
 
315
347
 
316
348
  def disconnect_from_parent(self):
@@ -671,11 +703,13 @@ class NeuronSection(Section):
671
703
  first_segment = self.segments[0]
672
704
  parent = first_segment.parent
673
705
 
674
- for i, seg in enumerate(new_segments[:]):
675
- if i == 0:
676
- seg_tree.insert_node_before(seg, first_segment)
677
- else:
678
- seg_tree.insert_node_before(seg, new_segments[i-1])
706
+ for seg in new_segments:
707
+ seg_tree.insert_node_before(seg, first_segment)
708
+ # for i, seg in enumerate(new_segments[:]):
709
+ # if i == 0:
710
+ # seg_tree.insert_node_before(seg, first_segment)
711
+ # else:
712
+ # seg_tree.insert_node_after(seg, new_segments[i-1])
679
713
 
680
714
  for seg in old_segments:
681
715
  seg_tree.remove_node(seg)
@@ -178,7 +178,7 @@ class NeuronSimulator(Simulator):
178
178
  The location along the normalized section length to remove the recording from.
179
179
  """
180
180
  seg = sec(loc)
181
- if self._recordings[var].get(seg):
181
+ if seg in self._recordings[var]:
182
182
  self._recordings[var][seg] = None
183
183
  self._recordings[var].pop(seg)
184
184
  if not self._recordings[var]:
@@ -255,7 +255,7 @@ class Population():
255
255
  'syn_type': [self.syn_type] * len(flat_synapses),
256
256
  'name': [self.name] * len(flat_synapses),
257
257
  'sec_idx': [syn.sec.idx for syn in flat_synapses],
258
- 'loc': [syn.loc for syn in flat_synapses],
258
+ 'loc': [round(syn.loc, 8) for syn in flat_synapses],
259
259
  }
260
260
 
261
261
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dendrotweaks
3
- Version: 0.4.1
3
+ Version: 0.4.3
4
4
  Summary: A toolbox for exploring dendritic dynamics
5
5
  Home-page: https://dendrotweaks.dendrites.gr
6
6
  Author: Roman Makarov
@@ -1,16 +1,16 @@
1
- dendrotweaks/__init__.py,sha256=t5t6BzFqY45ePdil4bFlkEaQZiKS-KKthacbRUkwDhc,384
2
- dendrotweaks/model.py,sha256=Y6m9U0FOoMYG3qCLZczxYfl1-nXeL-4uZNuAqrDlwPQ,69113
1
+ dendrotweaks/__init__.py,sha256=qo6haBW1LRSOhXRjByeruiXSnrnjxrUxqRbDymWSLaQ,384
2
+ dendrotweaks/model.py,sha256=Dm_KJ8KbvHLCFKB3LwBHhgSrdiQteamqgda1J0p898w,71053
3
3
  dendrotweaks/model_io.py,sha256=xwXKMcUle-Y0HoWFYVZu3G8v4pdQXmeaDfl2Xi65eHw,2137
4
4
  dendrotweaks/path_manager.py,sha256=dai5o6UA0nk-ubwKWRu4LFdDBO77zW_SsMf6k0MLBiI,8703
5
- dendrotweaks/simulators.py,sha256=Vg6ToTkDZ4OGcANYwYtamGnasyLNbyP8pfPHl9PnMnM,7413
5
+ dendrotweaks/simulators.py,sha256=OscZ4H6z9YiNloDtMAgpPx9n2e-9WJyLEWtcSD1YZR8,7411
6
6
  dendrotweaks/utils.py,sha256=jaUJNb39Bsevg3WJByP56bO7CLj1wzlh-uGZl-lxi1I,7131
7
7
  dendrotweaks/analysis/__init__.py,sha256=SEYpoQ5iXiQXyHB20-IAdDHYI-7CR5GYFXIwr-O05Ug,858
8
- dendrotweaks/analysis/ephys_analysis.py,sha256=Caiww27p9dnL5_27OmZ95AZ_OmefvImAPyItsJMSdmA,14320
8
+ dendrotweaks/analysis/ephys_analysis.py,sha256=PqT3aBCuxQbvdm9jnXjlTJ3R5n7_Vwp4fLrHGgtfoWw,14362
9
9
  dendrotweaks/analysis/morphometric_analysis.py,sha256=5zohjGssyx-wezI-yY3Q-kYM_wzAQLLFBJ9Xk950_JY,3571
10
10
  dendrotweaks/biophys/__init__.py,sha256=k0o2xwyoaJUb1lfO9OHtqxheNP6R-Ya5o0g-bJOdCZg,360
11
- dendrotweaks/biophys/distributions.py,sha256=ADPFPA-CN7AbRJj0Ry4TxFZJhdYXJm87iIGWZSDr5vI,10299
11
+ dendrotweaks/biophys/distributions.py,sha256=XGczxBYJ0-vkIfXbfzvlIqlH9OpBuT9J95Kzyd2zL5A,10325
12
12
  dendrotweaks/biophys/groups.py,sha256=Q4kBIqL1-piIgrpsVq6ojinAWHiEJ1GzMjSAQ7Ni_E8,3212
13
- dendrotweaks/biophys/mechanisms.py,sha256=j9uygcwkK6Z_08hpTHax40Wn-eV4V_k_on_KyPDnO90,18520
13
+ dendrotweaks/biophys/mechanisms.py,sha256=IxKcyYftEbupJWPqGAV20Ox5a3uPbnXs3gYSlC3z-E0,18520
14
14
  dendrotweaks/biophys/default_mod/AMPA.mod,sha256=HY_pWzYvaSDV-w7qruenG2mnll8v79s40HFHjUCIi4U,980
15
15
  dendrotweaks/biophys/default_mod/AMPA_NMDA.mod,sha256=ztv2ePUiEQZ93-23FTkGO2DC91rehQuqo0NUIbHZ368,2318
16
16
  dendrotweaks/biophys/default_mod/CaDyn.mod,sha256=gwc69K_rxu2w_mV7CnOSOnVaCMc8Z-MfdBFf6lAj4kg,1298
@@ -33,9 +33,9 @@ dendrotweaks/biophys/io/loader.py,sha256=Wv9ZkEDyA3MkCdV0sMeRnBffg2WAI7yTV3r6C41
33
33
  dendrotweaks/biophys/io/parser.py,sha256=boT27lFrn5LYrJnkZFs0SwrZZrkSkwO8efqGPJ4Qj0I,17914
34
34
  dendrotweaks/biophys/io/reader.py,sha256=JWm5WM9illvSfDkhWEmWBcj8Y7PSi8zeZX9j1ARUHVU,6576
35
35
  dendrotweaks/morphology/__init__.py,sha256=JwXmSmdn9e_jqslITEdiU9kWvzxcxT9Aw_kUkXLbm5o,353
36
- dendrotweaks/morphology/domains.py,sha256=Y4txcGdBdl2aK1DfbTRziNtDyd6bChczwpCWE7lTFzg,2391
36
+ dendrotweaks/morphology/domains.py,sha256=l57KVR5eo1LlH_fCd1AOMiG_SsYLBPBTGQ5R78BHfdM,2545
37
37
  dendrotweaks/morphology/point_trees.py,sha256=5dUPaQXYPdJbWoD3pFI2DV2XnuFRhB5d0wTBlfmmIeI,21600
38
- dendrotweaks/morphology/sec_trees.py,sha256=fj-1kBj7InkqB27Ux-jPidGQXts7FAPXa1m2LdzekNY,35816
38
+ dendrotweaks/morphology/sec_trees.py,sha256=eKLC-yNhsn_rPdTE7w7p6STa1onYkBTGcpBKBpEWZUI,36957
39
39
  dendrotweaks/morphology/seg_trees.py,sha256=-XeSJuD7ZixBJYQDzvmSEiNvOWbVmX_DanyAPkkR-NA,4042
40
40
  dendrotweaks/morphology/trees.py,sha256=NrNvPMR-U0clt63eqwVJqU0H8NJgY53QGA_BkdcwkQI,16033
41
41
  dendrotweaks/morphology/io/__init__.py,sha256=gAZqZdf5VKPb6ksK8Lwt7MbTAq8TDP8uq3Vs_ebNFEY,324
@@ -47,10 +47,10 @@ dendrotweaks/morphology/reduce/reduce.py,sha256=5czZDrG3xsvHn3c_tbYhUOlXgST989-R
47
47
  dendrotweaks/morphology/reduce/reduced_cylinder.py,sha256=jGJ4J-amukRr-3DPirVR5pzNO-6H7_sZF1N_X57ZGdw,5132
48
48
  dendrotweaks/stimuli/__init__.py,sha256=bFfSEZhCVpwOVEBgLe65iiY3SdpjKPhyLemC1z5OX9I,153
49
49
  dendrotweaks/stimuli/iclamps.py,sha256=NjkhhwZKJR1f_g3N9BVxMVoO9ubBk5WkQ6h9Bnf9xgA,1681
50
- dendrotweaks/stimuli/populations.py,sha256=y85v8smiMifINIqXm1O3mOINAlDTz-SPGLS78alhX5A,8325
50
+ dendrotweaks/stimuli/populations.py,sha256=vq2NUOaxaltUwlcT7wuCe8z1JWc8rk6HfPmfEd1kK68,8335
51
51
  dendrotweaks/stimuli/synapses.py,sha256=g4MgWTske2TZ2i9FIIOE8-KXNx_3dWa3zEhB2rcqYig,5470
52
- dendrotweaks-0.4.1.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
53
- dendrotweaks-0.4.1.dist-info/METADATA,sha256=zX87vndTAiJHfWJkywrmMiqa7lNxcg4Kwfbf6tQ0OKs,2740
54
- dendrotweaks-0.4.1.dist-info/WHEEL,sha256=A8Eltl-h0W-qZDVezsLjjslosEH_pdYC2lQ0JcbgCzs,91
55
- dendrotweaks-0.4.1.dist-info/top_level.txt,sha256=OzT_2BSI5j5zxC447K6Y-0W-GHbued7iX-_hFGAKMxY,13
56
- dendrotweaks-0.4.1.dist-info/RECORD,,
52
+ dendrotweaks-0.4.3.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
53
+ dendrotweaks-0.4.3.dist-info/METADATA,sha256=KV5Ev0CC98m0JrqsvOG5szTc8Yqi_RjcKuOK4C7yHZo,2740
54
+ dendrotweaks-0.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
55
+ dendrotweaks-0.4.3.dist-info/top_level.txt,sha256=OzT_2BSI5j5zxC447K6Y-0W-GHbued7iX-_hFGAKMxY,13
56
+ dendrotweaks-0.4.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5