Download tests/integration/test_adversarial_vector_attack.py from Tatopenn/dense-Evolution: direct link, hf CLI and curl.
- Browser
- Download file 8.1 kB
-
https://huggingface.co/Tatopenn/dense-Evolution/resolve/main/tests/integration/test_adversarial_vector_attack.py
- Command line
-
hf download hf://Tatopenn/dense-Evolution/tests/integration/test_adversarial_vector_attack.py
-
curl -L -o test_adversarial_vector_attack.py https://huggingface.co/Tatopenn/dense-Evolution/resolve/main/tests/integration/test_adversarial_vector_attack.py
8.1 kB
| """ | |
| Tests for ia_utils.adversarial_vector_attack -- the gradient-based | |
| Phi-Trigger stress test (arXiv:2607.27465-inspired) for | |
| enhanced_dense_healing_hybrid. Verifies both the isolated | |
| craft_adversarial_healing_perturbation call AND the crafted | |
| perturbation's effect on the real end-to-end healing pipeline, not | |
| just the isolated trigger-magnitude proxy. | |
| """ | |
| import numpy as np | |
| import pytest | |
| from ia_utils.adversarial_vector_attack import craft_adversarial_healing_perturbation | |
| from ia_utils.vector_healing import enhanced_dense_healing_hybrid | |
| def _static_segment(seed, n=30, dim=8): | |
| """A near-constant sequence -- the Phi-Trigger should classify this | |
| as static (median-replaced) almost everywhere.""" | |
| rng = np.random.default_rng(seed) | |
| base = rng.normal(size=dim) * 0.3 | |
| return np.tile(base, (n, 1)) + rng.normal(size=(n, dim)) * 0.001 | |
| def _dynamic_segment(seed, n=30, dim=8): | |
| """Ordinary iid noise at a scale that keeps calculate_phi_ab's | |
| distance term within MAX_SEMANTIC_DISTANCE (avoids the clip- | |
| saturation edge case verified separately below) -- the Phi-Trigger | |
| should classify this as dynamic (kept as-is) almost everywhere.""" | |
| rng = np.random.default_rng(seed) | |
| return rng.normal(size=(n, dim)) * 0.3 | |
| class TestInputValidation: | |
| def test_rejects_target_idx_below_2(self): | |
| vettori = _dynamic_segment(0) | |
| with pytest.raises(ValueError, match="target_idx"): | |
| craft_adversarial_healing_perturbation(vettori, 1) | |
| def test_rejects_target_idx_at_or_beyond_length(self): | |
| vettori = _dynamic_segment(0) | |
| with pytest.raises(ValueError, match="target_idx"): | |
| craft_adversarial_healing_perturbation(vettori, len(vettori)) | |
| def test_rejects_unknown_direction(self): | |
| vettori = _dynamic_segment(0) | |
| with pytest.raises(ValueError, match="direction"): | |
| craft_adversarial_healing_perturbation(vettori, 15, direction="sideways") | |
| def test_rejects_non_positive_epsilon(self): | |
| vettori = _dynamic_segment(0) | |
| with pytest.raises(ValueError, match="epsilon"): | |
| craft_adversarial_healing_perturbation(vettori, 15, epsilon=0.0) | |
| class TestEpsilonBudget: | |
| def test_perturbation_norm_never_exceeds_epsilon(self): | |
| vettori = _dynamic_segment(0) | |
| for epsilon in (0.05, 0.2, 1.0, 3.0): | |
| result = craft_adversarial_healing_perturbation( | |
| vettori, 15, epsilon=epsilon, direction="flip_to_static") | |
| assert result["perturbation_norm"] <= epsilon + 1e-9 | |
| def test_larger_epsilon_is_never_worse_than_smaller(self): | |
| # BUG FIX regression: step_size used to scale with epsilon | |
| # (2*epsilon/n_steps), which made a LARGER budget overshoot and | |
| # converge to a WORSE (higher, for flip_to_static) final | |
| # magnitude than a smaller budget -- the opposite of what any | |
| # correct optimizer should do (more room to move should never | |
| # hurt). Verified directly: final_magnitude must be | |
| # non-increasing as epsilon grows, for a fixed seed/target. | |
| vettori = _dynamic_segment(0) | |
| epsilons = [0.1, 0.3, 0.5, 1.0, 2.0, 3.0] | |
| magnitudes = [] | |
| for epsilon in epsilons: | |
| result = craft_adversarial_healing_perturbation( | |
| vettori, 15, epsilon=epsilon, n_steps=50, direction="flip_to_static") | |
| magnitudes.append(result["final_magnitude"]) | |
| for earlier, later in zip(magnitudes, magnitudes[1:]): | |
| assert later <= earlier + 1e-9, ( | |
| f"larger epsilon gave a worse (higher) final magnitude: {magnitudes}") | |
| class TestFlipToStatic: | |
| """Suppression direction: push an originally-dynamic (kept-as-is) | |
| point across the threshold so the trigger discards it as noise.""" | |
| def test_flips_the_isolated_trigger_decision(self): | |
| vettori = _dynamic_segment(0) | |
| result = craft_adversarial_healing_perturbation( | |
| vettori, 15, epsilon=1.0, n_steps=50, direction="flip_to_static") | |
| assert result["original_trigger_active"] is True | |
| assert result["success"] is True | |
| assert result["final_trigger_active"] is False | |
| assert result["final_magnitude"] < result["original_magnitude"] | |
| def test_flips_the_real_end_to_end_healing_decision(self): | |
| # The point of this utility: the crafted perturbation must | |
| # change what the REAL enhanced_dense_healing_hybrid pipeline | |
| # does, not just an isolated proxy computation. | |
| vettori = _dynamic_segment(0) | |
| target_idx = 15 | |
| result = craft_adversarial_healing_perturbation( | |
| vettori, target_idx, epsilon=1.0, n_steps=50, direction="flip_to_static") | |
| assert result["success"] is True | |
| healed_orig, _ = enhanced_dense_healing_hybrid(vettori.copy()) | |
| healed_pert, _ = enhanced_dense_healing_hybrid(result["perturbed_vettori"].copy()) | |
| # Originally kept as-is (dynamic): healed output equals raw input. | |
| assert np.allclose(healed_orig[target_idx], vettori[target_idx]) | |
| # After the attack: median-replaced instead, so it must differ | |
| # from the (perturbed) input that was fed in. | |
| assert not np.allclose(healed_pert[target_idx], result["perturbed_vettori"][target_idx]) | |
| class TestFlipToDynamic: | |
| """Evasion direction: push an originally-static (median-replaced) | |
| point across the threshold so the trigger keeps it as-is instead.""" | |
| def test_flips_the_isolated_trigger_decision(self): | |
| vettori = _static_segment(1) | |
| result = craft_adversarial_healing_perturbation( | |
| vettori, 15, epsilon=1.0, n_steps=50, direction="flip_to_dynamic") | |
| assert result["original_trigger_active"] is False | |
| assert result["success"] is True | |
| assert result["final_trigger_active"] is True | |
| assert result["final_magnitude"] > result["original_magnitude"] | |
| def test_flips_the_real_end_to_end_healing_decision(self): | |
| vettori = _static_segment(1) | |
| target_idx = 15 | |
| result = craft_adversarial_healing_perturbation( | |
| vettori, target_idx, epsilon=1.0, n_steps=50, direction="flip_to_dynamic") | |
| assert result["success"] is True | |
| healed_orig, _ = enhanced_dense_healing_hybrid(vettori.copy()) | |
| healed_pert, _ = enhanced_dense_healing_hybrid(result["perturbed_vettori"].copy()) | |
| # Originally median-replaced (static): healed output must NOT | |
| # equal the raw (noisy) input at that index. | |
| assert not np.allclose(healed_orig[target_idx], vettori[target_idx]) | |
| # After the attack: evades the healer, passes through unhealed, | |
| # so the healed output must equal the (perturbed) input exactly. | |
| assert np.allclose(healed_pert[target_idx], result["perturbed_vettori"][target_idx]) | |
| class TestGradientSaturationEdgeCase: | |
| """calculate_phi_ab clips to [0, 1] -- when the clip saturates (the | |
| unclipped weighted sum falls outside that range), the local | |
| gradient is exactly zero, so a gradient-based attack cannot move at | |
| all from that starting point. This is a real property of the | |
| underlying formula (see dense_evolution.healing), not a bug in the | |
| attack -- verified directly that it's detectable via | |
| perturbation_norm == 0 with success == False, not silently | |
| misreported as a "found nothing better" result.""" | |
| def test_saturated_phi_ab_gives_zero_perturbation_and_no_success(self): | |
| rng = np.random.default_rng(0) | |
| n, dim = 30, 8 | |
| # Unscaled noise: distance_A_B regularly exceeds | |
| # MAX_SEMANTIC_DISTANCE=sqrt(2), saturating phi_ab's clip to 0. | |
| vettori = rng.normal(size=(n, dim)) | |
| result = craft_adversarial_healing_perturbation( | |
| vettori, 15, epsilon=0.15, n_steps=30, direction="flip_to_dynamic") | |
| assert result["perturbation_norm"] == 0.0 | |
| assert result["success"] is False | |