Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add unit tests for elementary modules #97

Merged
merged 9 commits into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions examples/generate_model_results_snapshot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import pickle
from pathlib import Path
from sklearn.datasets import make_blobs
import numpy as np
from artlib import ART1, ART2A, BayesianART, DualVigilanceART, EllipsoidART, FuzzyART, GaussianART, HypersphereART, QuadraticNeuronART


def model_factory(model_class, params):
"""
A factory function to initialize models, handling special cases.
"""
if model_class.__name__ == "DualVigilanceART":
# For DualVigilanceART, initialize with a base ART model
base_art = FuzzyART(params["rho"], params["alpha"], params["beta"])
return model_class(base_art, params["rho_lower_bound"])

# Default case for other models
return model_class(**params)


def cluster_and_store_results():
# Generate blob data for clustering
data, _ = make_blobs(n_samples=150, centers=3, cluster_std=0.50, random_state=0, shuffle=False)
print("Data has shape:", data.shape)

# Define the models and their parameters in a list of tuples
models_with_params = [
# (ART1, {"rho": 0.7, "beta": 1.0, "L": 1.0}),
(ART2A, {"rho": 0.2, "alpha": 0.0, "beta": 1.0}),
(BayesianART, {"rho": 7e-5, "cov_init": np.array([[1e-4, 0.0], [0.0, 1e-4]])}),
(DualVigilanceART, {"rho": 0.85, "alpha": 0.8, "beta": 1.0, "rho_lower_bound": 0.78}),
(EllipsoidART, {"rho": 0.01, "alpha": 0.0, "beta": 1.0, "r_hat": 0.65, "mu": 0.8}),
(FuzzyART, {"rho": 0.5, "alpha": 0.0, "beta": 1.0}),
(GaussianART, {"rho": 0.05, "sigma_init": np.array([0.5, 0.5])}),
(HypersphereART, {"rho": 0.5, "alpha": 0.0, "beta": 1.0, "r_hat": 0.8}),
(QuadraticNeuronART, {"rho": 0.9, "s_init": 0.9, "lr_b": 0.1, "lr_w": 0.1, "lr_s": 0.1}),
]

results = {}

for model_class, params in models_with_params:
# Instantiate the model
cls = model_factory(model_class, params)
model_name = model_class.__name__

# Prepare data
X = cls.prepare_data(data)
print(f"Prepared data for {model_name} has shape:", X.shape)

# Fit the model and predict clusters
labels = cls.fit_predict(X)

# Store the labels and params in a dictionary keyed by the model name
results[model_name] = {"params": params, "labels": labels}
print(f"{cls.n_clusters} clusters found for {model_name}")


# Save the results to a pickle file
current_file_path = Path(__file__).resolve().parent.parent
output_file = current_file_path / "unit_tests" / "cluster_results_snapshot.pkl"

# Ensure the output directory exists
output_file.parent.mkdir(parents=True, exist_ok=True)

# Save the results to the pickle file
with open(output_file, "wb") as f:
pickle.dump(results, f)

print("Results saved to cluster_results.pkl")

if __name__ == "__main__":
cluster_and_store_results()
Binary file added unit_tests/cluster_results_snapshot.pkl
Binary file not shown.
124 changes: 124 additions & 0 deletions unit_tests/test_ART1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
import pytest
import numpy as np
from artlib.elementary.ART1 import ART1

# Fixture to initialize an ART1 instance for testing
@pytest.fixture
def art_model():
rho = 0.7
beta = 0.5
L = 2.0
return ART1(rho=rho, beta=beta, L=L)

def test_initialization(art_model):
# Test that the model initializes correctly
assert art_model.params['rho'] == 0.7
assert art_model.params['beta'] == 0.5
assert art_model.params['L'] == 2.0

def test_validate_params():
# Test the validate_params method
valid_params = {
"rho": 0.7,
"beta": 0.5,
"L": 2.0
}
ART1.validate_params(valid_params)

