evonet 0.1.0.dev7__tar.gz → 0.1.0.dev9__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 (25) hide show
  1. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/PKG-INFO +1 -1
  2. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/activation.py +1 -0
  3. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/core.py +67 -30
  4. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/mutation.py +2 -2
  5. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet.egg-info/PKG-INFO +1 -1
  6. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/pyproject.toml +1 -1
  7. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/tests/test_core.py +4 -4
  8. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/tests/test_nnet_io_and_forward.py +5 -5
  9. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/tests/test_recurrent_dynamics.py +3 -3
  10. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/LICENSE +0 -0
  11. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/README.md +0 -0
  12. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/__init__.py +0 -0
  13. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/connection.py +0 -0
  14. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/enums.py +0 -0
  15. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/io.py +0 -0
  16. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/layer.py +0 -0
  17. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/neuron.py +0 -0
  18. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/utils.py +0 -0
  19. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet/visualize.py +0 -0
  20. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet.egg-info/SOURCES.txt +0 -0
  21. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet.egg-info/dependency_links.txt +0 -0
  22. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet.egg-info/requires.txt +0 -0
  23. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/evonet.egg-info/top_level.txt +0 -0
  24. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/setup.cfg +0 -0
  25. {evonet-0.1.0.dev7 → evonet-0.1.0.dev9}/tests/test_activation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evonet
3
- Version: 0.1.0.dev7
3
+ Version: 0.1.0.dev9
4
4
  Summary: Evolvable neural network core for integration with EvoLib
5
5
  Author-email: EvoLib <evolib@dismail.de>
6
6
  License: MIT License
@@ -203,4 +203,5 @@ ACTIVATIONS: dict[str, Callable] = {
203
203
  "softplus": softplus,
204
204
  "softsign": softsign,
205
205
  "hard_sigmoid": hard_sigmoid,
206
+ "softmax": softmax,
206
207
  }
@@ -9,11 +9,12 @@ export interfaces.
9
9
 
10
10
  from __future__ import annotations
11
11
 
12
- from typing import Optional
12
+ from typing import Literal, Optional
13
13
 
14
14
  import graphviz
15
15
  import numpy as np
16
16
 
17
+ from evonet.activation import softmax as softmax_vec
17
18
  from evonet.connection import Connection
18
19
  from evonet.enums import ConnectionType, NeuronRole, RecurrentKind
19
20
  from evonet.layer import Layer
@@ -100,31 +101,33 @@ class Nnet:
100
101
  label: str = "",
101
102
  role: NeuronRole = NeuronRole.HIDDEN,
102
103
  count: int = 1,
103
- connect_layer: bool = True,
104
+ connection_init: Literal["random", "zero", "none"] = "random",
104
105
  recurrent: Optional[set[RecurrentKind]] = None,
105
106
  ) -> list[Neuron]:
106
107
  """
107
108
  Add one or more neurons to the network.
108
109
 
109
110
  Args:
110
- layer_idx (int | None): Target layer index. Defaults to last layer.
111
- activation (str): Activation function name.
112
- bias (float): Initial bias value.
113
- label (str): Optional label.
114
- role (NeuronRole): Role of the neuron (INPUT, HIDDEN, OUTPUT).
115
- count (int): Number of neurons to add (default: 1).
116
- connect_layer (bool): If True, auto-connect to adjacent layers.
117
- recurrent (set[RecurrentKind] | None): Optional recurrent connection types.
111
+ layer_idx: Target layer index. Defaults to last layer.
112
+ activation: Activation function name.
113
+ bias: Initial bias value.
114
+ label: Optional label.
115
+ role: Role of the neuron (INPUT, HIDDEN, OUTPUT).
116
+ count: Number of neurons to add (default: 1).
117
+ connection_init:
118
+ "random" connect with random weights (feedforward + recurrent)
119
+ "zero" – connect with weight 0.0 (feedforward + recurrent)
120
+ "none" – do not create connections (feedforward + recurrent)
121
+ recurrent: Optional recurrent connection types.
118
122
 
119
123
  Returns:
120
124
  list[Neuron]: List of added neurons.
121
125
  """
122
-
123
126
  if layer_idx is None:
124
127
  layer_idx = len(self.layers) - 1 # Add neuron to last layer
125
128
 
126
129
  if layer_idx < 0:
127
- raise ValueError(f"Expected positiv layerindex: got {layer_idx}")
130
+ raise ValueError(f"Layer index must be >= 0 (got {layer_idx})")
128
131
  if layer_idx >= len(self.layers):
129
132
  raise ValueError(f"Layer index out of bounds: {layer_idx}")
130
133
 
@@ -132,46 +135,56 @@ class Nnet:
132
135
  new_neurons: list[Neuron] = []
133
136
 
134
137
  # Create neurons without connections
135
- for i in range(count):
138
+ for _ in range(count):
136
139
  neuron = Neuron(activation=activation, bias=bias)
137
140
  neuron.role = role
138
141
  neuron.label = label
139
142
  target_layer.neurons.append(neuron)
140
143
  new_neurons.append(neuron)
