dendrotweaks 0.4.2__py3-none-any.whl → 0.4.4__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.2"
1
+ __version__ = "0.4.4"
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,23 +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._nseg = nseg
499
- sec._ref.nseg = nseg
500
- # Rebuild the segment tree
501
- 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
502
520
 
503
- # Redistribute parameters
521
+ # Rebuild the segment tree and redistribute parameters
522
+ self.seg_tree = create_segment_tree(self.sec_tree)
504
523
  self.distribute_all()
505
524
 
525
+ # Reload stimuli and clean up temporary files
526
+ self._temp_reload_stimuli()
527
+
506
528
 
507
529
  # ========================================================================
508
530
  # MECHANISMS
@@ -675,17 +697,26 @@ class Model():
675
697
 
676
698
  def define_domain(self, domain_name: str, sections, distribute=True):
677
699
  """
678
- Adds a new domain to the tree and ensures correct partitioning of
679
- 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.
680
708
 
681
709
  Parameters
682
710
  ----------
683
711
  domain_name : str
684
- The name of the domain.
712
+ The name of the domain to be added or extended.
685
713
  sections : list[Section] or Callable
686
- The sections to include in the domain. If a callable is provided,
687
- it should be a filter function applied to the list of all sections
688
- 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.
689
720
  """
690
721
  if isinstance(sections, Callable):
691
722
  sections = self.get_sections(sections)
@@ -715,14 +746,22 @@ class Model():
715
746
  sec.uninsert_mechanism(mech)
716
747
 
717
748
 
749
+ # Add sections to the new domain
718
750
  for sec in sections_to_move:
719
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.
720
756
  for mech_name in self.domains_to_mechs.get(domain.name, set()):
721
757
  mech = self.mechanisms[mech_name]
722
- sec.insert_mechanism(mech, distribute=distribute)
758
+ sec.insert_mechanism(mech)
723
759
 
724
760
  self._remove_empty()
725
761
 
762
+ if distribute:
763
+ self.distribute_all()
764
+
726
765
 
727
766
  def _add_domain_groups(self, domain_name):
728
767
  """
@@ -1024,7 +1063,6 @@ class Model():
1024
1063
  distribution = Distribution(distr_type, **distr_params)
1025
1064
  self.params[param_name][group_name] = distribution
1026
1065
 
1027
-
1028
1066
  def distribute_all(self):
1029
1067
  """
1030
1068
  Distribute all parameters to the segments.
@@ -1471,6 +1509,8 @@ class Model():
1471
1509
 
1472
1510
 
1473
1511
  # Reinsert active mechanisms after creating the new domain
1512
+ # The new domain by default has no mechanisms. Here we re-insert the
1513
+ # exact same mechanisms as in the original domain of the root section.
1474
1514
  for mech_name in inserted_mechs:
1475
1515
  mech = self.mechanisms[mech_name]
1476
1516
  root.insert_mechanism(mech)
@@ -1889,10 +1929,10 @@ class Model():
1889
1929
  segments = [self.sec_tree.sections[sec_idx](loc)
1890
1930
  for sec_idx, loc in zip(df_pop['sec_idx'], df_pop['loc'])]
1891
1931
 
1892
- pop = Population(i,
1893
- segments,
1894
- pop_data['N'],
1895
- 'AMPA')
1932
+ pop = Population(idx=i,
1933
+ segments=segments,
1934
+ N=pop_data['N'],
1935
+ syn_type=syn_type)
1896
1936
 
1897
1937
  syn_locs = [(self.sec_tree.sections[sec_idx], loc) for sec_idx, loc in zip(df_pop['sec_idx'].tolist(), df_pop['loc'].tolist())]
1898
1938
 
@@ -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.2
3
+ Version: 0.4.4
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=9L85_UrXGRtxCoSzemZUlzdq_FWZPX0Hjz7HUtyqepg,384
2
- dendrotweaks/model.py,sha256=rMvS0BnqQ4ozIbM65t1jgydm5IeFo1QH0FAXEQNM-bs,69141
1
+ dendrotweaks/__init__.py,sha256=Z2JpwnO9nBPrkR1w0u_6gWnCJetIhTx_27PehYlfTRM,384
2
+ dendrotweaks/model.py,sha256=IjmLrp-Ooh1WLdTb7Xg_9q2KUO7Xuokll26NQyVITlk,70965
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.2.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
53
- dendrotweaks-0.4.2.dist-info/METADATA,sha256=RHDsvzirnDTlrF2wl4IqLgSLw-40UOJ8_VfNtoezyYw,2740
54
- dendrotweaks-0.4.2.dist-info/WHEEL,sha256=Nw36Djuh_5VDukK0H78QzOX-_FQEo6V37m3nkm96gtU,91
55
- dendrotweaks-0.4.2.dist-info/top_level.txt,sha256=OzT_2BSI5j5zxC447K6Y-0W-GHbued7iX-_hFGAKMxY,13
56
- dendrotweaks-0.4.2.dist-info/RECORD,,
52
+ dendrotweaks-0.4.4.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
53
+ dendrotweaks-0.4.4.dist-info/METADATA,sha256=2eqzr1zuEY9kyPT0X0ggJ5m_lAqV2_sQKEtSX9TA9PQ,2740
54
+ dendrotweaks-0.4.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
55
+ dendrotweaks-0.4.4.dist-info/top_level.txt,sha256=OzT_2BSI5j5zxC447K6Y-0W-GHbued7iX-_hFGAKMxY,13
56
+ dendrotweaks-0.4.4.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.7.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5