Initial commit based of previous repositories contents
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*/__pycache__/**
|
||||||
38
README.md
Normal file
38
README.md
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
# Boundary-aware centrality for Spatial Graphs
|
||||||
|
|
||||||
|
University project for boundary-aware node centralities for spatial graphs. For the project details and results see the [typst paper](./doc/paper.typ). For building the document and the usage of the python library used for this project see the corresponding sections below.
|
||||||
|
|
||||||
|
## Code usage
|
||||||
|
|
||||||
|
> [!important]
|
||||||
|
> The implementation uses [gurobi](https://www.gurobi.com/solutions/gurobi-optimizer/) for solving the linear problem for the function fitting which requires a *license*. Please refer to the gurobi license documentation for details.
|
||||||
|
|
||||||
|
Install the requirements into a virtual environment:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# create virtual environment
|
||||||
|
python -m venv venv
|
||||||
|
# activate virtual environment
|
||||||
|
source venv/bin/activate
|
||||||
|
# install required dependencies
|
||||||
|
pip install -r requirements.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
The `src` directory contains all the python source files that are used for the creation of the diagrams and images found in the `doc/figures` directory. For the corresponding usages for both datasets used for this project please see [merfish.py](./merfish.py) and [mibitof.py](./mibitof.py).
|
||||||
|
|
||||||
|
For running the generation of the diagrams and images you can run one of the two python scripts like so (inside of the virtual environment):
|
||||||
|
|
||||||
|
```sh
|
||||||
|
python merfish.py
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
## Document generation
|
||||||
|
|
||||||
|
The document is written using [typst](https://typst.app) and can be compiled into a pdf file using:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
typst c doc/paper.typ
|
||||||
|
```
|
||||||
|
|
||||||
|
The generated pdf file will then be generated into the `doc` directory named `paper.pdf`.
|
||||||
188
example.py
Normal file
188
example.py
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
# import squidpy as sq
|
||||||
|
from graph_tool.all import *
|
||||||
|
|
||||||
|
from src import centrality
|
||||||
|
from src import plot
|
||||||
|
from src import fitting
|
||||||
|
|
||||||
|
|
||||||
|
def merfish():
|
||||||
|
"""
|
||||||
|
Merfish dataset from `squidpy`.
|
||||||
|
"""
|
||||||
|
adata = sq.datasets.merfish()
|
||||||
|
adata = adata[adata.obs.Bregma == -9].copy()
|
||||||
|
return adata
|
||||||
|
|
||||||
|
|
||||||
|
def mibitof():
|
||||||
|
"""
|
||||||
|
Mibitof dataset from `squidpy`.
|
||||||
|
"""
|
||||||
|
adata = sq.datasets.mibitof()
|
||||||
|
return adata
|
||||||
|
|
||||||
|
def random_graph(n=5000, seed=None):
|
||||||
|
"""
|
||||||
|
Uniformly random point cloud generation.
|
||||||
|
`n` [int] Number of points to generate. Default 5000 seems like a good starting point in point density and corresponding runtime for the subsequent calculations.
|
||||||
|
@return [numpy.ndarray] Array of shape(n, 2) containing the coordinates for each point of the generated point cloud.
|
||||||
|
"""
|
||||||
|
if seed is None:
|
||||||
|
import secrets
|
||||||
|
seed = secrets.randbits(128)
|
||||||
|
rng = np.random.default_rng(seed=seed)
|
||||||
|
return rng.random((n, 2)), seed
|
||||||
|
|
||||||
|
|
||||||
|
def spatial_graph(adata):
|
||||||
|
"""
|
||||||
|
Generate the spatial graph using delaunay for the given `adata`.
|
||||||
|
`adata` will contain the calculated spatial graph contents in the keys
|
||||||
|
`adata.obps['spatial_distances']` and `adata.obsm['spatial']` afterwards too.
|
||||||
|
@return [Graph] generated networkx graph from adata['spatial_distances']
|
||||||
|
"""
|
||||||
|
g, pos = graph_tool.generation.triangulation(adata, type="delaunay")
|
||||||
|
g.vp["pos"] = pos
|
||||||
|
weight = g.new_edge_property("double")
|
||||||
|
for e in g.edges():
|
||||||
|
weight[e] = math.sqrt(sum(map(abs, pos[e.source()].a - pos[e.target()].a)))**2
|
||||||
|
return g, weight
|
||||||
|
|
||||||
|
# generate spatial graph from a given dataset
|
||||||
|
# g, weight = spatial_graph(merfish().obsm['spatial'])
|
||||||
|
for i in range(1, 10):
|
||||||
|
points, seed = random_graph()
|
||||||
|
g, weight = spatial_graph(points)
|
||||||
|
g = GraphView(g)
|
||||||
|
|
||||||
|
# calculate centrality values
|
||||||
|
vp = closeness(g, weight=weight)
|
||||||
|
vp.a = np.nan_to_num(vp.a) # correct floating point values
|
||||||
|
# ep.a = np.nan_to_num(ep.a) # correct floating point values
|
||||||
|
|
||||||
|
# calculate convex hull
|
||||||
|
convex_hull = centrality.convex_hull(g)
|
||||||
|
|
||||||
|
# plot graph with convex_hull
|
||||||
|
fig = plt.figure(figsize=(15, 5))
|
||||||
|
ax0, ax1 = fig.subplots(1, 2)
|
||||||
|
plot.graph_plot(fig, ax0, g, vp, convex_hull, f"Random Graph (seed: {seed})\nCloseness")
|
||||||
|
|
||||||
|
# generate model based on convex hull and associated centrality values
|
||||||
|
quantification = plot.quantification_data(g, vp, convex_hull)
|
||||||
|
|
||||||
|
# optimize model's piece-wise linear function
|
||||||
|
d = quantification[:, 0]
|
||||||
|
C = quantification[:, 1]
|
||||||
|
m_opt, c0_opt, b_opt = fitting.fit_piece_wise_linear(d, C)
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# should this be part of the plotting function itself, it should not be necessary for me to do this
|
||||||
|
d_curve = np.linspace(min(d), max(d), 500)
|
||||||
|
C_curve = np.piecewise(
|
||||||
|
d_curve,
|
||||||
|
[d_curve <= b_opt, d_curve > b_opt],
|
||||||
|
[lambda x: m_opt * x + c0_opt, lambda x: m_opt * b_opt + c0_opt]
|
||||||
|
)
|
||||||
|
# plot model containing modeled piece-wise linear function
|
||||||
|
plot.quantification_plot(ax1, quantification, d_curve, C_curve, 'Models')
|
||||||
|
|
||||||
|
# linear regression model
|
||||||
|
m_reg, c_reg = fitting.fit_linear_regression(d, C)
|
||||||
|
x = np.linspace(min(d), max(d), 500)
|
||||||
|
y = m_reg * x + c_reg
|
||||||
|
ax1.plot(x, y, color='k', linewidth=1, label="Simple Linear Regression")
|
||||||
|
ax1.legend()
|
||||||
|
|
||||||
|
fig.savefig(f"random_point_clouds/{i}_closeness.svg", format='svg')
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# calculate centrality values
|
||||||
|
vp, ep = betweenness(g, weight=weight)
|
||||||
|
vp.a = np.nan_to_num(vp.a) # correct floating point values
|
||||||
|
# ep.a = np.nan_to_num(ep.a) # correct floating point values
|
||||||
|
|
||||||
|
# calculate convex hull
|
||||||
|
convex_hull = centrality.convex_hull(g)
|
||||||
|
|
||||||
|
# plot graph with convex_hull
|
||||||
|
fig = plt.figure(figsize=(15, 5))
|
||||||
|
ax0, ax1 = fig.subplots(1, 2)
|
||||||
|
plot.graph_plot(fig, ax0, g, vp, convex_hull, f"Random Graph (seed: {seed})\nBetweenness")
|
||||||
|
|
||||||
|
# generate model based on convex hull and associated centrality values
|
||||||
|
quantification = plot.quantification_data(g, vp, convex_hull)
|
||||||
|
|
||||||
|
# optimize model's piece-wise linear function
|
||||||
|
d = quantification[:, 0]
|
||||||
|
C = quantification[:, 1]
|
||||||
|
m_opt, c0_opt, b_opt = fitting.fit_piece_wise_linear(d, C)
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# should this be part of the plotting function itself, it should not be necessary for me to do this
|
||||||
|
d_curve = np.linspace(min(d), max(d), 500)
|
||||||
|
C_curve = np.piecewise(
|
||||||
|
d_curve,
|
||||||
|
[d_curve <= b_opt, d_curve > b_opt],
|
||||||
|
[lambda x: m_opt * x + c0_opt, lambda x: m_opt * b_opt + c0_opt]
|
||||||
|
)
|
||||||
|
# plot model containing modeled piece-wise linear function
|
||||||
|
plot.quantification_plot(ax1, quantification, d_curve, C_curve, 'Models')
|
||||||
|
|
||||||
|
# linear regression model
|
||||||
|
m_reg, c_reg = fitting.fit_linear_regression(d, C)
|
||||||
|
x = np.linspace(min(d), max(d), 500)
|
||||||
|
y = m_reg * x + c_reg
|
||||||
|
ax1.plot(x, y, color='k', linewidth=1, label="Simple Linear Regression")
|
||||||
|
ax1.legend()
|
||||||
|
|
||||||
|
fig.savefig(f"random_point_clouds/{i}_betweenness.svg", format='svg')
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# calculate centrality values
|
||||||
|
vp = pagerank(g, weight=weight)
|
||||||
|
vp.a = np.nan_to_num(vp.a) # correct floating point values
|
||||||
|
# ep.a = np.nan_to_num(ep.a) # correct floating point values
|
||||||
|
|
||||||
|
# calculate convex hull
|
||||||
|
convex_hull = centrality.convex_hull(g)
|
||||||
|
|
||||||
|
# plot graph with convex_hull
|
||||||
|
fig = plt.figure(figsize=(15, 5))
|
||||||
|
ax0, ax1 = fig.subplots(1, 2)
|
||||||
|
plot.graph_plot(fig, ax0, g, vp, convex_hull, f"Random Graph (seed: {seed})\nPageRank")
|
||||||
|
|
||||||
|
# generate model based on convex hull and associated centrality values
|
||||||
|
quantification = plot.quantification_data(g, vp, convex_hull)
|
||||||
|
|
||||||
|
# optimize model's piece-wise linear function
|
||||||
|
d = quantification[:, 0]
|
||||||
|
C = quantification[:, 1]
|
||||||
|
m_opt, c0_opt, b_opt = fitting.fit_piece_wise_linear(d, C)
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
# should this be part of the plotting function itself, it should not be necessary for me to do this
|
||||||
|
d_curve = np.linspace(min(d), max(d), 500)
|
||||||
|
C_curve = np.piecewise(
|
||||||
|
d_curve,
|
||||||
|
[d_curve <= b_opt, d_curve > b_opt],
|
||||||
|
[lambda x: m_opt * x + c0_opt, lambda x: m_opt * b_opt + c0_opt]
|
||||||
|
)
|
||||||
|
# plot model containing modeled piece-wise linear function
|
||||||
|
plot.quantification_plot(ax1, quantification, d_curve, C_curve, 'Models')
|
||||||
|
|
||||||
|
# linear regression model
|
||||||
|
m_reg, c_reg = fitting.fit_linear_regression(d, C)
|
||||||
|
x = np.linspace(min(d), max(d), 500)
|
||||||
|
y = m_reg * x + c_reg
|
||||||
|
ax1.plot(x, y, color='k', linewidth=1, label="Simple Linear Regression")
|
||||||
|
ax1.legend()
|
||||||
|
|
||||||
|
fig.savefig(f"random_point_clouds/{i}_pagerank.svg", format='svg')
|
||||||
4
requirements.txt
Normal file
4
requirements.txt
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
gurobipy
|
||||||
|
graph-tools
|
||||||
|
numpy
|
||||||
|
matplotlib
|
||||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
80
src/centrality.py
Normal file
80
src/centrality.py
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from src import plot
|
||||||
|
|
||||||
|
def convex_hull(G):
|
||||||
|
# gift wrapping algorithm
|
||||||
|
pos = G.vp["pos"]
|
||||||
|
x = []
|
||||||
|
y = []
|
||||||
|
for v in G.vertices():
|
||||||
|
# print(pos[v])
|
||||||
|
ver = pos[v]
|
||||||
|
x.append(ver[0])
|
||||||
|
y.append(ver[1])
|
||||||
|
|
||||||
|
min_x = min(x)
|
||||||
|
min_y = min(y)
|
||||||
|
max_x = max(x)
|
||||||
|
max_y = max(y)
|
||||||
|
pos = np.stack((np.array(x), np.array(y)), axis=-1)
|
||||||
|
|
||||||
|
convex_hull = []
|
||||||
|
hull_point = np.array([min_x,pos[np.argmin(x)][1]])
|
||||||
|
convex_hull.append(hull_point)
|
||||||
|
endpoint = None
|
||||||
|
i = 0
|
||||||
|
while not plot.Vector.eql(endpoint, convex_hull[0]):
|
||||||
|
endpoint = pos[0]
|
||||||
|
for point in pos:
|
||||||
|
if plot.Vector.eql(endpoint, hull_point) or plot.Vector.cross_z(plot.Vector.vec(convex_hull[i], endpoint), plot.Vector.vec(convex_hull[i], point)) < 0:
|
||||||
|
endpoint = point
|
||||||
|
i += 1
|
||||||
|
hull_point = endpoint
|
||||||
|
convex_hull.append(hull_point)
|
||||||
|
return convex_hull
|
||||||
|
|
||||||
|
def correct(G, centrality, m_opt, c0_opt, b_opt):
|
||||||
|
"""
|
||||||
|
Correct a given centrality through the calculated model.
|
||||||
|
@param pos [Array-2d] Positions of points (i.e. `g.vp["pos"].get_2d_array()`)
|
||||||
|
@param centrality [VertexPropertyMap] Result of a centrality calculation of `graph-tool` on graph G
|
||||||
|
@param m_opt [Float] Model m value (slope of linear function)
|
||||||
|
@param c0_opt [Float] Model c0 value (offset of linear function)
|
||||||
|
@param b_opt [Float] Model b value (intersection point)
|
||||||
|
@return [VertexPropertyMap] Corrected centrality values based on @param centrality
|
||||||
|
"""
|
||||||
|
corrected_metric = {}
|
||||||
|
pos = G.vp["pos"]
|
||||||
|
x = []
|
||||||
|
y = []
|
||||||
|
for v in G.vertices():
|
||||||
|
# print(pos[v])
|
||||||
|
ver = pos[v]
|
||||||
|
x.append(ver[0])
|
||||||
|
y.append(ver[1])
|
||||||
|
|
||||||
|
keys = iter(centrality.a)
|
||||||
|
hull = convex_hull(x, y)
|
||||||
|
|
||||||
|
points = np.stack((np.array(x), np.array(y)), axis=-1)
|
||||||
|
for point in pos:
|
||||||
|
min_distance = math.inf
|
||||||
|
key = next(keys)
|
||||||
|
for edge in hull:
|
||||||
|
vector = plot.Vector.vec(point, edge)
|
||||||
|
distance = plot.Vector.vec_len(vector)
|
||||||
|
if distance < min_distance:
|
||||||
|
min_distance = distance
|
||||||
|
if min_distance <= b_opt:
|
||||||
|
# requires correction
|
||||||
|
value = centrality[key]
|
||||||
|
delta = (m_opt * b_opt) - (m_opt * min_distance)
|
||||||
|
corrected_metric[key] = value + delta
|
||||||
|
else:
|
||||||
|
# unaffected by boundary
|
||||||
|
corrected_metric[key] = centrality[key]
|
||||||
|
|
||||||
|
return corrected_metric
|
||||||
30
src/dataset.py
Normal file
30
src/dataset.py
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import networkx as nx
|
||||||
|
import squidpy as sq
|
||||||
|
|
||||||
|
# NOTE these shall be provided from the outside and not be inside of the library!
|
||||||
|
def merfish():
|
||||||
|
"""
|
||||||
|
Merfish dataset from `squidpy`.
|
||||||
|
"""
|
||||||
|
adata = sq.datasets.merfish()
|
||||||
|
adata = adata[adata.obs.Bregma == -9].copy()
|
||||||
|
return adata
|
||||||
|
|
||||||
|
|
||||||
|
def mibitof():
|
||||||
|
"""
|
||||||
|
Mibitof dataset from `squidpy`.
|
||||||
|
"""
|
||||||
|
adata = sq.datasets.mibitof()
|
||||||
|
return adata
|
||||||
|
|
||||||
|
|
||||||
|
def spatial_graph(adata):
|
||||||
|
"""
|
||||||
|
Generate the spatial graph using delaunay for the given `adata`.
|
||||||
|
`adata` will contain the calculated spatial graph contents in the keys
|
||||||
|
`adata.obps['spatial_distances']` and `adata.obsm['spatial']` afterwards too.
|
||||||
|
@return [Graph] generated networkx graph from adata['spatial_distances']
|
||||||
|
"""
|
||||||
|
sq.gr.spatial_neighbors(adata, delaunay=True, coord_type="generic")
|
||||||
|
return nx.from_scipy_sparse_array(adata.obsp['spatial_distances'])
|
||||||
101
src/fitting.py
Normal file
101
src/fitting.py
Normal file
@@ -0,0 +1,101 @@
|
|||||||
|
import gurobipy as gp
|
||||||
|
from gurobipy import GRB
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
# NOTE:
|
||||||
|
# Thank you Anna for providing her sources: https://github.com/uderhardtlab/truncated_graphs/blob/main/src/fit.py
|
||||||
|
|
||||||
|
|
||||||
|
def fit_piece_wise_linear(d, C, M=1000):
|
||||||
|
"""
|
||||||
|
Fits a piecewise linear function to the given data using optimization.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
d (array-like): Distance values.
|
||||||
|
C (array-like): Corresponding centrality values.
|
||||||
|
M (int, optional): Large constant for big-M constraints. Default is 1000.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: Optimized slope (m), intercept (c0), and breakpoint (b).
|
||||||
|
"""
|
||||||
|
n = len(d)
|
||||||
|
|
||||||
|
model = gp.Model()
|
||||||
|
m = model.addVar(vtype=GRB.CONTINUOUS, name="m") # Slope before breakpoint
|
||||||
|
|
||||||
|
c0 = model.addVar(vtype=GRB.CONTINUOUS, name="c0") # y-intercept
|
||||||
|
b = model.addVar(vtype=GRB.CONTINUOUS, lb=min(d), ub=max(d), name="b") # Breakpoint
|
||||||
|
|
||||||
|
z = model.addVars(n, vtype=GRB.BINARY, name="z")
|
||||||
|
epsilon = model.addVars(n, vtype=GRB.CONTINUOUS, name="epsilon")
|
||||||
|
|
||||||
|
model.setObjective(gp.quicksum(epsilon[i] * epsilon[i] for i in range(n)), GRB.MINIMIZE)
|
||||||
|
|
||||||
|
# Setting solver parameters for precision
|
||||||
|
model.setParam('OptimalityTol', 1e-4)
|
||||||
|
model.setParam('MIPGap', 0.01)
|
||||||
|
|
||||||
|
for i in range(n):
|
||||||
|
# Constraints enforcing piecewise linear fit
|
||||||
|
model.addConstr(epsilon[i] >= (m * d[i] + c0 - C[i]) - (1 - z[i]) * M)
|
||||||
|
model.addConstr(epsilon[i] >= -(m * d[i] + c0 - C[i]) - (1 - z[i]) * M)
|
||||||
|
|
||||||
|
model.addConstr(epsilon[i] >= (m * b + c0 - C[i]) - z[i] * M)
|
||||||
|
model.addConstr(epsilon[i] >= -(m * b + c0 - C[i]) - z[i] * M)
|
||||||
|
|
||||||
|
# Binary switch for piecewise behavior
|
||||||
|
model.addConstr(z[i] * M >= b - d[i])
|
||||||
|
model.addConstr((1 - z[i]) * M >= d[i] - b)
|
||||||
|
|
||||||
|
model.optimize()
|
||||||
|
return m.X, c0.X, b.X
|
||||||
|
|
||||||
|
|
||||||
|
def fit_linear_regression(d, C):
|
||||||
|
"""
|
||||||
|
Fits a simple linear regression model to the given data using optimization.
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
d (array-like): Distance values.
|
||||||
|
C (array-like): Corresponding centrality values.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
tuple: Optimized slope (beta) and intercept (alpha).
|
||||||
|
"""
|
||||||
|
n = len(d)
|
||||||
|
model = gp.Model()
|
||||||
|
|
||||||
|
alpha = model.addVar(vtype=GRB.CONTINUOUS, name="alpha")
|
||||||
|
beta = model.addVar(vtype=GRB.CONTINUOUS, name="beta")
|
||||||
|
|
||||||
|
model.setObjective(gp.quicksum((C[i] - alpha - beta * d[i])**2 for i in range(n)), GRB.MINIMIZE)
|
||||||
|
model.optimize()
|
||||||
|
return beta.X, alpha.X
|
||||||
|
|
||||||
|
|
||||||
|
def plot_piece_wise_linear(d, C, m_opt, c0_opt, b_opt, measure, n, t, path):
|
||||||
|
"""
|
||||||
|
Plots the original data and optimized piecewise linear fit.
|
||||||
|
"""
|
||||||
|
d_curve = np.linspace(min(d), max(d), 500)
|
||||||
|
C_curve = np.piecewise(
|
||||||
|
d_curve,
|
||||||
|
[d_curve <= b_opt, d_curve > b_opt],
|
||||||
|
[lambda x: m_opt * x + c0_opt, lambda x: m_opt * b_opt + c0_opt]
|
||||||
|
)
|
||||||
|
|
||||||
|
plt.scatter(d, C, color="blue", label="Original", alpha=0.5)
|
||||||
|
plt.plot(d_curve, C_curve, color="red", label="Optimized", linewidth=2)
|
||||||
|
plt.xlabel("Distance to border")
|
||||||
|
plt.ylabel(f"{measure.capitalize()} centrality")
|
||||||
|
plt.title(f"Optimized piece-wise linear fit for {t} graph and {measure} centrality")
|
||||||
|
plt.legend()
|
||||||
|
plt.savefig(path, format='svg')
|
||||||
|
|
||||||
|
fig, ax = plt.subplots()
|
||||||
|
ax.set_title(metric_name)
|
||||||
|
ax.set_xlabel('Distance to Bounding-Box')
|
||||||
|
ax.set_ylabel('Centrality')
|
||||||
|
ax.scatter(d, C, color='b', s=0.2)
|
||||||
|
fig.savefig(path, format='svg')
|
||||||
160
src/plot.py
Normal file
160
src/plot.py
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
import numpy as np
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import matplotlib.colors as mcolors
|
||||||
|
|
||||||
|
from matplotlib.collections import LineCollection
|
||||||
|
from src import centrality
|
||||||
|
|
||||||
|
class Vector:
|
||||||
|
"""
|
||||||
|
Small Vector class used for calculations of distances
|
||||||
|
"""
|
||||||
|
def cross_z(a, b):
|
||||||
|
return a[0] * b[1] - a[1] * b[0]
|
||||||
|
|
||||||
|
def vec(start, end):
|
||||||
|
"""
|
||||||
|
Create a new vector from `start` to `end`
|
||||||
|
@param start [Array-2d]
|
||||||
|
@param end [Array-2d]
|
||||||
|
@return [Array-2d]
|
||||||
|
"""
|
||||||
|
return [end[0]-start[0],end[1]-start[1]]
|
||||||
|
|
||||||
|
def eql(point, other):
|
||||||
|
"""
|
||||||
|
Check if `point` is equivalent to `other`.
|
||||||
|
"""
|
||||||
|
if point is None or other is None:
|
||||||
|
return False
|
||||||
|
return point[0] == other[0] and point[1] == other[1]
|
||||||
|
|
||||||
|
def vec_len(vector):
|
||||||
|
"""
|
||||||
|
Calculate the length of a `vector`
|
||||||
|
"""
|
||||||
|
return math.sqrt(vector[0]**2 + vector[1]**2)
|
||||||
|
|
||||||
|
|
||||||
|
def graph_plot(fig, ax, G, measures, convex_hull, name, show_edges=False):
|
||||||
|
"""
|
||||||
|
Plot a given graph.
|
||||||
|
@param G [Graph] networkx Graph
|
||||||
|
@param pos [Array-2d] position values of points
|
||||||
|
@param measures [Dict] Result of a centrality calculation of networkx on graph G that should be visualized
|
||||||
|
@param name [String] Name of the measurement used, which will be integrated into the generated plot
|
||||||
|
@param path [String] Path to store the generated plot as svg file
|
||||||
|
"""
|
||||||
|
pos = G.vp["pos"]
|
||||||
|
x = []
|
||||||
|
y = []
|
||||||
|
for v in G.vertices():
|
||||||
|
# print(pos[v])
|
||||||
|
ver = pos[v]
|
||||||
|
x.append(ver[0])
|
||||||
|
y.append(ver[1])
|
||||||
|
|
||||||
|
c = measures.get_array()
|
||||||
|
# convex hull -> Bounding-Box
|
||||||
|
ch = LineCollection([convex_hull], colors=['g'], linewidths=1)
|
||||||
|
ax.add_collection(ch)
|
||||||
|
if show_edges:
|
||||||
|
for e in G.edges():
|
||||||
|
ex = [pos[e.source()][0], pos[e.target()][0]]
|
||||||
|
ey = [pos[e.source()][1], pos[e.target()][1]]
|
||||||
|
ax.add_collection(LineCollection([np.column_stack([ex, ey])], colors=['k'], linewidths=0.1))
|
||||||
|
|
||||||
|
sc = ax.scatter(x, y, s=1, cmap=plt.cm.plasma, c=c) # map closeness values as color mapping on the verticies
|
||||||
|
ax.set_title(name)
|
||||||
|
fig.colorbar(sc, ax=ax)
|
||||||
|
|
||||||
|
|
||||||
|
def normalize_dict(d):
|
||||||
|
max = np.max(list(d.values()))
|
||||||
|
return {k: (v / max) for k, v in d.items()}
|
||||||
|
|
||||||
|
|
||||||
|
def quantification_data(G, measures, convex_hull):
|
||||||
|
"""
|
||||||
|
Create distance to metric relationship.
|
||||||
|
@param pos [Array-2d] Positions of points
|
||||||
|
@param metric [Dict] Result of a centrality calculation of networkx on graph G
|
||||||
|
@return [Array-2d] relationship ordered (acending) by distance
|
||||||
|
"""
|
||||||
|
quantification = []
|
||||||
|
pos = G.vp["pos"]
|
||||||
|
x = []
|
||||||
|
y = []
|
||||||
|
for v in G.vertices():
|
||||||
|
# print(pos[v])
|
||||||
|
ver = pos[v]
|
||||||
|
x.append(ver[0])
|
||||||
|
y.append(ver[1])
|
||||||
|
|
||||||
|
measures = measures.a
|
||||||
|
keys = iter(measures)
|
||||||
|
|
||||||
|
points = np.stack((np.array(x), np.array(y)), axis=-1)
|
||||||
|
for point in points:
|
||||||
|
min_distance = math.inf
|
||||||
|
key = next(keys)
|
||||||
|
for edge in convex_hull:
|
||||||
|
vector = Vector.vec(point, edge)
|
||||||
|
distance = Vector.vec_len(vector)
|
||||||
|
if distance < min_distance:
|
||||||
|
min_distance = distance
|
||||||
|
quantification.append([min_distance, key])
|
||||||
|
|
||||||
|
# sort by distance
|
||||||
|
quantification.sort(key=lambda entry: entry[0])
|
||||||
|
return np.array(quantification)
|
||||||
|
|
||||||
|
|
||||||
|
def quantification_plot(ax, quantification, d_curve, C_curve, metric_name):
|
||||||
|
"""
|
||||||
|
Plot relationship data.
|
||||||
|
@param data [Array-2d] see `data(pos, metric)`
|
||||||
|
@param d_curve linear function of the left side of the intersection point
|
||||||
|
@param C_curve constant function of the right side of the intersection point
|
||||||
|
@param metric_name [String] Name of the metric to be used as a title for the plot
|
||||||
|
@param path [String] Path to store the generated plot as svg file
|
||||||
|
"""
|
||||||
|
ax.set_title(metric_name)
|
||||||
|
ax.set_xlabel('Distance to Bounding-Box')
|
||||||
|
ax.set_ylabel('Centrality')
|
||||||
|
ax.scatter(quantification[:, 0], quantification[:, 1], c=quantification[:, 1], cmap=plt.cm.plasma, s=0.2)
|
||||||
|
if d_curve is not None and C_curve is not None:
|
||||||
|
ax.plot(d_curve, C_curve, color='g', linewidth=1, label='Piecewise Linear Model')
|
||||||
|
|
||||||
|
|
||||||
|
class Quantification:
|
||||||
|
def opt_data(pos, metric, b_opt):
|
||||||
|
"""
|
||||||
|
Optimal data relationship. Describe Points which are effected by the boundary.
|
||||||
|
@param pos [Array-2d] Positions of points
|
||||||
|
@param metric [Dict] Result of a centrality calculation of networkx on graph G
|
||||||
|
@param b_opt [int] Cross point distance.
|
||||||
|
@return [Dict] `metric` like dict (same keys) where True determines a node that is uneffected by the boundary, otherwise False.
|
||||||
|
"""
|
||||||
|
keys = iter(metric.keys())
|
||||||
|
boundary_effected = {}
|
||||||
|
hull = convex_hull(pos)
|
||||||
|
|
||||||
|
for point in pos:
|
||||||
|
min_distance = math.inf
|
||||||
|
key = next(keys)
|
||||||
|
for edge in hull:
|
||||||
|
vector = Vector.vec(point, edge)
|
||||||
|
distance = Vector.vec_len(vector)
|
||||||
|
if distance < min_distance:
|
||||||
|
min_distance = distance
|
||||||
|
if min_distance <= b_opt:
|
||||||
|
boundary_effected[key] = True
|
||||||
|
else:
|
||||||
|
boundary_effected[key] = False
|
||||||
|
|
||||||
|
# sort by distance
|
||||||
|
return boundary_effected
|
||||||
|
|
||||||
Reference in New Issue
Block a user