complexeva 0.1.5__tar.gz → 0.1.6__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.
- {complexeva-0.1.5 → complexeva-0.1.6}/PKG-INFO +1 -1
- complexeva-0.1.6/complexeva/measures/__init__.py +6 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva/measures/validate.py +13 -7
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva.egg-info/PKG-INFO +1 -1
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva.egg-info/SOURCES.txt +0 -4
- {complexeva-0.1.5 → complexeva-0.1.6}/setup.py +1 -1
- complexeva-0.1.5/complexeva/measures/__init__.py +0 -5
- complexeva-0.1.5/complexeva/measures/cbb_matrix.py +0 -134
- complexeva-0.1.5/complexeva/measures/cbb_networkx.py +0 -288
- complexeva-0.1.5/complexeva/measures/co_activation.py +0 -100
- complexeva-0.1.5/complexeva/measures/higuchi.py +0 -91
- {complexeva-0.1.5 → complexeva-0.1.6}/README.md +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva/__init__.py +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva/dummies/__init__.py +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva/dummies/matrix.py +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva/dummies/networks.py +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva/utils/__init__.py +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva/utils/convert_image_matrix.py +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva/utils/convert_matrix_network.py +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva/utils/device.py +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva.egg-info/dependency_links.txt +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/complexeva.egg-info/top_level.txt +0 -0
- {complexeva-0.1.5 → complexeva-0.1.6}/setup.cfg +0 -0
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
from .dimension_1.lempel_ziv import lempel_ziv
|
|
2
|
+
from .dimension_1.higuchi import hfd_matlab_equivalent, hfd_pyeeg
|
|
3
|
+
from .dimension_2.cbb_matrix import FractalHandlerMatrix
|
|
4
|
+
from .network_space.cbb_networkx import FractalHandlerNetworkX
|
|
5
|
+
|
|
6
|
+
__all__ = ["lempel_ziv", "hfd_matlab_equivalent", "hfd_pyeeg", "FractalHandlerMatrix", "FractalHandlerNetworkX"]
|
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
#!/Users/donyin/miniconda3/envs/rotation-1/bin/python
|
|
2
2
|
|
|
3
|
+
"""
|
|
4
|
+
- validating measures of complexity is actually working using synthetic data on various Hurst exponents
|
|
5
|
+
"""
|
|
3
6
|
|
|
4
7
|
import numpy
|
|
5
8
|
from scipy.fft import fft, ifft
|
|
6
9
|
import matplotlib.pyplot as plt
|
|
7
10
|
from donware import inspect_package
|
|
8
|
-
from complexeva.measures.higuchi import hfd_pyeeg, hfd_matlab_equivalent
|
|
11
|
+
from complexeva.measures.dimension_1.higuchi import hfd_pyeeg, hfd_matlab_equivalent
|
|
12
|
+
from complexeva.measures.dimension_1.lempel_ziv import lempel_ziv_v1, lempel_ziv_v2, lempel_ziv, lempel_ziv_v4
|
|
9
13
|
|
|
10
14
|
|
|
11
|
-
class
|
|
12
|
-
def __init__(self, funcs,
|
|
13
|
-
self.funcs, self.
|
|
15
|
+
class ComplexityMeasuresValidator2D:
|
|
16
|
+
def __init__(self, funcs, timeseries_len=1000, num_hurst=200):
|
|
17
|
+
self.funcs, self.num_hurst, self.timeseries_len = funcs, num_hurst, timeseries_len
|
|
14
18
|
|
|
15
19
|
def fbm(self, hurst):
|
|
16
20
|
"""
|
|
@@ -52,7 +56,7 @@ class HiguchiMethodValidate:
|
|
|
52
56
|
hfd_results = []
|
|
53
57
|
|
|
54
58
|
for func in self.funcs:
|
|
55
|
-
hfd_results.append([func(series
|
|
59
|
+
hfd_results.append([func(series) for series in fBm_series])
|
|
56
60
|
|
|
57
61
|
# ---- plotting ----
|
|
58
62
|
fig, axs = plt.subplots(3, 1, figsize=(9, 10))
|
|
@@ -91,6 +95,8 @@ class HiguchiMethodValidate:
|
|
|
91
95
|
|
|
92
96
|
|
|
93
97
|
if __name__ == "__main__":
|
|
94
|
-
|
|
95
|
-
validator
|
|
98
|
+
validator = ComplexityMeasuresValidator2D([hfd_pyeeg, hfd_matlab_equivalent])
|
|
99
|
+
validator.plot_hfd_vs_hurst()
|
|
100
|
+
|
|
101
|
+
validator = ComplexityMeasuresValidator2D([lempel_ziv_v1, lempel_ziv_v2, lempel_ziv, lempel_ziv_v4])
|
|
96
102
|
validator.plot_hfd_vs_hurst()
|
|
@@ -9,10 +9,6 @@ complexeva/dummies/__init__.py
|
|
|
9
9
|
complexeva/dummies/matrix.py
|
|
10
10
|
complexeva/dummies/networks.py
|
|
11
11
|
complexeva/measures/__init__.py
|
|
12
|
-
complexeva/measures/cbb_matrix.py
|
|
13
|
-
complexeva/measures/cbb_networkx.py
|
|
14
|
-
complexeva/measures/co_activation.py
|
|
15
|
-
complexeva/measures/higuchi.py
|
|
16
12
|
complexeva/measures/validate.py
|
|
17
13
|
complexeva/utils/__init__.py
|
|
18
14
|
complexeva/utils/convert_image_matrix.py
|
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
import numpy as np
|
|
2
|
-
import matplotlib.pyplot as plt
|
|
3
|
-
from matplotlib import animation
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class FractalHandlerMatrix:
|
|
7
|
-
"""
|
|
8
|
-
Handles the fractal dimension calculation of a binary matrix using an improved Compact Box-Burning algorithm.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
def __init__(self, matrix: np.ndarray):
|
|
12
|
-
self.matrix = matrix
|
|
13
|
-
|
|
14
|
-
def compute_fractal_dimension(self, save_plot_as=None, save_animated_as=None):
|
|
15
|
-
"""
|
|
16
|
-
Calculate the fractal dimension of the matrix using the improved Compact Box-Burning algorithm.
|
|
17
|
-
|
|
18
|
-
Parameters:
|
|
19
|
-
save_plot_as (str, optional): Path to save the static plot showing fractal dimension calculation
|
|
20
|
-
save_animated_as (str, optional): Path to save the animated GIF showing box counting process
|
|
21
|
-
|
|
22
|
-
Returns:
|
|
23
|
-
float: The fractal dimension of the matrix.
|
|
24
|
-
"""
|
|
25
|
-
assert np.all((self.matrix == 0) | (self.matrix == 1)), "> warning: Matrix should be binary (0s and 1s only)"
|
|
26
|
-
binary_matrix = self.matrix.astype(np.uint8)
|
|
27
|
-
min_dim = min(binary_matrix.shape)
|
|
28
|
-
max_scale = 2 ** int(np.floor(np.log2(min_dim)))
|
|
29
|
-
|
|
30
|
-
num_scales = int(np.floor(np.log2(max_scale))) * 10
|
|
31
|
-
scales = np.unique(np.floor(np.logspace(0, np.log2(max_scale), num=num_scales, base=2))).astype(int)
|
|
32
|
-
scales = scales[scales >= 1]
|
|
33
|
-
|
|
34
|
-
box_counts = []
|
|
35
|
-
padded_matrices = []
|
|
36
|
-
|
|
37
|
-
# Animation setup if needed
|
|
38
|
-
if save_animated_as:
|
|
39
|
-
fig, ax = plt.subplots(figsize=(8, 8))
|
|
40
|
-
|
|
41
|
-
for scale in scales:
|
|
42
|
-
padded_size = (
|
|
43
|
-
int(np.ceil(binary_matrix.shape[0] / scale)) * scale,
|
|
44
|
-
int(np.ceil(binary_matrix.shape[1] / scale)) * scale,
|
|
45
|
-
)
|
|
46
|
-
padded_matrix = np.zeros(padded_size, dtype=np.uint8)
|
|
47
|
-
padded_matrix[: binary_matrix.shape[0], : binary_matrix.shape[1]] = binary_matrix
|
|
48
|
-
|
|
49
|
-
if save_animated_as:
|
|
50
|
-
padded_matrices.append((padded_matrix, scale))
|
|
51
|
-
|
|
52
|
-
reshaped = padded_matrix.reshape(padded_size[0] // scale, scale, padded_size[1] // scale, scale)
|
|
53
|
-
summed = reshaped.sum(axis=(1, 3))
|
|
54
|
-
occupied_boxes = np.count_nonzero(summed)
|
|
55
|
-
box_counts.append(occupied_boxes)
|
|
56
|
-
|
|
57
|
-
# calculate fractal dimension
|
|
58
|
-
scales = np.array(scales)
|
|
59
|
-
box_counts = np.array(box_counts)
|
|
60
|
-
nonzero = box_counts > 0
|
|
61
|
-
scales = scales[nonzero]
|
|
62
|
-
box_counts = box_counts[nonzero]
|
|
63
|
-
|
|
64
|
-
log_scales = np.log(1 / scales)
|
|
65
|
-
log_box_counts = np.log(box_counts)
|
|
66
|
-
|
|
67
|
-
coeffs = np.polyfit(log_scales, log_box_counts, 1)
|
|
68
|
-
fractal_dimension = coeffs[0]
|
|
69
|
-
|
|
70
|
-
# save static plot if requested
|
|
71
|
-
if save_plot_as:
|
|
72
|
-
save_plot_as = save_plot_as if save_plot_as.endswith(".png") else save_plot_as + ".png"
|
|
73
|
-
plt.figure(figsize=(8, 6))
|
|
74
|
-
plt.plot(log_scales, log_box_counts, "o-", label="Data")
|
|
75
|
-
plt.plot(
|
|
76
|
-
log_scales,
|
|
77
|
-
np.polyval(coeffs, log_scales),
|
|
78
|
-
"r--",
|
|
79
|
-
label=f"Fit (D = {fractal_dimension:.4f})",
|
|
80
|
-
)
|
|
81
|
-
plt.xlabel("log(1/ε)")
|
|
82
|
-
plt.ylabel("log(N(ε))")
|
|
83
|
-
plt.title("Fractal Dimension via Compact Box-Burning")
|
|
84
|
-
plt.legend()
|
|
85
|
-
plt.grid(True)
|
|
86
|
-
plt.tight_layout()
|
|
87
|
-
plt.savefig(save_plot_as, dpi=420, bbox_inches="tight")
|
|
88
|
-
plt.close()
|
|
89
|
-
|
|
90
|
-
# Create and save animation if requested
|
|
91
|
-
if save_animated_as:
|
|
92
|
-
|
|
93
|
-
def init():
|
|
94
|
-
ax.imshow(binary_matrix, cmap="binary", interpolation="nearest")
|
|
95
|
-
ax.axis("off")
|
|
96
|
-
return []
|
|
97
|
-
|
|
98
|
-
def update(frame):
|
|
99
|
-
padded_matrix, scale = padded_matrices[frame]
|
|
100
|
-
ax.clear()
|
|
101
|
-
ax.imshow(padded_matrix, cmap="binary", interpolation="nearest")
|
|
102
|
-
ax.set_title(f"Scale: {scale} pixels, Boxes: {box_counts[frame]}")
|
|
103
|
-
ax.axis("off")
|
|
104
|
-
|
|
105
|
-
# Overlay red boxes on occupied regions
|
|
106
|
-
for y in range(0, padded_matrix.shape[0], scale):
|
|
107
|
-
for x in range(0, padded_matrix.shape[1], scale):
|
|
108
|
-
if np.any(padded_matrix[y : y + scale, x : x + scale]):
|
|
109
|
-
rect = plt.Rectangle(
|
|
110
|
-
(x - 0.5, y - 0.5),
|
|
111
|
-
scale,
|
|
112
|
-
scale,
|
|
113
|
-
edgecolor="red",
|
|
114
|
-
facecolor="none",
|
|
115
|
-
lw=1,
|
|
116
|
-
)
|
|
117
|
-
ax.add_patch(rect)
|
|
118
|
-
return []
|
|
119
|
-
|
|
120
|
-
ani = animation.FuncAnimation(
|
|
121
|
-
fig,
|
|
122
|
-
update,
|
|
123
|
-
frames=len(scales),
|
|
124
|
-
init_func=init,
|
|
125
|
-
blit=False,
|
|
126
|
-
repeat=False,
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
if not save_animated_as.endswith((".png", ".gif")):
|
|
130
|
-
save_animated_as += ".gif"
|
|
131
|
-
ani.save(save_animated_as.replace(".png", ".gif"), writer="pillow", fps=2)
|
|
132
|
-
plt.close(fig)
|
|
133
|
-
|
|
134
|
-
return fractal_dimension
|
|
@@ -1,288 +0,0 @@
|
|
|
1
|
-
#!/Users/donyin/miniconda3/bin/python
|
|
2
|
-
import random
|
|
3
|
-
from PIL import Image
|
|
4
|
-
import numpy as np
|
|
5
|
-
import networkx as nx
|
|
6
|
-
from tqdm import tqdm
|
|
7
|
-
from scipy import stats
|
|
8
|
-
import matplotlib.pyplot as plt
|
|
9
|
-
from networkx.exception import NetworkXError
|
|
10
|
-
import matplotlib.animation as animation
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"""
|
|
14
|
-
reference:
|
|
15
|
-
- https://github.com/PeterTKovacs/boxes/blob/main/boxes/cbb.py
|
|
16
|
-
"""
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
class FractalHandlerNetworkX:
|
|
20
|
-
"""
|
|
21
|
-
Handles fractal analysis of NetworkX graphs using box-covering method.
|
|
22
|
-
"""
|
|
23
|
-
|
|
24
|
-
def __init__(self, network: nx.Graph):
|
|
25
|
-
self.network = network
|
|
26
|
-
|
|
27
|
-
def auto_pick_box_sizes(self):
|
|
28
|
-
diameter = nx.diameter(self.network) if nx.is_connected(self.network) else 1
|
|
29
|
-
min_size = max(1, diameter // 21)
|
|
30
|
-
max_size = diameter
|
|
31
|
-
return np.logspace(np.log10(min_size), np.log10(max_size), num=12, dtype=int)
|
|
32
|
-
|
|
33
|
-
def compute_fractal_dimensions(self, box_sizes="auto", iterations=10, save_plot_as=None, save_animated_as=None):
|
|
34
|
-
box_sizes = self.auto_pick_box_sizes() if box_sizes == "auto" else box_sizes
|
|
35
|
-
network_image, box_counts = self.plot_network(), []
|
|
36
|
-
|
|
37
|
-
# If animation is requested, set up storage for animation frames
|
|
38
|
-
if save_animated_as:
|
|
39
|
-
animation_data = []
|
|
40
|
-
pos = nx.spring_layout(self.network, dim=3) # 3D layout
|
|
41
|
-
|
|
42
|
-
# Create a color mapping based on z-coordinates
|
|
43
|
-
z_coords = np.array([pos[node][2] for node in self.network.nodes()])
|
|
44
|
-
z_min, z_max = z_coords.min(), z_coords.max()
|
|
45
|
-
node_colors = {}
|
|
46
|
-
for node in self.network.nodes():
|
|
47
|
-
z = pos[node][2]
|
|
48
|
-
# Normalize z to [0,1] range for color mapping
|
|
49
|
-
color_val = (z - z_min) / (z_max - z_min) if z_max != z_min else 0
|
|
50
|
-
node_colors[node] = plt.cm.rainbow(color_val)
|
|
51
|
-
|
|
52
|
-
for max_distance in tqdm(box_sizes, desc="Box sizes"):
|
|
53
|
-
num_boxes_list = []
|
|
54
|
-
boxes_for_animation = []
|
|
55
|
-
for _ in tqdm(range(iterations), desc=f"Networks for size {max_distance}", leave=False):
|
|
56
|
-
boxes = self.random_box_covering(max_distance)
|
|
57
|
-
num_boxes_list.append(len(boxes))
|
|
58
|
-
if save_animated_as:
|
|
59
|
-
boxes_for_animation.append(boxes)
|
|
60
|
-
avg_num_boxes = sum(num_boxes_list) / iterations
|
|
61
|
-
box_counts.append(avg_num_boxes)
|
|
62
|
-
|
|
63
|
-
if save_animated_as:
|
|
64
|
-
# For animation, store the boxes from the first iteration
|
|
65
|
-
animation_data.append(
|
|
66
|
-
{
|
|
67
|
-
"boxes": boxes_for_animation[0],
|
|
68
|
-
"max_distance": max_distance,
|
|
69
|
-
"num_boxes": num_boxes_list[0],
|
|
70
|
-
}
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
log_box_sizes, log_counts = np.log(box_sizes), np.log(box_counts)
|
|
74
|
-
|
|
75
|
-
try:
|
|
76
|
-
slope, intercept, r_value, p_value, std_err = stats.linregress(log_box_sizes, log_counts)
|
|
77
|
-
except ValueError:
|
|
78
|
-
slope, intercept, r_value = 0, 0, 0
|
|
79
|
-
fractal_dimension = -slope
|
|
80
|
-
|
|
81
|
-
if save_plot_as:
|
|
82
|
-
save_plot_as = save_plot_as if save_plot_as.endswith(".png") else f"{save_plot_as}.png"
|
|
83
|
-
fig, ax = plt.subplots(figsize=(10, 6))
|
|
84
|
-
ax.scatter(log_box_sizes, log_counts, color="blue", label="Data")
|
|
85
|
-
ax.plot(
|
|
86
|
-
log_box_sizes,
|
|
87
|
-
slope * log_box_sizes + intercept,
|
|
88
|
-
color="red",
|
|
89
|
-
label=f"Fit (D={fractal_dimension:.2f})",
|
|
90
|
-
linestyle="-",
|
|
91
|
-
)
|
|
92
|
-
ax.set_xlabel("log(Box Size)", fontsize=21)
|
|
93
|
-
ax.set_ylabel("log(Number of Boxes)", fontsize=21)
|
|
94
|
-
ax.set_title("Fractal Dimension Regression", fontsize=21)
|
|
95
|
-
ax.legend(fontsize=21)
|
|
96
|
-
ax.grid(True)
|
|
97
|
-
ax.tick_params(axis="both", which="major", labelsize=21)
|
|
98
|
-
fig.canvas.draw()
|
|
99
|
-
plot_image = np.array(fig.canvas.renderer.buffer_rgba())
|
|
100
|
-
network_height, network_width = network_image.shape[:2]
|
|
101
|
-
plot_height, plot_width = plot_image.shape[:2]
|
|
102
|
-
aspect_ratio = network_width / network_height
|
|
103
|
-
new_network_height = plot_height
|
|
104
|
-
new_network_width = int(new_network_height * aspect_ratio)
|
|
105
|
-
resized_network_image = np.array(Image.fromarray(network_image).resize((new_network_width, new_network_height)))
|
|
106
|
-
resized_network_image = resized_network_image[:, :, :3]
|
|
107
|
-
plot_image = plot_image[:, :, :3]
|
|
108
|
-
combined_image = np.hstack((resized_network_image, plot_image))
|
|
109
|
-
plt.figure(
|
|
110
|
-
figsize=(
|
|
111
|
-
combined_image.shape[1] / 100,
|
|
112
|
-
combined_image.shape[0] / 100,
|
|
113
|
-
)
|
|
114
|
-
)
|
|
115
|
-
plt.imshow(combined_image)
|
|
116
|
-
plt.axis("off")
|
|
117
|
-
plt.tight_layout()
|
|
118
|
-
plt.savefig(save_plot_as, dpi=420, bbox_inches="tight")
|
|
119
|
-
plt.close()
|
|
120
|
-
|
|
121
|
-
# Create and save animation if requested
|
|
122
|
-
if save_animated_as:
|
|
123
|
-
fig = plt.figure(figsize=(15, 8)) # Wider figure to accommodate legend
|
|
124
|
-
gs = plt.GridSpec(1, 2, width_ratios=[2, 1]) # 2:1 ratio for plot and legend
|
|
125
|
-
ax = fig.add_subplot(gs[0], projection="3d")
|
|
126
|
-
legend_ax = fig.add_subplot(gs[1]) # Create legend subplot once
|
|
127
|
-
legend_ax.axis("off")
|
|
128
|
-
legend_ax.spines["top"].set_visible(False)
|
|
129
|
-
|
|
130
|
-
def update(frame):
|
|
131
|
-
ax.clear()
|
|
132
|
-
legend_ax.clear() # Clear previous legend
|
|
133
|
-
|
|
134
|
-
data = animation_data[frame]
|
|
135
|
-
boxes = data["boxes"]
|
|
136
|
-
max_distance = data["max_distance"]
|
|
137
|
-
num_boxes = data["num_boxes"]
|
|
138
|
-
|
|
139
|
-
# Draw network and boxes in 3D subplot
|
|
140
|
-
edge_list = list(self.network.edges())
|
|
141
|
-
|
|
142
|
-
# Assign box colors based on average z-coordinate of nodes in each box
|
|
143
|
-
box_colors = []
|
|
144
|
-
for box in boxes:
|
|
145
|
-
box_nodes = list(box)
|
|
146
|
-
box_colors.append(np.mean([node_colors[node] for node in box_nodes], axis=0))
|
|
147
|
-
|
|
148
|
-
# Create a mapping of nodes to their box colors
|
|
149
|
-
node_to_color = {}
|
|
150
|
-
for box, color in zip(boxes, box_colors):
|
|
151
|
-
for node in box:
|
|
152
|
-
node_to_color[node] = color
|
|
153
|
-
|
|
154
|
-
# Draw edges with box colors
|
|
155
|
-
for edge in edge_list:
|
|
156
|
-
start = pos[edge[0]]
|
|
157
|
-
end = pos[edge[1]]
|
|
158
|
-
# Use color of both nodes for edge
|
|
159
|
-
start_color = node_to_color.get(edge[0], "gray")
|
|
160
|
-
end_color = node_to_color.get(edge[1], "gray")
|
|
161
|
-
edge_color = start_color if np.array_equal(start_color, end_color) else "gray"
|
|
162
|
-
|
|
163
|
-
mid_point = (start + end) / 2
|
|
164
|
-
offset = np.random.normal(0, 0.05, 3)
|
|
165
|
-
control_point = mid_point + offset
|
|
166
|
-
t = np.linspace(0, 1, 20)
|
|
167
|
-
curve = np.array([(1 - t) ** 2 * start + 2 * (1 - t) * t * control_point + t**2 * end for t in t])
|
|
168
|
-
ax.plot3D(curve[:, 0], curve[:, 1], curve[:, 2], c=edge_color, alpha=0.7, linewidth=1.5)
|
|
169
|
-
|
|
170
|
-
# Draw boxes and create legend elements
|
|
171
|
-
legend_elements = []
|
|
172
|
-
for box_idx, (box, color) in enumerate(zip(boxes, box_colors)):
|
|
173
|
-
box_nodes = list(box)
|
|
174
|
-
box_pos = np.array([pos[node] for node in box_nodes])
|
|
175
|
-
scatter = ax.scatter(
|
|
176
|
-
box_pos[:, 0], box_pos[:, 1], box_pos[:, 2], c=[color], s=150, alpha=0.8, edgecolor="white"
|
|
177
|
-
)
|
|
178
|
-
legend_elements.append((scatter, f"Box {box_idx+1}"))
|
|
179
|
-
|
|
180
|
-
# Set 3D plot properties
|
|
181
|
-
ax.set_title(f"Step {frame+1}/{len(animation_data)}\nBox Size = {max_distance}\nBoxes Required: {num_boxes}")
|
|
182
|
-
ax.set_xlabel("X")
|
|
183
|
-
ax.set_ylabel("Y")
|
|
184
|
-
ax.set_zlabel("Z")
|
|
185
|
-
|
|
186
|
-
# Calculate optimal number of columns based on number of boxes
|
|
187
|
-
n_boxes = len(boxes)
|
|
188
|
-
n_cols = min(4, n_boxes) # Max 4 columns for more spacing
|
|
189
|
-
n_rows = (n_boxes + n_cols - 1) // n_cols # Ceiling division
|
|
190
|
-
|
|
191
|
-
# Create legend with more spacing
|
|
192
|
-
legend = legend_ax.legend(
|
|
193
|
-
*zip(*legend_elements),
|
|
194
|
-
loc="center",
|
|
195
|
-
ncol=n_cols,
|
|
196
|
-
bbox_to_anchor=(0.5, 0.5),
|
|
197
|
-
borderaxespad=1,
|
|
198
|
-
labelspacing=1.5, # Increase vertical spacing between legend entries
|
|
199
|
-
handletextpad=1.2, # Increase spacing between marker and text
|
|
200
|
-
title="Box Groups",
|
|
201
|
-
frameon=True, # Add frame around legend
|
|
202
|
-
fancybox=True, # Round corners
|
|
203
|
-
shadow=True, # Add shadow
|
|
204
|
-
)
|
|
205
|
-
legend.get_title().set_fontsize(14)
|
|
206
|
-
for text in legend.get_texts():
|
|
207
|
-
text.set_fontsize(12)
|
|
208
|
-
|
|
209
|
-
ax.set_box_aspect([1, 1, 1])
|
|
210
|
-
ax.view_init(elev=20, azim=45)
|
|
211
|
-
ax.dist = 8
|
|
212
|
-
|
|
213
|
-
ani = animation.FuncAnimation(fig, update, frames=len(animation_data), interval=2000, repeat=True)
|
|
214
|
-
|
|
215
|
-
save_animated_as = save_animated_as if save_animated_as.endswith((".png", ".gif")) else save_animated_as + ".gif"
|
|
216
|
-
ani.save(save_animated_as.replace(".png", ".gif"), writer="pillow", fps=0.5)
|
|
217
|
-
plt.close(fig)
|
|
218
|
-
|
|
219
|
-
return fractal_dimension, r_value**2, log_box_sizes, log_counts
|
|
220
|
-
|
|
221
|
-
# ---- functional helpers ----
|
|
222
|
-
def random_box_covering(self, max_distance: int):
|
|
223
|
-
"""
|
|
224
|
-
[NOTE] This implementation handles disconnected graphs by processing each connected component separately.
|
|
225
|
-
|
|
226
|
-
This function implements the random box covering algorithm to estimate
|
|
227
|
-
the boxes needed to cover a network at a given maximum distance.
|
|
228
|
-
|
|
229
|
-
Args:
|
|
230
|
-
max_distance (int): The maximum distance allowed within each box, in number of hops.
|
|
231
|
-
|
|
232
|
-
Returns:
|
|
233
|
-
List[Set]: A list of sets, each containing the nodes in a box.
|
|
234
|
-
"""
|
|
235
|
-
boxes = []
|
|
236
|
-
for component in nx.connected_components(self.network):
|
|
237
|
-
subgraph = self.network.subgraph(component)
|
|
238
|
-
uncovered_nodes = set(subgraph.nodes())
|
|
239
|
-
while uncovered_nodes:
|
|
240
|
-
p = random.choice(list(uncovered_nodes))
|
|
241
|
-
distances = nx.single_source_shortest_path_length(subgraph, p)
|
|
242
|
-
box_nodes = set(node for node, dist in distances.items() if dist <= max_distance).intersection(uncovered_nodes)
|
|
243
|
-
boxes.append(box_nodes)
|
|
244
|
-
uncovered_nodes -= box_nodes
|
|
245
|
-
return boxes
|
|
246
|
-
|
|
247
|
-
def plot_network(self):
|
|
248
|
-
pos = nx.get_node_attributes(self.network, "pos")
|
|
249
|
-
fig = plt.figure(figsize=(10, 8))
|
|
250
|
-
ax = fig.add_subplot(111, projection="3d")
|
|
251
|
-
|
|
252
|
-
if pos:
|
|
253
|
-
# Use provided positions if they exist
|
|
254
|
-
pos_3d = {node: (x, y, 0) for node, (x, y) in pos.items()} # Convert 2D to 3D
|
|
255
|
-
else:
|
|
256
|
-
# Generate 3D spring layout
|
|
257
|
-
pos_3d = nx.spring_layout(self.network, dim=3)
|
|
258
|
-
|
|
259
|
-
# Draw edges with curves
|
|
260
|
-
for edge in self.network.edges():
|
|
261
|
-
start = np.array(pos_3d[edge[0]])
|
|
262
|
-
end = np.array(pos_3d[edge[1]])
|
|
263
|
-
ax.plot([start[0], end[0]], [start[1], end[1]], [start[2], end[2]], "gray", alpha=0.5)
|
|
264
|
-
|
|
265
|
-
# Draw nodes
|
|
266
|
-
node_pos = np.array([pos_3d[node] for node in self.network.nodes()])
|
|
267
|
-
ax.scatter(node_pos[:, 0], node_pos[:, 1], node_pos[:, 2], c="lightblue", s=30)
|
|
268
|
-
|
|
269
|
-
ax.set_title("3D Network Visualization")
|
|
270
|
-
ax.set_xlabel("X")
|
|
271
|
-
ax.set_ylabel("Y")
|
|
272
|
-
ax.set_zlabel("Z")
|
|
273
|
-
|
|
274
|
-
plt.tight_layout()
|
|
275
|
-
fig.canvas.draw()
|
|
276
|
-
image = np.array(fig.canvas.renderer.buffer_rgba())
|
|
277
|
-
plt.close(fig)
|
|
278
|
-
return image
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if __name__ == "__main__":
|
|
282
|
-
from src.dummies.networks import WebFractalNetwork, SierpinskiFractal
|
|
283
|
-
|
|
284
|
-
network = SierpinskiFractal(recursion_depth=6)
|
|
285
|
-
network = WebFractalNetwork(complexity=4)
|
|
286
|
-
|
|
287
|
-
handler = FractalHandlerNetworkX(network.network)
|
|
288
|
-
handler.compute_fractal_dimensions(box_sizes=np.linspace(1, 10, 20), iterations=10)
|
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
#!/Users/donyin/miniconda3/envs/rotation-1/bin/python
|
|
2
|
-
|
|
3
|
-
"""
|
|
4
|
-
- now we have two 1d series as signals make dummy data using numpy
|
|
5
|
-
- use either a sliding window or regression method to produce a new signal that represents the co-activation between the two signals
|
|
6
|
-
- in the sliding_window method, the window_size parameter is used to determine the size of the window used to calculate the co-activation
|
|
7
|
-
- it moves one step at a time and calculates the correlation between the two signals in the window
|
|
8
|
-
- in the regression method, the two signals are used as independent and dependent variables in a linear regression model
|
|
9
|
-
- the resulting signal is the predicted dependent variable
|
|
10
|
-
- if we use the sliding_window method, the window_size parameter must be an integer greater than 0
|
|
11
|
-
- if we use the regression method, we must plot the two signal and the resulting signal
|
|
12
|
-
"""
|
|
13
|
-
|
|
14
|
-
import numpy as np
|
|
15
|
-
from sklearn.linear_model import LinearRegression
|
|
16
|
-
import matplotlib.pyplot as plt
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
def get_co_activation(
|
|
20
|
-
signal_1: np.ndarray, signal_2: np.ndarray, method: str, window_size: int = None, save_plot_as=None
|
|
21
|
-
) -> np.ndarray:
|
|
22
|
-
"""
|
|
23
|
-
Get a measure of co-activation between two 1D signals.
|
|
24
|
-
|
|
25
|
-
Args:
|
|
26
|
-
signal_1: First 1D time series signal
|
|
27
|
-
signal_2: Second 1D time series signal
|
|
28
|
-
method: Either 'sliding_window' or 'regression'
|
|
29
|
-
window_size: Size of the window for 'sliding_window' method (must be an integer greater than 0)
|
|
30
|
-
|
|
31
|
-
Returns:
|
|
32
|
-
np.ndarray: A new signal representing the co-activation between the two signals
|
|
33
|
-
"""
|
|
34
|
-
match method:
|
|
35
|
-
case "sliding_window":
|
|
36
|
-
if window_size is None or window_size <= 0:
|
|
37
|
-
raise ValueError("window_size must be an integer greater than 0 for sliding_window method")
|
|
38
|
-
correlations = []
|
|
39
|
-
num_windows = len(signal_1) - window_size + 1
|
|
40
|
-
for i in range(num_windows):
|
|
41
|
-
window_signal_1 = signal_1[i : i + window_size]
|
|
42
|
-
window_signal_2 = signal_2[i : i + window_size]
|
|
43
|
-
corr = np.corrcoef(window_signal_1, window_signal_2)[0, 1]
|
|
44
|
-
correlations.append(corr)
|
|
45
|
-
|
|
46
|
-
# Plot signals and correlations
|
|
47
|
-
correlations = np.array(correlations)
|
|
48
|
-
if save_plot_as:
|
|
49
|
-
plt.figure(figsize=(10, 5))
|
|
50
|
-
plt.plot(signal_1, label="Signal 1", alpha=0.7)
|
|
51
|
-
plt.plot(signal_2, label="Signal 2", alpha=0.7)
|
|
52
|
-
plt.plot(
|
|
53
|
-
np.arange(window_size // 2, len(correlations) + window_size // 2),
|
|
54
|
-
correlations,
|
|
55
|
-
label="Sliding Window Correlation",
|
|
56
|
-
linestyle="--",
|
|
57
|
-
)
|
|
58
|
-
plt.legend()
|
|
59
|
-
plt.title(f"Sliding Window Method Co-Activation (window size={window_size})")
|
|
60
|
-
plt.savefig(save_plot_as, dpi=320)
|
|
61
|
-
plt.close()
|
|
62
|
-
|
|
63
|
-
return correlations
|
|
64
|
-
|
|
65
|
-
case "regression":
|
|
66
|
-
# Reshape signals for regression
|
|
67
|
-
X = signal_1.reshape(-1, 1)
|
|
68
|
-
y = signal_2
|
|
69
|
-
|
|
70
|
-
# Fit linear regression
|
|
71
|
-
model = LinearRegression()
|
|
72
|
-
model.fit(X, y)
|
|
73
|
-
|
|
74
|
-
# Predict y
|
|
75
|
-
y_pred = model.predict(X)
|
|
76
|
-
|
|
77
|
-
# Plot signals
|
|
78
|
-
if save_plot_as:
|
|
79
|
-
plt.figure(figsize=(10, 5))
|
|
80
|
-
plt.plot(signal_1, label="Signal 1 (IV)")
|
|
81
|
-
plt.plot(signal_2, label="Signal 2 (DV)")
|
|
82
|
-
plt.plot(y_pred, label="Predicted DV", linestyle="--")
|
|
83
|
-
plt.legend()
|
|
84
|
-
plt.title("Regression Method Co-Activation")
|
|
85
|
-
plt.savefig(save_plot_as, dpi=320)
|
|
86
|
-
plt.close()
|
|
87
|
-
|
|
88
|
-
return y_pred
|
|
89
|
-
|
|
90
|
-
case _:
|
|
91
|
-
raise ValueError("method must be 'sliding_window' or 'regression'")
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
if __name__ == "__main__":
|
|
95
|
-
t = np.linspace(0, 10, 1000)
|
|
96
|
-
signal1 = np.sin(2 * np.pi * 0.5 * t) + 0.2 * np.random.randn(1000)
|
|
97
|
-
signal2 = 0.8 * np.sin(2 * np.pi * 0.5 * t + np.pi / 4) + 0.2 * np.random.randn(1000)
|
|
98
|
-
|
|
99
|
-
get_co_activation(signal1, signal2, method="sliding_window", window_size=32, save_plot_as="coactivation_sliding_window.png")
|
|
100
|
-
get_co_activation(signal1, signal2, method="regression", save_plot_as="coactivation_regression.png")
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
#!/Users/donyin/miniconda3/envs/rotation-1/bin/python
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
import numpy
|
|
5
|
-
from scipy.fft import fft, ifft
|
|
6
|
-
import matplotlib.pyplot as plt
|
|
7
|
-
from donware import inspect_package
|
|
8
|
-
from numba import njit
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def hfd_matlab_equivalent(data, k_max):
|
|
12
|
-
"""
|
|
13
|
-
[translated from MATLAB code]
|
|
14
|
-
|
|
15
|
-
[output]
|
|
16
|
-
- produce values between 1 and 2
|
|
17
|
-
"""
|
|
18
|
-
N = len(data)
|
|
19
|
-
L = numpy.zeros(k_max)
|
|
20
|
-
x = numpy.zeros(k_max)
|
|
21
|
-
y = numpy.zeros(k_max)
|
|
22
|
-
|
|
23
|
-
for k in range(1, k_max + 1):
|
|
24
|
-
Lk = numpy.zeros(k)
|
|
25
|
-
for m in range(1, k + 1):
|
|
26
|
-
norm_factor = (N - 1) / (numpy.round((N - m) / k) * k)
|
|
27
|
-
X = numpy.sum(numpy.abs(numpy.diff(data[m - 1 :: k])))
|
|
28
|
-
Lk[m - 1] = X * norm_factor / k
|
|
29
|
-
|
|
30
|
-
y[k - 1] = numpy.log(numpy.sum(Lk) / k)
|
|
31
|
-
x[k - 1] = numpy.log(1 / k)
|
|
32
|
-
|
|
33
|
-
D = numpy.polyfit(x, y, 1)
|
|
34
|
-
HFD = D[0]
|
|
35
|
-
|
|
36
|
-
return HFD
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
@njit
|
|
40
|
-
def compute_L_x(X, k_max):
|
|
41
|
-
N = len(X)
|
|
42
|
-
L = numpy.zeros(k_max)
|
|
43
|
-
x = numpy.zeros(k_max)
|
|
44
|
-
for k in range(1, k_max + 1):
|
|
45
|
-
Lk = numpy.zeros(k)
|
|
46
|
-
for m in range(k):
|
|
47
|
-
Lmk = 0.0
|
|
48
|
-
n_max = (N - m) // k
|
|
49
|
-
for i in range(1, n_max):
|
|
50
|
-
Lmk += abs(X[m + i * k] - X[m + (i - 1) * k])
|
|
51
|
-
Lmk *= (N - 1) / (n_max * k)
|
|
52
|
-
Lk[m] = Lmk
|
|
53
|
-
L[k - 1] = numpy.log(Lk.mean())
|
|
54
|
-
x[k - 1] = numpy.log(1.0 / k)
|
|
55
|
-
return x, L
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def hfd_pyeeg(X, k_max):
|
|
59
|
-
x, L = compute_L_x(X, k_max)
|
|
60
|
-
A = numpy.column_stack((x, numpy.ones_like(x)))
|
|
61
|
-
beta, _, _, _ = numpy.linalg.lstsq(A, L, rcond=None)
|
|
62
|
-
return beta[0]
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def _hfd_pyeeg(X, k_max):
|
|
66
|
-
"""
|
|
67
|
-
[from PyEEG package]
|
|
68
|
-
Higuchi Fractal Dimension of a time series X. kmax is an HFD parameter
|
|
69
|
-
|
|
70
|
-
[output]
|
|
71
|
-
- for some reasons produce values between 0 and 1
|
|
72
|
-
|
|
73
|
-
[modifications]
|
|
74
|
-
- kmax should be inclusive, Kmax + 1
|
|
75
|
-
"""
|
|
76
|
-
L = []
|
|
77
|
-
x = []
|
|
78
|
-
N = len(X)
|
|
79
|
-
for k in range(1, k_max + 1):
|
|
80
|
-
Lk = []
|
|
81
|
-
for m in range(0, k):
|
|
82
|
-
Lmk = 0
|
|
83
|
-
for i in range(1, int(numpy.floor((N - m) / k))):
|
|
84
|
-
Lmk += abs(X[m + i * k] - X[m + i * k - k])
|
|
85
|
-
Lmk = Lmk * (N - 1) / numpy.floor((N - m) / float(k)) / k
|
|
86
|
-
Lk.append(Lmk)
|
|
87
|
-
L.append(numpy.log(numpy.mean(Lk)))
|
|
88
|
-
x.append([numpy.log(float(1) / k), 1])
|
|
89
|
-
|
|
90
|
-
(p, _, _, _) = numpy.linalg.lstsq(x, L, rcond=None)
|
|
91
|
-
return p[0]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|