argument-backend / gradual /computations.py
youssef
fixed part 3 backend
b6710f4
import numpy as np
from scipy.spatial import ConvexHull, QhullError
from .h_categorizer import h_categorizer
def dict_to_vector(A, d):
"""Converts a dictionary {arg: value} into a numpy vector following the order of A."""
return np.array([d[a] for a in A], dtype=float)
def sample_and_compute_X(
A,
R,
epsilon=1e-4,
max_iter=1000,
n_samples=10000,
seed=42,
controlled_args=None
):
"""Generates n_samples random weight vectors and computes corresponding h-Categorizer results."""
rng = np.random.default_rng(seed)
X = np.zeros((n_samples, len(A)), dtype=float)
for i in range(n_samples):
w = dict(zip(A, rng.random(len(A))))
# Override controlled arguments if specified
if controlled_args:
for arg, value in controlled_args.items():
w[arg] = value
HC = h_categorizer(A, R, w, max_iter, epsilon)
X[i, :] = dict_to_vector(A, HC)
return X
def _safe_hull(points, qhull_opts="QJ", jitter=1e-8):
"""
Try to compute a convex hull robustly.
Uses 'QJ' (joggle) and adds slight random jitter if needed.
Returns None if still degenerate.
"""
try:
return ConvexHull(points, qhull_options=qhull_opts)
except QhullError:
try:
pts = points + jitter * np.random.randn(*points.shape)
return ConvexHull(pts, qhull_options=qhull_opts)
except QhullError:
return None
# def compute_gradual_semantics(
# A,
# R,
# n_samples=1000,
# val_axes=None,
# controlled_args=None,
# epsilon=1e-4,
# max_iter=1000
# ):
# """Compute samples and convex hull information for the given argumentation framework."""
# X_res = sample_and_compute_X(
# A, R, epsilon, max_iter, n_samples, controlled_args=controlled_args
# )
# # Case 1D
# if len(A) == 1:
# axes = [A[0]]
# hull = _safe_hull(X_res)
# dim = 1
# return dim, axes, X_res, hull
# # Case 2D
# if len(A) == 2:
# axes = A[:2]
# hull = _safe_hull(X_res)
# dim = 2
# return dim, axes, X_res, hull
# # Case ≥ 3D → project on chosen axes
# axes = val_axes if val_axes else A[:3]
# idx = [A.index(ax) for ax in axes]
# Xp = X_res[:, idx]
# hull = _safe_hull(Xp)
# dim = 3
# return dim, axes, Xp, hull
def compute_gradual_space(num_args, R, n_samples, axes=None, controlled_args=None, epsilon=1e-4, max_iter=1000):
"""
Compute the convex hull (acceptability degree space) for the weighted h-categorizer.
Returns (num_args, hull_volume, hull_area, hull_points, samples, axes)
"""
# Generate argument labels A, B, C, ...
A = [chr(ord("A") + i) for i in range(num_args)]
# 1. Sample and compute semantics
X_res = sample_and_compute_X(
A, R, epsilon, max_iter, n_samples, controlled_args=controlled_args
)
# 2. Handle projections depending on argument count
if num_args == 1:
dim = 1
axes_used = [A[0]]
hull_points = np.array([[np.min(X_res)], [np.max(X_res)]])
hull_volume = float(np.max(X_res) - np.min(X_res))
hull_area = None
return num_args, hull_volume, hull_area, hull_points.tolist(), X_res.tolist(), axes_used
if num_args == 2:
dim = 2
axes_used = A[:2]
hull = _safe_hull(X_res)
if hull is None:
hull_volume = 0.0
hull_area = 0.0
hull_points = []
else:
hull_volume = float(hull.volume)
hull_area = float(hull.area)
hull_points = hull.points[hull.vertices].tolist()
return num_args, hull_volume, hull_area, hull_points, X_res.tolist(), axes_used
# num_args >= 3
axes_used = axes if axes else A[:3]
idx = [A.index(ax) for ax in axes_used]
Xp = X_res[:, idx]
hull = _safe_hull(Xp)
if hull is None:
hull_volume = 0.0
hull_area = 0.0
hull_points = []
else:
hull_volume = float(hull.volume)
hull_area = float(hull.area)
hull_points = hull.points[hull.vertices].tolist()
return num_args, hull_volume, hull_area, hull_points, Xp.tolist(), axes_used