Repository navigation
Expand file tree
/
Copy pathutils.py
More file actions
370 lines (308 loc) · 11.7 KB
/
Copy pathutils.py
File metadata and controls
370 lines (308 loc) · 11.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
"""Utility functions for TNFR SDK.
This module provides helper functions for common operations,
analysis, and visualization support.
"""
from __future__ import annotations
import json
from pathlib import Path
from typing import Any
from .._coherence_validation import validate_structural_coherence
from .._exact_time import bounded_represented_real, finite_represented_real
from ..metrics.common import finite_mean, finite_population_std
from ..utils.io import _reject_duplicate_json_keys as _reject_duplicate_keys
from ..utils.io import json_dumps, json_loads, safe_write
__all__ = [
"compare_networks",
"compute_network_statistics",
"export_to_json",
"import_from_json",
]
def compare_networks(
networks: dict[str, Any],
metrics: list[str] | None = None,
) -> dict[str, dict[str, float]]:
"""Compare metrics across multiple networks.
Selected scalar observations are re-admitted before use. An invalid
coherence sample cannot enter the comparison as a measured value.
Parameters
----------
networks : dict[str, NetworkResults]
Dictionary mapping network names to their results.
metrics : list[str], optional
list of metrics to compare. If None, compares all available.
Options: 'coherence', 'avg_si', 'avg_delta_nfr', 'node_count'
Returns
-------
dict[str, dict[str, float]]
Comparison table with metrics for each network.
Examples
--------
>>> from tnfr.sdk import TNFRExperimentBuilder
>>> comparison = TNFRExperimentBuilder.compare_topologies(30, 5)
>>> from tnfr.sdk.utils import compare_networks
>>> stats = compare_networks(comparison)
"""
if metrics is None:
metrics = ["coherence", "avg_si", "avg_delta_nfr", "node_count"]
comparison: dict[str, dict[str, float]] = {}
for name, results in networks.items():
comparison[name] = {}
if "coherence" in metrics:
comparison[name]["coherence"] = validate_structural_coherence(
results.coherence
)
if "avg_si" in metrics:
si_values = list(results.sense_indices.values())
comparison[name]["avg_si"] = finite_mean(si_values, name="Si")
if "avg_delta_nfr" in metrics:
dnfr_values = list(results.delta_nfr.values())
comparison[name]["avg_delta_nfr"] = finite_mean(
dnfr_values, name="DeltaNFR"
)
if "node_count" in metrics:
comparison[name]["node_count"] = len(results.sense_indices)
return comparison
def compute_network_statistics(results: Any) -> dict[str, float]:
"""Compute extended statistics for a network.
Present scalar observations use shared represented-real admission.
Coherence is in [0, 1] and average capacity is nonnegative; missing optional
averages remain omitted. Statistics consume the admitted representation,
including extrema, without changing the supplied result.
Parameters
----------
results : NetworkResults
Results from network measurement.
Returns
-------
dict[str, float]
Dictionary of computed statistics.
Examples
--------
>>> from tnfr.sdk import TNFRNetwork
>>> network = TNFRNetwork().add_nodes(10).connect_nodes(0.3)
>>> results = network.apply_sequence("basic_activation").measure()
>>> from tnfr.sdk.utils import compute_network_statistics
>>> stats = compute_network_statistics(results)
"""
si_values = [
finite_represented_real(value, "Si")[0]
for value in results.sense_indices.values()
]
dnfr_values = [
finite_represented_real(value, "DeltaNFR")[0]
for value in results.delta_nfr.values()
]
stats = {
"coherence": validate_structural_coherence(results.coherence),
"node_count": len(si_values),
}
if si_values:
stats["avg_si"] = finite_mean(si_values, name="Si")
stats["min_si"] = min(si_values)
stats["max_si"] = max(si_values)
stats["std_si"] = finite_population_std(si_values, name="Si")
if dnfr_values:
stats["avg_delta_nfr"] = finite_mean(dnfr_values, name="DeltaNFR")
stats["min_delta_nfr"] = min(dnfr_values)
stats["max_delta_nfr"] = max(dnfr_values)
stats["std_delta_nfr"] = finite_population_std(dnfr_values, name="DeltaNFR")
if results.avg_vf is not None:
stats["avg_vf"] = bounded_represented_real(results.avg_vf, "avg_vf", minimum=0)[
0
]
if results.avg_phase is not None:
stats["avg_phase"] = finite_represented_real(results.avg_phase, "avg_phase")[0]
return stats
def export_to_json(
network_data: Any,
filepath: Path | str,
indent: int = 2,
) -> None:
"""Export network data without overwriting reports on serialization errors.
Non-string keys retain the standard JSON encoder's conversions, but two
distinct keys that become the same JSON object name are rejected, including
inside nested objects. Existing Python-dictionary key loss cannot be undone.
Parameters
----------
network_data : TNFRNetwork or NetworkResults or dict
Network data to export. If TNFRNetwork, calls export_to_dict().
If NetworkResults, calls to_dict(). Otherwise uses dict directly.
filepath : Path or str
Path where JSON file should be saved.
indent : int, default=2
JSON indentation level for readability.
Examples
--------
>>> from tnfr.sdk import TNFRNetwork
>>> from tnfr.sdk.utils import export_to_json
>>> network = TNFRNetwork().add_nodes(10).connect_nodes(0.3)
>>> export_to_json(network, "network.json")
"""
filepath = Path(filepath)
# Convert to dict if needed
if hasattr(network_data, "export_to_dict"):
data = network_data.export_to_dict()
elif hasattr(network_data, "to_dict"):
data = network_data.to_dict()
else:
data = network_data
# Serialize before opening a destination, then reuse the shared atomic
# writer. Failed serialization cannot truncate an existing report.
payload = json_dumps(
data,
indent=indent,
ensure_ascii=False,
allow_nan=False,
separators=(",", ": ") if indent is not None else (", ", ": "),
)
# Check actual encoded names recursively instead of duplicating the
# encoder's int/float/bool/None conversion rules. The hook need not retain
# a second decoded object tree, and validation precedes any file writes.
json.loads(payload, object_pairs_hook=_reject_duplicate_keys)
safe_write(filepath, lambda stream: stream.write(payload))
def import_from_json(filepath: Path | str) -> Any:
"""Import JSON data without reconstructing a live network.
Duplicate object names and nonfinite numbers are rejected at every depth.
Nonzero fractional literals that underflow to binary64 zero are rejected;
representable fractional numbers retain standard JSON float rounding.
Integer literals retain Python's integer representation. Decoding does not
authenticate report provenance or validate a study recipe's model fields.
Parameters
----------
filepath : Path or str
Path to JSON file to load.
Returns
-------
Any
Decoded JSON data; object, array and scalar roots are supported.
Examples
--------
>>> from tnfr.sdk.utils import import_from_json
>>> data = import_from_json("network.json")
>>> print(data['metadata']['nodes'])
"""
return json_loads(Path(filepath).read_text(encoding="utf-8"))
def format_comparison_table(
comparison: dict[str, dict[str, float]],
metrics: list[str] | None = None,
) -> str:
"""Format a comparison without replacing unavailable measurements by zero.
Parameters
----------
comparison : dict[str, dict[str, float]]
Comparison data from compare_networks().
metrics : list[str], optional
Metrics to include in table. If None, uses all available.
Returns
-------
str
Formatted table string.
Examples
--------
>>> from tnfr.sdk import TNFRExperimentBuilder
>>> from tnfr.sdk.utils import compare_networks, format_comparison_table
>>> results = TNFRExperimentBuilder.compare_topologies(20, 3)
>>> comp = compare_networks(results)
>>> print(format_comparison_table(comp))
"""
if not comparison:
return "No networks to compare"
# Get all available metrics if not specified
if metrics is None:
metrics = list(dict.fromkeys(key for row in comparison.values() for key in row))
# Build table
lines = []
# Header
header = f"{'Network':<20} " + " ".join(f"{m:>12}" for m in metrics)
lines.append(header)
lines.append("-" * len(header))
# Data rows
for network_name, data in sorted(comparison.items()):
formatted = []
for metric in metrics:
try:
value = finite_represented_real(data.get(metric), metric)[0]
except (TypeError, ValueError):
formatted.append(f"{'unavailable':>12}")
else:
formatted.append(f"{value:>12.3f}")
values = " ".join(formatted)
lines.append(f"{network_name:<20} {values}")
return "\n".join(lines)
def suggest_sequence_for_goal(goal: str) -> tuple[str, str]:
"""Suggest operator sequence for a specific goal.
Parameters
----------
goal : str
Description of the goal. Options:
- "activation", "start", "initialize"
- "stabilize", "consolidate"
- "explore", "diverge"
- "synchronize", "align", "coordinate"
- "mutate", "innovate", "create"
Returns
-------
tuple[str, str]
(sequence_name, description) tuple.
Examples
--------
>>> from tnfr.sdk.utils import suggest_sequence_for_goal
>>> seq, desc = suggest_sequence_for_goal("stabilize")
>>> print(f"Use: {seq}")
>>> print(f"Description: {desc}")
"""
goal_lower = goal.lower()
suggestions = {
"activation": (
"basic_activation",
"Initiates network with emission, reception, coherence, and resonance",
),
"start": (
"basic_activation",
"Initiates network with emission, reception, coherence, and resonance",
),
"initialize": (
"basic_activation",
"Initiates network with emission, reception, coherence, and resonance",
),
"stabilize": (
"stabilization",
"Establishes and maintains coherent structure with recursivity",
),
"consolidate": (
"consolidation",
"Consolidates structure with recursive coherence and silence",
),
"explore": (
"exploration",
"Explores phase space with dissonance and transition",
),
"diverge": (
"exploration",
"Explores phase space with dissonance and transition",
),
"synchronize": (
"network_sync",
"Synchronizes nodes through coupling and resonance",
),
"align": ("network_sync", "Synchronizes nodes through coupling and resonance"),
"coordinate": (
"network_sync",
"Synchronizes nodes through coupling and resonance",
),
"mutate": (
"creative_mutation",
"Generates variation through dissonance and mutation",
),
"innovate": (
"creative_mutation",
"Generates variation through dissonance and mutation",
),
"create": (
"creative_mutation",
"Generates variation through dissonance and mutation",
),
}
return suggestions.get(
goal_lower, ("basic_activation", "Default: basic activation sequence")
)