graphical-sampling 0.1.0__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.
@@ -0,0 +1,234 @@
1
+ from itertools import pairwise
2
+ from dataclasses import dataclass
3
+
4
+ import numpy as np
5
+ from numpy.typing import NDArray
6
+ import matplotlib.pyplot as plt
7
+ from matplotlib.patches import Polygon
8
+ from scipy.spatial import ConvexHull
9
+
10
+ from ..clustering import DublyBalancedKMeans
11
+
12
+
13
+ @dataclass
14
+ class Zone:
15
+ units: NDArray
16
+
17
+
18
+ @dataclass
19
+ class Cluster:
20
+ units: NDArray
21
+ zones: list[Zone]
22
+
23
+
24
+ class Population:
25
+ def __init__(
26
+ self,
27
+ coordinate: NDArray,
28
+ inclusion_probability: NDArray,
29
+ *,
30
+ n_clusters: int,
31
+ n_zones: tuple[int, int],
32
+ tolerance: int,
33
+ ) -> None:
34
+ self.coords = coordinate
35
+ self.probs = inclusion_probability
36
+ self.n_clusters = n_clusters
37
+ self.n_zones = n_zones
38
+ self.tolerance = tolerance
39
+
40
+ self.clusters = self._generate_clusters()
41
+
42
+ def _generate_clusters(self) -> list[Cluster]:
43
+ # kmeans = SoftBalancedKMeans(self.n_clusters, tolerance=self.tolerance)
44
+ # kmeans.fit(self.coords, self.probs)
45
+
46
+ # agg = AggregateBalancedKMeans(k=self.n_clusters, tolerance=self.tolerance)
47
+ # agg.fit(self.coords, self.probs.reshape(-1, 1), np.array([1]))
48
+
49
+ dbk = DublyBalancedKMeans(k=self.n_clusters)
50
+ dbk.fit(self.coords, self.probs)
51
+
52
+ return [
53
+ Cluster(units=units, zones=self._generate_zones(units))
54
+ # for units in agg.get_clusters()
55
+ for units in dbk.clusters
56
+ ]
57
+
58
+ def _generate_zones(self, units) -> list[Zone]:
59
+ vertical_zones = self._sweep(
60
+ units[np.argsort(units[:, 1])], 1 / self.n_zones[0]
61
+ )
62
+ zones = []
63
+ for zone in vertical_zones:
64
+ units_of_basic_zones = self._sweep(
65
+ zone[np.argsort(zone[:, 2])], 1 / (np.prod(self.n_zones))
66
+ )
67
+ for units in units_of_basic_zones:
68
+ units[:, 3] = self._numerical_stabilizer(units[:, 3])
69
+ zones.append(Zone(units=units))
70
+ return zones
71
+
72
+ def _sweep(
73
+ self, units: NDArray, threshold: float
74
+ ) -> tuple[list[NDArray], list[int]]:
75
+ boarder_units_remainings, zones_indices = self._generate_boarders_and_indices(
76
+ units, threshold
77
+ )
78
+ swept_zones = []
79
+ for indices in pairwise(zones_indices):
80
+ zone, boarder_units_remainings = self._sweep_zone(
81
+ units, boarder_units_remainings, indices, threshold
82
+ )
83
+ swept_zones.append(zone)
84
+ return swept_zones
85
+
86
+ def _generate_boarders_and_indices(self, units: NDArray, threshold: float):
87
+ thresholds = np.arange(
88
+ threshold, np.sum(units[:, 3]) - threshold / 2, threshold
89
+ )
90
+ indices = np.concatenate(
91
+ (
92
+ [0],
93
+ np.searchsorted(units[:, 3].cumsum(), thresholds, side="right"),
94
+ [units.shape[0] - 1],
95
+ )
96
+ )
97
+ boarder_units = {index: units[index][3] for index in np.unique(indices)}
98
+ return boarder_units, indices
99
+
100
+ def _sweep_zone(
101
+ self,
102
+ units: NDArray,
103
+ boarder_units_remainings: NDArray,
104
+ indices: tuple[NDArray, NDArray],
105
+ threshold: float,
106
+ ) -> NDArray:
107
+ zone, start_remainder = self._sweep_boarder_unit(
108
+ np.array([]).reshape(0, 4),
109
+ units[indices[0]],
110
+ boarder_units_remainings[indices[0]],
111
+ threshold,
112
+ )
113
+ boarder_units_remainings[indices[0]] = start_remainder
114
+
115
+ zone = np.concatenate([zone, units[indices[0] + 1 : indices[1]]])
116
+
117
+ zone, stop_remainder = self._sweep_boarder_unit(
118
+ zone,
119
+ units[indices[1]],
120
+ boarder_units_remainings[indices[1]],
121
+ threshold - np.sum(zone[:, 3]),
122
+ )
123
+ boarder_units_remainings[indices[1]] = stop_remainder
124
+
125
+ return zone, boarder_units_remainings
126
+
127
+ def _sweep_boarder_unit(
128
+ self, zone: NDArray, unit: NDArray, probability: float, threshold: float
129
+ ) -> tuple[NDArray, float]:
130
+ if probability < 10**-self.tolerance:
131
+ return zone, 0
132
+ if threshold < 10**-self.tolerance:
133
+ return zone, probability
134
+ if probability < threshold - 10**-self.tolerance:
135
+ return np.concatenate(
136
+ [zone, np.append(unit[:3], probability).reshape(1, -1)]
137
+ ), 0
138
+ elif probability > threshold + 10**-self.tolerance:
139
+ return np.concatenate(
140
+ [zone, np.append(unit[:3], threshold).reshape(1, -1)]
141
+ ), probability - threshold
142
+ return np.concatenate([zone, np.append(unit[:3], threshold).reshape(1, -1)]), 0
143
+
144
+ def _numerical_stabilizer(self, probs: NDArray) -> NDArray:
145
+ probs_stabled = np.round(probs, self.tolerance)
146
+ probs_stabled *= 1 / (np.sum(probs_stabled) * np.prod(self.n_zones))
147
+ return probs_stabled
148
+
149
+ def plot(self, ax=None, figsize: tuple[int, int] = (8, 6)) -> None:
150
+ if ax is None:
151
+ fig, ax = plt.subplots(figsize=figsize)
152
+
153
+ def plot_convex_hull(
154
+ points, ax, color, alpha=0.3, edge_color="black", line_width=1.0
155
+ ):
156
+ if len(points) < 3:
157
+ return ax, None
158
+ hull = ConvexHull(points)
159
+ polygon = Polygon(
160
+ points[hull.vertices],
161
+ closed=True,
162
+ facecolor=color,
163
+ alpha=alpha,
164
+ edgecolor=edge_color,
165
+ lw=line_width,
166
+ )
167
+ ax.add_patch(polygon)
168
+ return ax, hull
169
+
170
+ for cluster_idx, cluster in enumerate(self.clusters):
171
+ cluster_points = cluster.units[:, 1:3]
172
+ cluster_color = plt.cm.tab10(cluster_idx % 10)
173
+ ax, _ = plot_convex_hull(cluster_points, ax, color=cluster_color, alpha=0.2)
174
+ ax.scatter(
175
+ cluster_points[:, 0],
176
+ cluster_points[:, 1],
177
+ color=cluster_color,
178
+ label=f"Cluster {cluster_idx+1}",
179
+ alpha=0.8,
180
+ )
181
+
182
+ for zone_idx, zone in enumerate(cluster.zones):
183
+ zone_points = zone.units[:, 1:3]
184
+ zone_color = cluster_color
185
+ ax, hull = plot_convex_hull(
186
+ zone_points,
187
+ ax,
188
+ color=zone_color,
189
+ alpha=0.4,
190
+ edge_color="gray",
191
+ line_width=0.8,
192
+ )
193
+
194
+ hull_center = np.mean(
195
+ zone_points if hull is None else zone_points[hull.vertices], axis=0
196
+ )
197
+ ax.text(
198
+ hull_center[0],
199
+ hull_center[1],
200
+ f"{zone_idx+1}",
201
+ color="black",
202
+ fontsize=16,
203
+ alpha=0.3,
204
+ ha="center",
205
+ va="center",
206
+ weight="bold",
207
+ )
208
+ return ax
209
+
210
+ def plot_with_samples(self, samples: NDArray, max_cols: int = 4) -> None:
211
+ n_samples = len(samples)
212
+ n_cols = min(max_cols, n_samples)
213
+ n_rows = (n_samples + n_cols - 1) // n_cols
214
+ figsize = (5 * n_cols, 5 * n_rows)
215
+
216
+ fig, axes = plt.subplots(n_rows, n_cols, figsize=figsize)
217
+ axes = axes.flatten() if n_samples > 1 else [axes]
218
+
219
+ for sample_idx, sample in enumerate(samples):
220
+ ax = axes[sample_idx]
221
+ ax = self.plot(ax)
222
+ ax.scatter(
223
+ self.coords[sample][:, 0],
224
+ self.coords[sample][:, 1],
225
+ color="black",
226
+ marker="X",
227
+ alpha=0.8,
228
+ s=200,
229
+ label=f"Sample {sample_idx+1}",
230
+ )
231
+ ax.set_title(f"Sample {sample_idx+1}")
232
+
233
+ for ax in axes[n_samples:]:
234
+ fig.delaxes(ax)
@@ -0,0 +1,21 @@
1
+ import numpy as np
2
+ from numpy.typing import NDArray
3
+
4
+
5
+ class RandomSampling:
6
+ def __init__(
7
+ self,
8
+ coordinate: NDArray,
9
+ inclusion_probability: NDArray,
10
+ n: int,
11
+ ) -> None:
12
+ self.coords = coordinate
13
+ self.probs = inclusion_probability
14
+ self.n = n
15
+ self.rng = np.random.default_rng()
16
+
17
+ def sample(self, n_samples: int):
18
+ return np.array(
19
+ [self.rng.integers(0, self.probs.shape[0], size=self.n) for _ in range(n_samples)],
20
+ dtype=int
21
+ )
@@ -0,0 +1,4 @@
1
+ from .astar import AStar
2
+
3
+
4
+ __all__ = ["AStar"]
@@ -0,0 +1,119 @@
1
+ from dataclasses import dataclass
2
+ from typing import Generator, Any
3
+
4
+ import numpy as np
5
+
6
+ from ..criteria.criteria import Criteria
7
+ from ..design import Design
8
+ from ..red_black_tree import RedBlackTree
9
+
10
+
11
+ @dataclass(frozen=True, order=False, eq=False)
12
+ class Node:
13
+ criteria_value: float
14
+ design: Design
15
+
16
+ def __lt__(self, other: Any) -> bool:
17
+ if not isinstance(other, Node):
18
+ return NotImplemented
19
+ return self.criteria_value < other.criteria_value
20
+
21
+ def __eq__(self, other: Any) -> bool:
22
+ if not isinstance(other, Node):
23
+ return NotImplemented
24
+ return self.criteria_value == other.criteria_value
25
+
26
+ def __le__(self, other: Any) -> bool:
27
+ return self < other or self == other
28
+
29
+ def __ge__(self, other: Any) -> bool:
30
+ return not self < other
31
+
32
+ def __gt__(self, other: Any) -> bool:
33
+ return other < self
34
+
35
+
36
+ class AStar:
37
+ def __init__(
38
+ self,
39
+ initial_design: Design,
40
+ criteria: Criteria,
41
+ *,
42
+ switch_coefficient: float = 0.5,
43
+ random_pull: bool = False,
44
+ threshold: float = 1e-2,
45
+ rng: np.random.Generator = np.random.default_rng(),
46
+ ) -> None:
47
+ self.initial_design = initial_design
48
+ self.criteria = criteria
49
+ self.switch_coefficient = switch_coefficient
50
+ self.random_pull = random_pull
51
+ self.threshold = threshold
52
+ self.rng = rng
53
+
54
+ self.initial_criteria_value = self.criteria(self.initial_design)
55
+ self.best_design = self.initial_design
56
+ self.best_criteria_value = self.initial_criteria_value
57
+
58
+ def iterate_design(self, design: Design, num_changes: int) -> Design:
59
+ new_design = design.copy()
60
+ for _ in range(num_changes):
61
+ new_design.iterate(
62
+ random_pull=self.random_pull,
63
+ switch_coefficient=self.switch_coefficient,
64
+ )
65
+ return new_design
66
+
67
+ def neighbors(
68
+ self,
69
+ design: Design,
70
+ num_new_nodes: int,
71
+ num_changes: int,
72
+ ) -> Generator[Design, None, None]:
73
+ for _ in range(num_new_nodes):
74
+ yield self.iterate_design(design, num_changes)
75
+
76
+ def run(
77
+ self,
78
+ max_iterations: int,
79
+ num_new_nodes: int,
80
+ max_open_set_size: int,
81
+ num_changes: int,
82
+ ):
83
+ closed_set = set()
84
+ open_set = RedBlackTree[Node]()
85
+ open_set.insert(Node(self.initial_criteria_value, self.initial_design))
86
+
87
+ for it in range(max_iterations):
88
+ if not open_set:
89
+ break
90
+ mn = open_set.get_min()
91
+ if not mn:
92
+ break
93
+ current_design = mn.design
94
+ if current_design in closed_set:
95
+ continue
96
+ closed_set.add(current_design)
97
+ for new_design in self.neighbors(
98
+ current_design, num_new_nodes, num_changes
99
+ ):
100
+ new_criteria_value = self.criteria(new_design)
101
+
102
+ if new_design in closed_set:
103
+ continue
104
+ if len(open_set) < max_open_set_size:
105
+ open_set.insert(Node(new_criteria_value, new_design))
106
+ else:
107
+ mx = open_set.get_max()
108
+ if mx is None or mx.criteria_value > new_criteria_value:
109
+ if mx is not None:
110
+ open_set.remove(mx)
111
+ open_set.insert(Node(new_criteria_value, new_design))
112
+
113
+ if new_criteria_value < self.best_criteria_value:
114
+ self.best_design = new_design
115
+ self.best_criteria_value = new_criteria_value
116
+
117
+ if self.best_criteria_value < self.threshold:
118
+ return it
119
+ return max_iterations
@@ -0,0 +1,94 @@
1
+ from __future__ import annotations
2
+ import heapq
3
+ from dataclasses import dataclass
4
+ from typing import Iterator, Generic, Collection, Optional, Any
5
+ from typing import TypeVar
6
+
7
+ import numpy as np
8
+
9
+ from .type import ComparableNegatable
10
+
11
+ T = TypeVar("T", bound=ComparableNegatable)
12
+
13
+
14
+ class MaxHeap(Generic[T]):
15
+ def __init__(
16
+ self,
17
+ initial_heap: Optional[Collection[T]] = None,
18
+ rng: np.random.Generator = np.random.default_rng(),
19
+ ):
20
+ self.heap: list[T] = []
21
+ if initial_heap is not None:
22
+ self.heap = [-item for item in initial_heap]
23
+ heapq.heapify(self.heap)
24
+ self.rng = rng
25
+
26
+ def push(self, item: T):
27
+ heapq.heappush(self.heap, -item)
28
+
29
+ def pop(self) -> T:
30
+ return -heapq.heappop(self.heap)
31
+
32
+ def peek(self) -> T:
33
+ return -self.heap[0]
34
+
35
+ def randompop(self) -> T:
36
+ idx = self.rng.integers(len(self.heap))
37
+ val = -self.heap[idx]
38
+ self.heap[idx] = self.heap[-1]
39
+ self.heap.pop()
40
+ if idx < len(self.heap):
41
+ heapq._siftup(self.heap, idx) # type: ignore
42
+ heapq._siftdown(self.heap, 0, idx) # type: ignore
43
+ return val
44
+
45
+ def copy(self) -> MaxHeap[T]:
46
+ new_heap = MaxHeap[T]()
47
+ new_heap.heap = self.heap[:]
48
+ new_heap.rng = self.rng
49
+ return new_heap
50
+
51
+ def __len__(self) -> int:
52
+ return len(self.heap)
53
+
54
+ def __bool__(self) -> bool:
55
+ return bool(self.heap)
56
+
57
+ def __iter__(self) -> Iterator[T]:
58
+ return map(lambda x: -x, self.heap)
59
+
60
+ def __str__(self):
61
+ return str(list(map(lambda x: -x, self.heap)))
62
+
63
+ def __hash__(self) -> int:
64
+ return hash(tuple(self.heap))
65
+
66
+ def __eq__(self, other: object) -> bool:
67
+ if not isinstance(other, MaxHeap):
68
+ return NotImplemented
69
+ return self.heap == other.heap
70
+
71
+
72
+ @dataclass(order=False)
73
+ class Sample:
74
+ probability: float
75
+ ids: frozenset[int]
76
+
77
+ def almost_zero(self) -> bool:
78
+ return self.probability < 1e-9
79
+
80
+ def __eq__(self, other: Any) -> bool:
81
+ if not isinstance(other, Sample):
82
+ return NotImplemented
83
+ return self.probability == other.probability and self.ids == other.ids
84
+
85
+ def __lt__(self, other: Any) -> bool:
86
+ if not isinstance(other, Sample):
87
+ return NotImplemented
88
+ return self.probability < other.probability
89
+
90
+ def __neg__(self) -> Sample:
91
+ return Sample(-self.probability, self.ids)
92
+
93
+ def __hash__(self):
94
+ return hash(self.ids)
@@ -0,0 +1,17 @@
1
+ from abc import abstractmethod
2
+ from typing import Protocol, Any
3
+
4
+
5
+ class Comparable(Protocol):
6
+ @abstractmethod
7
+ def __eq__(self, other: Any) -> bool: ...
8
+
9
+ @abstractmethod
10
+ def __lt__(self, other: Any) -> bool: ...
11
+
12
+
13
+ class Negatable(Protocol):
14
+ def __neg__(self) -> "Negatable": ...
15
+
16
+
17
+ class ComparableNegatable(Comparable, Negatable, Protocol): ...
@@ -0,0 +1,85 @@
1
+ Metadata-Version: 2.4
2
+ Name: graphical-sampling
3
+ Version: 0.1.0
4
+ Summary: Python package for Graphical Sampling Method
5
+ Author-email: Bardia Panahbehagh <bardia.panah@gmail.com>, Mehdi Mohebbi <mehdi.mohebbi23@gmail.com>, AmirMohammad HosseiniNasab <awmirhn@gmail.com>, Mehdi Hosseini Moghadam <m.h.moghadam1996@gmail.com>
6
+ License-File: LICENSE
7
+ Requires-Python: >=3.10
8
+ Requires-Dist: k-means-constrained>=0.7.5
9
+ Requires-Dist: matplotlib
10
+ Requires-Dist: numpy
11
+ Requires-Dist: package-sampling>=0.3.0
12
+ Requires-Dist: pandas
13
+ Requires-Dist: scikit-learn
14
+ Requires-Dist: scipy
15
+ Description-Content-Type: text/markdown
16
+
17
+
18
+ # Graphical Sampling Method - Python Package
19
+
20
+ The Graphical Sampling Method, introduced by Panahbehagh (2025), presents an innovative approach to finite population sampling based on a unique graphical framework. This method allows researchers to visually depict first-order inclusion probabilities (FIP) as bars on a two-dimensional graph. By adjusting the positions of these bars, users can explore a wide range of sampling designs while controlling second-order inclusion probabilities (SIP).
21
+
22
+ This package, `graphical_sampling`, provides tools for implementing the Graphical Sampling Method and is available on PyPI.
23
+
24
+ ---
25
+
26
+ ## Features
27
+ - Create various unequal probability sampling designs with a fixed FIP.
28
+ - Control and explore second-order inclusion probabilities (SIP) through visual manipulation.
29
+ - Incorporate search algorithms, such as A* and Genetic Algorithm, to optimize sampling designs for specific needs (e.g., well-spread or optimal sampling).
30
+
31
+ ## Installation
32
+
33
+ Install the package via pip:
34
+ ```bash
35
+ pip install graphical_sampling
36
+ ```
37
+
38
+ ## How to Use
39
+
40
+ The package includes core classes and methods to facilitate sampling design creation and optimization. Below is a basic example demonstrating the API.
41
+
42
+ ### Example Usage
43
+
44
+ ```python
45
+ import graphical_sampling as gs
46
+ import numpy as np
47
+
48
+ # Set up random generator and sample data
49
+ rng = np.random.default_rng()
50
+ N = 50 # Population size
51
+ x = rng.random(size=N) # Auxiliary variable
52
+ n = 5 # Sample size
53
+
54
+ # Generate initial inclusion probabilities
55
+ inclusion = rng.random(N)
56
+ inclusion *= n / inclusion.sum()
57
+
58
+ # Define initial sampling design and evaluation criteria
59
+ initial_design = gs.Design(inclusion)
60
+ nht = gs.criteria.VarNHT(x, inclusion)
61
+ astar = gs.search.AStar(initial_design, nht, switch_coefficient=1)
62
+
63
+ # Display initial criteria and design
64
+ print("Initial criteria value:", astar.initial_criteria_value)
65
+ astar.initial_design.show()
66
+
67
+ # Run the A* search algorithm to optimize the design
68
+ astar.run(max_iterations=2000, num_new_nodes=10, max_open_set_size=10000, num_changes=1)
69
+
70
+ # Display results after optimization
71
+ print("Best criteria value:", astar.best_criteria_value)
72
+ astar.best_design.show()
73
+ ```
74
+
75
+
76
+ ## Maintainers
77
+
78
+ - Bardia Panahbehagh - [bardia.panah@gmail.com](mailto:bardia.panah@gmail.com)
79
+ - Mehdi Mohebbi - [mehdi.mohebbi23@gmail.com](mailto:mehdi.mohebbi23@gmail.com)
80
+ - AmirMohammad HosseiniNasab - [awmirhn@gmail.com](mailto:awmirhn@gmail.com)
81
+ - Mehdi Hosseini Moghadam - [m.h.moghadam1996@gmail.com](mailto:m.h.moghadam1996@gmail.com)
82
+
83
+ ---
84
+
85
+ For more details, consult the official paper: Panahbehagh, B. (2025). Graphical Sampling Method.
@@ -0,0 +1,27 @@
1
+ graphical_sampling/__init__.py,sha256=bb5HqAY4pgCI8x__PSHsHnadxOTmxkJUzim6clFZLAU,340
2
+ graphical_sampling/design.py,sha256=BtuCSp6tpXB54T_WppN-1JiCUj59cgghHUagj1EfyG8,4083
3
+ graphical_sampling/red_black_tree.py,sha256=IPrlUfSLhXL0VXuPraCboskD7u7vizKX-wDVF5gWlDs,16633
4
+ graphical_sampling/structs.py,sha256=ZDRG-DmH_DDeLzW10dP_nC5AkLc3dAQ3hGFPwedZXYQ,2533
5
+ graphical_sampling/type.py,sha256=TECZp9fU_ydavkKOE-Lds4sx5f2C6WgcWfZ5WKZz8Xo,364
6
+ graphical_sampling/clustering/__init__.py,sha256=K8F84-uCEZYt1iui9vTrUkiKhDYO9dPRN2vwSVcPcMA,588
7
+ graphical_sampling/clustering/aggregate.py,sha256=uVd8Mmp8qFt2C9t9IUk6sYDGihK51T_8onmeQ_XvswM,8037
8
+ graphical_sampling/clustering/dubly_balanced_clustering.py,sha256=2LbNzpZnqoehC1Ou6dEfl8J_75hEj8Y0e6tKYKfZjlI,7791
9
+ graphical_sampling/clustering/one_boundary.py,sha256=pA9nuF8QW-GsJ1UW0Nuuof9iudP9KeH1AZasi8Gp9Y0,9532
10
+ graphical_sampling/clustering/soft_balanced_kmeans.py,sha256=LAEEZ6iFxmQukgS3KalFwKwE_lMVcC58rBahuh1gl-M,6143
11
+ graphical_sampling/criteria/__init__.py,sha256=ruthywH9WgPKcHuPrPDztPzofv8KrriLyrdZsdGgUHg,51
12
+ graphical_sampling/criteria/criteria.py,sha256=ImL0fvutHVhUsMbk7N8RTF1V8VV0o-WGzv5AABWACmw,298
13
+ graphical_sampling/criteria/var_nht.py,sha256=8L15HBfo_Zz1VgF5LQTqpeq-sIAULSAs9We-awL_u6g,700
14
+ graphical_sampling/measure/__init__.py,sha256=2E0yd8sxUso8_fYB7SC4DO0dpfMw7Fz531lSeQbdLHQ,53
15
+ graphical_sampling/measure/density.py,sha256=mGSrdhJWsLZ1VYof7ISQ6E6v5IA_PEAfzXeHuy9eJp0,3882
16
+ graphical_sampling/random/__init__.py,sha256=K8MGYYz9svNRV_gwqXungGFd4f6gA6I1NKxMnJSyRAQ,47
17
+ graphical_sampling/random/generator.py,sha256=ZrWizUpJiqaiK4FXiA-TZcWceDhjC95I_owtPwtF2mQ,10926
18
+ graphical_sampling/sampling/__init__.py,sha256=nTweLFsRqf5klvbowJiVDn0gVax80i69KMUmgP6tkHI,208
19
+ graphical_sampling/sampling/kmeans_spatial_sampling.py,sha256=b6InONfN6tphlyt37RiQok4K4E2otymVXT1_PZYN0hs,2062
20
+ graphical_sampling/sampling/population.py,sha256=-wmXYJr6Ren6r7LAe4fB4ogxL_lp-KHVaxVkXHUrT7k,7885
21
+ graphical_sampling/sampling/random_sampling.py,sha256=JARWmciRuVObVM0WQqj-uAr2D2Lj0IMjgr0Ti0yaQro,538
22
+ graphical_sampling/search/__init__.py,sha256=Ag3JJcApUbE_Lct9qExrTYvHmxjlpq2x_yEXtwRwI2s,47
23
+ graphical_sampling/search/astar.py,sha256=wvMRGjjMkBC8kqyJ2wgc5OJh3snjPs0-SBSHmUdgkyM,3815
24
+ graphical_sampling-0.1.0.dist-info/METADATA,sha256=Ec2nHamNHbsIvgLgCr7AI2AuFIQID4HZmAKW5P-KHH0,3220
25
+ graphical_sampling-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
26
+ graphical_sampling-0.1.0.dist-info/licenses/LICENSE,sha256=1xF-ubUdk0b37lCULLgZ_WltcT3__rvChYoNxtwMGJE,1070
27
+ graphical_sampling-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Mehdi Mohebbi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.