invalid_params = {
"rho": -0.7, # Invalid vigilance parameter
"beta": -0.5, # Invalid beta
"L": 0.5 # Invalid L (must be >= 1)
}
with pytest.raises(AssertionError):
ART1.validate_params(invalid_params)

def test_validate_data(art_model):
# Test the validate_data method
binary_data = np.array([[1, 0], [0, 1]])
art_model.validate_data(binary_data)

non_binary_data = np.array([[0.5, 1.0], [1.2, 0.3]])
with pytest.raises(AssertionError):
art_model.validate_data(non_binary_data)

def test_category_choice(art_model):
# Test the category_choice method
art_model.dim_ = 2
i = np.array([1, 0])
w = np.array([0.5, 0.5, 1, 0]) # Mock weight with both bottom-up and top-down weights
params = {"rho": 0.7}

activation, _ = art_model.category_choice(i, w, params)
assert isinstance(activation, float)
assert activation == 0.5 # np.dot(i, [0.5, 0.5]) = 0.5

def test_match_criterion(art_model):
# Test the match_criterion method
art_model.dim_ = 2
i = np.array([1, 0])
w = np.array([0.5, 0.5, 1, 0]) # Mock weight with both bottom-up and top-down weights
params = {"rho": 0.7}
cache = {}

match_criterion, _ = art_model.match_criterion(i, w, params, cache=cache)
assert isinstance(match_criterion, float)
assert match_criterion == 1.0 # Intersection of i and top-down weight w_td: [1, 0] matches exactly with i

def test_update(art_model):
# Test the update method
art_model.dim_ = 2
i = np.array([1, 0])
w = np.array([0.5, 0.5, 1, 1]) # Mock weight with both bottom-up and top-down weights
params = {"L": 2.0, "beta": 0.5}

updated_weight = art_model.update(i, w, params)
assert len(updated_weight) == 4 # Bottom-up and top-down weights
assert np.array_equal(updated_weight[2:], np.array([1, 0])) # Top-down weights should match input i

def test_new_weight(art_model):
# Test the new_weight method
art_model.dim_ = 2
i = np.array([1, 0])
params = {"L": 2.0}

new_weight = art_model.new_weight(i, params)
assert len(new_weight) == 4 # Bottom-up and top-down weights
assert np.array_equal(new_weight[2:], i) # Top-down weights should be equal to input i

def test_get_cluster_centers(art_model):
# Test getting cluster centers
art_model.dim_ = 2
art_model.W = [np.array([0.5, 0.5, 1, 0]), np.array([0.3, 0.7, 0, 1])]
centers = art_model.get_cluster_centers()
assert len(centers) == 2
assert np.array_equal(centers[0], np.array([1, 0]))
assert np.array_equal(centers[1], np.array([0, 1]))

def test_fit(art_model):
# Test fitting the model
art_model.dim_ = 2
X = np.array([[1, 0], [0, 1], [1, 1]])
art_model.check_dimensions(X)
art_model.fit(X)

assert len(art_model.W) > 0 # Ensure that clusters were created

def test_partial_fit(art_model):
# Test partial_fit method
art_model.dim_ = 2
X = np.array([[1, 0], [0, 1]])
art_model.check_dimensions(X)
art_model.partial_fit(X)

assert len(art_model.W) > 0 # Ensure that clusters were partially fit

def test_predict(art_model):
# Test predict method
art_model.dim_ = 2
X = np.array([[1, 0], [0, 1]])
art_model.check_dimensions(X)
art_model.W = [np.array([0.5, 0.5, 1, 0]), np.array([0.3, 0.7, 0, 1])]

labels = art_model.predict(X)
assert len(labels) == 2
132 changes: 132 additions & 0 deletions unit_tests/test_ART2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import pytest
import numpy as np
from artlib.elementary.ART2 import ART2A