141
144
 
142
- # Connect to previous layer (feedforward)
143
- if connect_layer and layer_idx > 0:
145
+ # Weights based on init mode
146
+ weight_map = {"random": None, "zero": 0.0, "none": None}
147
+ if connection_init not in weight_map:
148
+ raise ValueError(f"Invalid connection_init: {connection_init}")
149
+ weight = weight_map[connection_init]
150
+
151
+ skip_connections = connection_init == "none"
152
+
153
+ # Connect to previous layer
154
+ if not skip_connections and layer_idx > 0:
144
155
  for prev_neuron in self.layers[layer_idx - 1].neurons:
145
156
  for n in new_neurons:
146
- self.add_connection(prev_neuron, n)
157
+ self.add_connection(prev_neuron, n, weight=weight)
147
158
 
148
- # Connect to next layer (feedforward)
159
+ # Connect to next layer (hidden only)
149
160
  if (
150
- connect_layer
161
+ not skip_connections
151
162
  and role == NeuronRole.HIDDEN
152
163
  and layer_idx < len(self.layers) - 1
153
164
  ):
154
-
155
165
  for next_neuron in self.layers[layer_idx + 1].neurons:
156
166
  for n in new_neurons:
157
- self.add_connection(n, next_neuron)
167
+ self.add_connection(n, next_neuron, weight=weight)
158
168
 
159
169
  # Recurrent connections
160
- if recurrent:
170
+ if recurrent and not skip_connections:
161
171
  if RecurrentKind.DIRECT in recurrent:
162
172
  for n in new_neurons:
163
173
  if n.role == NeuronRole.HIDDEN:
164
- self.add_connection(n, n, conn_type=ConnectionType.RECURRENT)
174
+ self.add_connection(
175
+ n, n, weight=weight, conn_type=ConnectionType.RECURRENT
176
+ )
165
177
 
166
178
  if RecurrentKind.LATERAL in recurrent:
167
- # All neurons in this layer (old + new)
168
179
  full_layer = list(self.layers[layer_idx].neurons)
169
-
170
180
  for src in full_layer:
171
181
  for dst in new_neurons:
172
182
  if src is not dst:
173
183
  self.add_connection(
174
- src, dst, conn_type=ConnectionType.RECURRENT
184
+ src,
185
+ dst,
186
+ weight=weight,
187
+ conn_type=ConnectionType.RECURRENT,
175
188
  )
176
189
 
177
190
  if RecurrentKind.INDIRECT in recurrent:
@@ -179,13 +192,19 @@ class Nnet:
179
192
  for lower_layer in self.layers[1:layer_idx]:
180
193
  for dst in lower_layer.neurons:
181
194
  self.add_connection(
182
- src, dst, conn_type=ConnectionType.RECURRENT
195
+ src,
196
+ dst,
197
+ weight=weight,
198
+ conn_type=ConnectionType.RECURRENT,
183
199
  )
184
200
  for higher_layer in self.layers[layer_idx + 1 :]:
185
201
  for src in higher_layer.neurons:
186
202
  for dst in new_neurons:
187
203
  self.add_connection(
188
- src, dst, conn_type=ConnectionType.RECURRENT
204
+ src,
205
+ dst,
206
+ weight=weight,
207
+ conn_type=ConnectionType.RECURRENT,
189
208
  )
190
209
 
191
210
  return new_neurons
@@ -256,10 +275,28 @@ class Nnet:
256
275
 
257
276
  # Feed-forward by layers: activate first, then propagate non-recurrent edges
258
277
  for layer in self.layers:
278
+
279
+ # Apply softmax to all neurons with activation_name == "softmax"
280
+ softmax_neurons = [
281
+ n for n in layer.neurons if n.activation_name == "softmax"
282
+ ]
283
+ if softmax_neurons:
284
+ if len(softmax_neurons) >= 2:
285
+ # Normal softmax behaviour
286
+ totals = [n.input + n.bias for n in softmax_neurons]
287
+ probabilities = softmax_vec(totals)
288
+ for n, p in zip(softmax_neurons, probabilities):
289
+ n.output = float(p)
290
+ else:
291
+ # Fallback: single softmax neuron acts like identity
292
+ n = softmax_neurons[0]
293
+ n.output = n.input + n.bias
294
+
259
295
  # Activate all neurons in this layer
260
296
  for n in layer.neurons:
261
- total = n.input + n.bias
262
- n.output = n.activation(total)
297
+ if n.activation_name != "softmax":
298
+ total = n.input + n.bias
299
+ n.output = n.activation(total)
263
300
 
264
301
  # Propagate to targets (exclude recurrent edges)
265
302
  for n in layer.neurons:
@@ -326,7 +363,7 @@ class Nnet:
326
363
  ratio="fill",
327
364
  splines="spline",
328
365
  size="6.68,5!",
329
- dpi="200",
366
+ dpi="600",
330
367
  )
