File size: 3,340 Bytes
e2b220f
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
"""
Unit tests for core functionality.
"""

import numpy as np
import pytest

import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))

from src.features.engineering import (
    compute_effective_pulses,
    compute_peak_fluence,
    compute_pulse_energy,
    compute_spatial_overlap,
)


class TestFeatureEngineering:
    """Test physics-informed feature computations."""

    def test_pulse_energy(self):
        """E = P / f: 100 mW / 100 kHz = 1 µJ"""
        result = compute_pulse_energy(
            np.array([100.0]), np.array([100.0])
        )
        np.testing.assert_allclose(result, [1.0], rtol=1e-5)

    def test_pulse_energy_vectorized(self):
        """Test vectorized computation."""
        powers = np.array([100, 200, 500])
        rates = np.array([100, 100, 1000])
        expected = np.array([1.0, 2.0, 0.5])
        result = compute_pulse_energy(powers, rates)
        np.testing.assert_allclose(result, expected, rtol=1e-5)

    def test_peak_fluence_gaussian(self):
        """Test Gaussian beam fluence formula: F = 2E/(π*r²)"""
        # 1 µJ pulse, 10 µm diameter (5 µm radius = 5e-4 cm)
        result = compute_peak_fluence(np.array([1.0]), np.array([10.0]))
        # Expected: 2 * 1e-6 / (π * (5e-4)²) = 2.546 J/cm²
        expected = 2 * 1e-6 / (np.pi * (5e-4) ** 2)
        np.testing.assert_allclose(result, [expected], rtol=1e-3)

    def test_effective_pulses(self):
        """N_eff = f_rep * d_spot / v_scan"""
        # 100 kHz, 10 µm spot, 1 mm/s
        result = compute_effective_pulses(
            np.array([100.0]),  # kHz
            np.array([10.0]),   # µm
            np.array([1.0]),    # mm/s
        )
        # Expected: 100*1000 Hz * 10e-3 mm / 1 mm/s = 1000
        np.testing.assert_allclose(result, [1000.0], rtol=1e-5)

    def test_overlap_high_speed(self):
        """High speed → low overlap."""
        result = compute_spatial_overlap(
            np.array([10.0]),   # mm/s (fast)
            np.array([100.0]),  # kHz
            np.array([10.0]),   # µm
        )
        # pitch = 10 / (100*1000) * 1000 = 0.1 µm → overlap ≈ 99%
        assert result[0] > 90

    def test_overlap_bounds(self):
        """Overlap should be clipped to [0, 99.9%]."""
        result = compute_spatial_overlap(
            np.array([1000.0]),  # Very fast
            np.array([1.0]),     # Low rep rate
            np.array([1.0]),     # Small spot
        )
        assert result[0] >= 0
        assert result[0] <= 100


class TestDataIntegrity:
    """Test data loading and preprocessing."""

    def test_no_negative_targets(self):
        """Physical targets should be non-negative."""
        # Generate dummy data matching expected format
        n = 100
        targets = np.random.exponential(5, size=(n, 5))
        assert np.all(targets >= 0)

    def test_feature_target_dimensions(self):
        """Feature and target arrays should have correct shapes."""
        n_samples, n_features, n_targets = 100, 21, 5
        X = np.random.randn(n_samples, n_features).astype(np.float32)
        y = np.random.randn(n_samples, n_targets).astype(np.float32)
        assert X.shape == (n_samples, n_features)
        assert y.shape == (n_samples, n_targets)


if __name__ == "__main__":
    pytest.main([__file__, "-v"])