# Fixture to initialize an ART2A instance for testing
@pytest.fixture
def art_model():
rho = 0.7
alpha = 0.1
beta = 0.5
return ART2A(rho=rho, alpha=alpha, beta=beta)


def test_initialization(art_model):
# Test that the model initializes correctly
assert art_model.params['rho'] == 0.7
assert art_model.params['alpha'] == 0.1
assert art_model.params['beta'] == 0.5


def test_validate_params():
# Test the validate_params method
valid_params = {
"rho": 0.7,
"alpha": 0.1,
"beta": 0.5
}
ART2A.validate_params(valid_params)

invalid_params = {
"rho": -0.7, # Invalid vigilance parameter
"alpha": -0.1, # Invalid alpha
"beta": 1.5 # Invalid beta
}
with pytest.raises(AssertionError):
ART2A.validate_params(invalid_params)


def test_check_dimensions(art_model):
# Test the check_dimensions method
X = np.array([[0.1, 0.2], [0.3, 0.4]])
art_model.check_dimensions(X)

assert art_model.dim_ == 2


def test_category_choice(art_model):
# Test the category_choice method
art_model.dim_ = 2
i = np.array([0.2, 0.3])
w = np.array([0.25, 0.35]) # Mock weight
params = {"rho": 0.7}

activation, cache = art_model.category_choice(i, w, params)
assert 'activation' in cache
assert isinstance(activation, float)


def test_match_criterion(art_model):
# Test the match_criterion method
art_model.dim_ = 2
i = np.array([0.2, 0.3])
w = np.array([0.25, 0.35]) # Mock weight
params = {"alpha": 0.1}
cache = {"activation": 0.5}

match_criterion, new_cache = art_model.match_criterion(i, w, params, cache=cache)
assert match_criterion == 0.5 # Since activation is higher than uncommitted activation


def test_update(art_model):
# Test the update method
art_model.dim_ = 2
i = np.array([0.2, 0.3])
w = np.array([0.25, 0.35]) # Mock weight
params = {"beta": 0.5}
cache = {"activation": 0.5}

updated_weight = art_model.update(i, w, params, cache=cache)
assert len(updated_weight) == 2 # Check that the weight is updated
assert np.allclose(updated_weight, (0.5 * i + 0.5 * w)) # Check the update formula


def test_new_weight(art_model):
# Test the new_weight method
i = np.array([0.2, 0.3])
params = {"beta": 0.5}

new_weight = art_model.new_weight(i, params)
assert len(new_weight) == 2 # Check that the weight has two dimensions
assert np.array_equal(new_weight, i)


def test_get_cluster_centers(art_model):
# Test getting cluster centers
art_model.W = [np.array([0.2, 0.3]), np.array([0.4, 0.5])]
centers = art_model.get_cluster_centers()
assert len(centers) == 2
assert np.array_equal(centers[0], np.array([0.2, 0.3]))
assert np.array_equal(centers[1], np.array([0.4, 0.5]))


def test_fit(art_model):
# Test fitting the model
art_model.dim_ = 2
X = np.array([[0.1, 0.2], [0.3, 0.4], [0.5, 0.6]])
art_model.check_dimensions(X)
art_model.fit(X)

assert len(art_model.W) > 0 # Ensure that clusters were created


def test_partial_fit(art_model):
# Test partial_fit method
art_model.dim_ = 2
X = np.array([[0.1, 0.2], [0.3, 0.4]])
art_model.check_dimensions(X)
art_model.partial_fit(X)

assert len(art_model.W) > 0 # Ensure that clusters were partially fit


def test_predict(art_model):
# Test predict method
art_model.dim_ = 2
X = np.array([[0.1, 0.2], [0.3, 0.4]])
art_model.check_dimensions(X)
art_model.W = [np.array([0.1, 0.2]), np.array([0.3, 0.4])]

labels = art_model.predict(X)
assert len(labels) == 2
Loading
Loading