331
368
  dot.node_attr.update(
332
369
  shape="circle", style="filled", fixedsize="shape", width="1.8"
@@ -270,7 +270,7 @@ def add_random_neuron(net: Nnet, activations: list[str] | None = None) -> None:
270
270
  layer_idx=net.layers.index(layer),
271
271
  activation=random_function_name(activations),
272
272
  role=NeuronRole.HIDDEN,
273
- connect_layer=True,
273
+ connection_init="random",
274
274
  )
275
275
 
276
276
 
@@ -332,7 +332,7 @@ def split_connection(net: Nnet, activation: str = "tanh", noise: float = 0.1) ->
332
332
  layer_idx=insert_idx,
333
333
  role=NeuronRole.HIDDEN,
334
334
  activation=activation,
335
- connect_layer=False,
335
+ connection_init="none",
336
336
  )[0]
337
337
 
338
338
  # Set new connections
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: evonet
3
- Version: 0.1.0.dev7
3
+ Version: 0.1.0.dev9
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.dev7"
7
+ version = "0.1.0.dev9"
8
8
  description = "Evolvable neural network core for integration with EvoLib"
9
9
  authors = [
10
10
  { name = "EvoLib", email = "evolib@dismail.de" }
@@ -13,14 +13,14 @@ def test_forward_pass_identity() -> None:
13
13
  activation="linear",
14
14
  role=NeuronRole.INPUT,
15
15
  label="in",
16
- connect_layer=False,
16
+ connection_init="none",
17
17
  )
18
18
  net.add_neuron(
19
19
  layer_idx=1,
20
20
  activation="linear",
21
21
  role=NeuronRole.OUTPUT,
22
22
  label="out",
23
- connect_layer=False,
23
+ connection_init="none",
24
24
  )
25
25
 
26
26
  src = net.layers[0].neurons[0]
@@ -49,7 +49,7 @@ def test_forward_pass_with_bias() -> None:
49
49
  activation="linear",
50
50
  role=NeuronRole.INPUT,
51
51
  label="in",
52
- connect_layer=False,
52
+ connection_init="none",
53
53
  )
54
54
  net.add_neuron(
55
55
  layer_idx=1,
@@ -57,7 +57,7 @@ def test_forward_pass_with_bias() -> None:
57
57
  role=NeuronRole.OUTPUT,
58
58
  bias=0.5,
59
59
  label="out",
60
- connect_layer=False,
60
+ connection_init="none",
61
61
  )
62
62
 
63
63
  src = net.layers[0].neurons[0]
@@ -29,21 +29,21 @@ def build_simple_ff_net() -> Nnet:
29
29
  role=NeuronRole.INPUT,
30
30
  activation="linear",
31
31
  count=2,
32
- connect_layer=False,
32
+ connection_init="none",
33
33
  )
34
34
  net.add_neuron(
35
35
  layer_idx=1,
36
36
  role=NeuronRole.HIDDEN,
37
37
  activation="linear",
38
38
  count=3,
39
- connect_layer=True,
39
+ connection_init="random",
40
40
  )
41
41
  net.add_neuron(
42
42
  layer_idx=2,
43
43
  role=NeuronRole.OUTPUT,
44
44
  activation="linear",
45
45
  count=1,
46
- connect_layer=True,
46
+ connection_init="random",
47
47
  )
48
48
  return net
49
49
 
@@ -117,14 +117,14 @@ def test_forward_sanity_linear_1x1() -> None:
117
117
  role=NeuronRole.INPUT,
118
118
  activation="linear",
119
119
  count=1,
120
- connect_layer=False,
120
+ connection_init="none",
121
121
  )
122
122
  net.add_neuron(
123
123
  layer_idx=1,
124
124
  role=NeuronRole.OUTPUT,
125
125
  activation="linear",
126
126
  count=1,
127
- connect_layer=True,
127
+ connection_init="random",
128
128
  )
129
129
 
130
130
  # There should be exactly one connection L0(0) -> L1(0). Force known params:
@@ -15,17 +15,17 @@ def _mk_linear_ih_h_o() -> tuple[Nnet, Neuron, Neuron, Neuron]:
15
15
  net.add_layer(3) # input, hidden, output
16
16
 
17
17
  neurons: list[Neuron] = net.add_neuron(
18
- layer_idx=0, role=NeuronRole.INPUT, activation="linear", connect_layer=False
18
+ layer_idx=0, role=NeuronRole.INPUT, activation="linear", connection_init="none"
19
19
  )
20
20
  n_in = neurons[0]
21
21
 
22
22
  neurons = net.add_neuron(
23
- layer_idx=1, role=NeuronRole.HIDDEN, activation="linear", connect_layer=False
23
+ layer_idx=1, role=NeuronRole.HIDDEN, activation="linear", connection_init="none"
24
24
  )
25
25
  n_hid = neurons[0]
26
26
 
27
27
  neurons = net.add_neuron(
28
- layer_idx=2, role=NeuronRole.OUTPUT, activation="linear", connect_layer=False
28
+ layer_idx=2, role=NeuronRole.OUTPUT, activation="linear", connection_init="none"
29
29
  )
30
30
  n_out = neurons[0]
31
31
 
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes