evonet 0.1.0.dev14__tar.gz → 0.1.0.dev16__tar.gz

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.
Files changed (28) hide show
  1. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/PKG-INFO +1 -1
  2. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/core.py +57 -28
  3. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/mutation.py +2 -8
  4. evonet-0.1.0.dev16/evonet/utils.py +28 -0
  5. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet.egg-info/PKG-INFO +1 -1
  6. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/pyproject.toml +1 -1
  7. evonet-0.1.0.dev14/evonet/utils.py +0 -0
  8. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/LICENSE +0 -0
  9. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/README.md +0 -0
  10. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/__init__.py +0 -0
  11. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/activation.py +0 -0
  12. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/connection.py +0 -0
  13. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/core_mit_plot_simple.py +0 -0
  14. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/enums.py +0 -0
  15. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/io.py +0 -0
  16. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/layer.py +0 -0
  17. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/neuron.py +0 -0
  18. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/serialization.py +0 -0
  19. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet/visualize.py +0 -0
  20. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet.egg-info/SOURCES.txt +0 -0
  21. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet.egg-info/dependency_links.txt +0 -0
  22. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet.egg-info/requires.txt +0 -0
  23. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/evonet.egg-info/top_level.txt +0 -0
  24. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/setup.cfg +0 -0
  25. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/tests/test_activation.py +0 -0
  26. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/tests/test_core.py +0 -0
  27. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/tests/test_nnet_io_and_forward.py +0 -0
  28. {evonet-0.1.0.dev14 → evonet-0.1.0.dev16}/tests/test_recurrent_dynamics.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evonet
3
- Version: 0.1.0.dev14
3
+ Version: 0.1.0.dev16
4
4
  Summary: Evolvable neural network core for integration with EvoLib
5
5
  Author-email: EvoLib <evolib@dismail.de>
6
6
  License: MIT License
@@ -9,6 +9,7 @@ export interfaces.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
+ import random
12
13
  from pathlib import Path
13
14
  from typing import Literal, Optional
14
15
 
@@ -20,6 +21,7 @@ from evonet.connection import Connection
20
21
  from evonet.enums import ConnectionType, NeuronRole, RecurrentKind
21
22
  from evonet.layer import Layer
22
23
  from evonet.neuron import Neuron
24
+ from evonet.utils import connection_init_value
23
25
 
24
26
 
25
27
  class Nnet:
@@ -104,6 +106,8 @@ class Nnet:
104
106
  count: int = 1,
105
107
  connection_init: Literal["random", "zero", "near_zero", "none"] = "zero",
106
108
  recurrent: Optional[set[RecurrentKind]] = None,
109
+ connection_scope: Literal["adjacent", "crosslayer"] = "adjacent",
110
+ connection_density: float = 1.0,
107
111
  ) -> list[Neuron]:
108
112
  """
109
113
  Add one or more neurons to the network.
@@ -118,9 +122,16 @@ class Nnet:
118
122
  connection_init:
119
123
  "random" - connect with random weights (feedforward + recurrent)
120
124
  "zero" - connect with weight 0.0 (feedforward + recurrent)
121
- "near_zero" – connect with small random weights (-0.05, 0.05)
125
+ "near_zero" - connect with small random weights (-0.05, 0.05)
122
126
  "none" - do not create connections (feedforward + recurrent)
123
127
  recurrent: Optional recurrent connection types.
128
+ connection_scope:
129
+ - "adjacent": only connect to directly neighboring layers (default)
130
+ - "crosslayer": connect to all earlier and later
131
+ layers (feedforward only)
132
+ connection_density:
133
+ Fraction of possible connections that should actually be created.
134
+ Must be in (0, 1]. A value <1.0 randomly samples a subset.
124
135
 
125
136
  Returns:
126
137
  list[Neuron]: List of added neurons.
@@ -128,11 +139,12 @@ class Nnet:
128
139
  if layer_idx is None:
129
140
  layer_idx = len(self.layers) - 1 # Add neuron to last layer
130
141
 
131
- if layer_idx < 0:
132
- raise ValueError(f"Layer index must be >= 0 (got {layer_idx})")
133
- if layer_idx >= len(self.layers):
142
+ if layer_idx < 0 or layer_idx >= len(self.layers):
134
143
  raise ValueError(f"Layer index out of bounds: {layer_idx}")
135
144
 
145
+ if not (0.0 < connection_density <= 1.0):
146
+ raise ValueError("connection_density must be in (0, 1].")
147
+
136
148
  target_layer = self.layers[layer_idx]
137
149
  new_neurons: list[Neuron] = []
138
150
 
@@ -144,32 +156,49 @@ class Nnet:
144
156
  target_layer.neurons.append(neuron)
145
157
  new_neurons.append(neuron)
146
158
 
147
- # Weights based on init mode
148
- weight_map = {"random": None, "zero": 0.0, "none": None}
149
- if connection_init not in weight_map:
150
- raise ValueError(f"Invalid connection_init: {connection_init}")
151
- weight = weight_map[connection_init]
152
-
153
- skip_connections = connection_init == "none"
154
-
155
- # Connect to previous layer
156
- if not skip_connections and layer_idx > 0:
157
- for prev_neuron in self.layers[layer_idx - 1].neurons:
158
- for n in new_neurons:
159
- self.add_connection(prev_neuron, n, weight=weight)
160
-
161
- # Connect to next layer (hidden only)
162
- if (
163
- not skip_connections
164
- and role == NeuronRole.HIDDEN
165
- and layer_idx < len(self.layers) - 1
166
- ):
167
- for next_neuron in self.layers[layer_idx + 1].neurons:
168
- for n in new_neurons:
169
- self.add_connection(n, next_neuron, weight=weight)
159
+ # Skip connection creation if requested
160
+ if connection_init == "none":
161
+ return new_neurons
162
+
163
+ weight = connection_init_value(connection_init)
164
+ possible_pairs: list[tuple[Neuron, Neuron]] = []
165
+
166
+ # Connection logic
167
+ if connection_scope == "adjacent":
168
+ # Previous layer ---> new neurons
169
+ if layer_idx > 0:
170
+ for src in self.layers[layer_idx - 1].neurons:
171
+ for n in new_neurons:
172
+ possible_pairs.append((src, n))
173
+ # New neurons ---> next layer (only for hidden neurons)
174
+ if role == NeuronRole.HIDDEN and layer_idx < len(self.layers) - 1:
175
+ for dst in self.layers[layer_idx + 1].neurons:
176
+ for n in new_neurons:
177
+ possible_pairs.append((n, dst))
178
+
179
+ elif connection_scope == "crosslayer":
180
+ # All earlier layers ---> new neurons
181
+ for i in range(0, layer_idx):
182
+ for src in self.layers[i].neurons:
183
+ for n in new_neurons:
184
+ possible_pairs.append((src, n))
185
+ # New neurons ---> all later layers
186
+ for j in range(layer_idx + 1, len(self.layers)):
187
+ for dst in self.layers[j].neurons:
188
+ for n in new_neurons:
189
+ possible_pairs.append((n, dst))
190
+
191
+ # Density control: subsample of possible pairs
192
+ if connection_density < 1.0 and possible_pairs:
193
+ k = max(1, int(len(possible_pairs) * connection_density))
194
+ possible_pairs = random.sample(possible_pairs, k)
195
+
196
+ # Create connections
197
+ for src, dst in possible_pairs:
198
+ self.add_connection(src, dst, weight=weight)
170
199
 
171
200
  # Recurrent connections
172
- if recurrent and not skip_connections:
201
+ if recurrent:
173
202
  if RecurrentKind.DIRECT in recurrent:
174
203
  for n in new_neurons:
175
204
  if n.role != NeuronRole.HIDDEN:
@@ -19,6 +19,7 @@ from evonet.connection import Connection
19
19
  from evonet.core import Nnet
20
20
  from evonet.enums import ConnectionType, NeuronRole, RecurrentKind
21
21
  from evonet.neuron import Neuron
22
+ from evonet.utils import connection_init_value
22
23
 
23
24
  ALL_ACTIVATIONS = "all"
24
25
 
@@ -181,14 +182,7 @@ def add_random_connection(
181
182
  """
182
183
 
183
184
  # Weight initialization
184
- if connection_init == "zero":
185
- weight = 0.0
186
- elif connection_init == "random":
187
- weight = np.random.randn() * 0.5
188
- elif connection_init == "near_zero":
189
- weight = np.random.uniform(-0.05, 0.05)
190
- else:
191
- raise ValueError(f"Unknown init mode: {connection_init}")
185
+ weight = connection_init_value(connection_init)
192
186
 
193
187
  # normalize to set[RecurrentKind]
194
188
  kinds: set[RecurrentKind] = set()
@@ -0,0 +1,28 @@
1
+ # SPDX-License-Identifier: MIT
2
+
3
+ import numpy as np
4
+
5
+
6
+ def connection_init_value(mode: str) -> float | None:
7
+ """
8
+ Return an initial weight value for a new connection.
9
+
10
+ Args:
11
+ mode (str): Initialization mode. One of:
12
+ - "zero": weight = 0.0
13
+ - "near_zero": weight ~ U(-0.05, 0.05)
14
+ - "random": weight ~ N(0, 0.5)
15
+ - "none": no initialization (returns None)
16
+
17
+ Returns:
18
+ float | None: Initial connection weight or None.
19
+ """
20
+ if mode == "zero":
21
+ return 0.0
22
+ if mode == "near_zero":
23
+ return np.random.uniform(-0.05, 0.05)
24
+ if mode == "random":
25
+ return np.random.randn() * 0.5
26
+ if mode == "none":
27
+ return None
28
+ raise ValueError(f"Unknown connection_init mode: {mode}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evonet
3
- Version: 0.1.0.dev14
3
+ Version: 0.1.0.dev16
4
4
  Summary: Evolvable neural network core for integration with EvoLib
5
5
  Author-email: EvoLib <evolib@dismail.de>
6
6
  License: MIT License
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "evonet"
7
- version = "0.1.0.dev14"
7
+ version = "0.1.0.dev16"
8
8
  description = "Evolvable neural network core for integration with EvoLib"
9
9
  authors = [
10
10
  { name = "EvoLib", email = "evolib@dismail.de" }
File without changes
File without changes
File without changes
File without changes
File without changes