Coverage for neuber_correction\performance_test.py: 0%
126 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-08-14 08:34 +0200
« prev ^ index » next coverage.py v7.9.2, created at 2025-08-14 08:34 +0200
1"""
2Performance tests for the NeuberCorrection class.
4This module provides comprehensive performance benchmarking for the Neuber correction
5functionality, including timing comparisons with and without memoization.
6"""
8import gc
9import random
10import sys
11import time
13from neuber_correction import (
14 MaterialForNeuberCorrection,
15 NeuberCorrection,
16 NeuberSolverSettings,
17)
20def format_time(seconds: float) -> str:
21 """Format time in seconds to a human-readable string."""
22 if seconds < 1e-6:
23 return f"{seconds * 1e9:.2f} ns"
24 if seconds < 1e-3:
25 return f"{seconds * 1e6:.2f} μs"
26 if seconds < 1:
27 return f"{seconds * 1e3:.2f} ms"
28 return f"{seconds:.3f} s"
31def get_memory_usage():
32 """Get current memory usage information."""
34 gc.collect() # Force garbage collection
36 # Count instances
37 instance_count = len(NeuberCorrection.instances)
38 total_cache_entries = sum(
39 len(instance.memoization_table)
40 for instance in NeuberCorrection.instances.values()
41 )
43 return {
44 "instance_count": instance_count,
45 "total_cache_entries": total_cache_entries,
46 }
49def benchmark_memoization_effectiveness():
50 """Benchmark the effectiveness of memoization with clear isolation."""
51 # Clear all existing instances to ensure clean state
52 NeuberCorrection.clear_all_instances()
54 # Check initial memory state
55 initial_memory = get_memory_usage()
57 print("NEUBER CORRECTION MEMOIZATION BENCHMARK")
58 print("=" * 60)
59 print(
60 f"Initial state: {initial_memory['instance_count']} instances, "
61 f"{initial_memory['total_cache_entries']} cache entries"
62 )
63 print()
65 # Create test material (S355 steel)
66 material = MaterialForNeuberCorrection(
67 yield_strength=315,
68 sigma_u=470,
69 elastic_mod=210000,
70 eps_u=0.12,
71 )
73 # Test stress values
74 stress_values = [400, 500, 600, 700, 800, 900, 1000]
76 print("Material: S355 Steel")
77 print(f"Test stresses: {stress_values}")
78 print()
80 # Test 1: No memoization (very high precision)
81 print("TEST 1: No Memoization (Precision = 1e-12)")
82 print("-" * 40)
84 settings_no_memo = NeuberSolverSettings(memoization_precision=1e-12)
85 neuber_no_memo = NeuberCorrection(material=material, settings=settings_no_memo)
87 # First run - all calculations
88 start_time = time.perf_counter()
89 for stress in stress_values:
90 neuber_no_memo.correct_stress_values([stress])
91 first_run_time = time.perf_counter() - start_time
93 # Second run - should recalculate everything
94 start_time = time.perf_counter()
95 for stress in stress_values:
96 neuber_no_memo.correct_stress_values([stress])
97 second_run_time = time.perf_counter() - start_time
99 print(f"First run: {format_time(first_run_time)}")
100 print(f"Second run: {format_time(second_run_time)}")
101 print(f"Cache entries: {len(neuber_no_memo.memoization_table)}")
102 print()
104 # Test 2: With memoization (realistic precision)
105 print("TEST 2: With Memoization (Precision = 0.1 MPa)")
106 print("-" * 40)
108 settings_with_memo = NeuberSolverSettings(memoization_precision=0.1)
109 neuber_with_memo = NeuberCorrection(material=material, settings=settings_with_memo)
111 # First run - all calculations
112 start_time = time.perf_counter()
113 for stress in stress_values:
114 neuber_with_memo.correct_stress_values([stress])
115 first_run_time = time.perf_counter() - start_time
117 # Second run - should use cache
118 start_time = time.perf_counter()
119 for stress in stress_values:
120 neuber_with_memo.correct_stress_values([stress])
121 second_run_time = time.perf_counter() - start_time
123 print(f"First run: {format_time(first_run_time)}")
124 print(f"Second run: {format_time(second_run_time)}")
125 print(f"Cache entries: {len(neuber_with_memo.memoization_table)}")
126 print(f"Speedup: {first_run_time / second_run_time:.2f}x")
127 print()
129 # Test 3: Precision-based cache hits
130 print("TEST 3: Precision-Based Cache Hits")
131 print("-" * 40)
133 settings_precision = NeuberSolverSettings(
134 memoization_precision=1.0
135 ) # 1 MPa precision
136 neuber_precision = NeuberCorrection(material=material, settings=settings_precision)
138 # Calculate for stress 500
139 start_time = time.perf_counter()
140 result1 = neuber_precision.correct_stress_values([500])[0]
141 time1 = time.perf_counter() - start_time
143 # Calculate for stress 500.5 (within 1 MPa precision)
144 start_time = time.perf_counter()
145 result2 = neuber_precision.correct_stress_values([500.5])[0]
146 time2 = time.perf_counter() - start_time
148 # Calculate for stress 502 (outside 1 MPa precision)
149 start_time = time.perf_counter()
150 result3 = neuber_precision.correct_stress_values([502])[0]
151 time3 = time.perf_counter() - start_time
153 print(f"Stress 500.0: {format_time(time1)} -> {result1:.2f} MPa")
154 print(
155 f"Stress 500.5: {format_time(time2)} -> {result2:.2f} MPa (cache hit: {result1 == result2})"
156 )
157 print(
158 f"Stress 502.0: {format_time(time3)} -> {result3:.2f} MPa (cache hit: {result1 == result3})"
159 )
160 print(f"Cache entries: {len(neuber_precision.memoization_table)}")
161 print()
163 # Test 4: Large dataset performance
164 print("TEST 4: Large Dataset Performance")
165 print("-" * 40)
167 # Generate 1000 stress values with many duplicates
168 random.seed(42) # For reproducible results
170 # Create a smaller set of unique values
171 unique_stresses = [500 + i * 10 for i in range(5000)] # 50 unique values
173 large_stress_list = []
174 for i in range(1000):
175 # Pick from the unique values (many duplicates)
176 large_stress_list.append(random.choice(unique_stresses))
178 # Test without memoization (very high precision = no cache hits)
179 settings_large_no_memo = NeuberSolverSettings(memoization_precision=1e-12)
180 neuber_large_no_memo = NeuberCorrection(
181 material=material, settings=settings_large_no_memo
182 )
184 start_time = time.perf_counter()
185 results_no_memo = neuber_large_no_memo.correct_stress_values(large_stress_list)
186 time_no_memo = time.perf_counter() - start_time
188 # Test with memoization (same precision, will benefit from duplicates)
189 settings_large_with_memo = NeuberSolverSettings(
190 memoization_precision=1e-12
191 ) # Same precision
192 neuber_large_with_memo = NeuberCorrection(
193 material=material, settings=settings_large_with_memo
194 )
196 start_time = time.perf_counter()
197 results_with_memo = neuber_large_with_memo.correct_stress_values(large_stress_list)
198 time_with_memo = time.perf_counter() - start_time
200 print(f"Dataset size: {len(large_stress_list)} stress values")
201 print(f"Without memoization: {format_time(time_no_memo)}")
202 print(f"With memoization: {format_time(time_with_memo)}")
203 print(f"Speedup: {time_no_memo / time_with_memo:.2f}x")
204 print(f"Cache entries: {len(neuber_large_with_memo.memoization_table)}")
205 print(
206 f"Results identical: {all(abs(a - b) < 1e-10 for a, b in zip(results_no_memo, results_with_memo))}"
207 )
208 print(
209 "Max difference: ",
210 max(abs(a - b) for a, b in zip(results_no_memo, results_with_memo)),
211 )
213 # Test 5: Memory efficiency
214 print("TEST 5: Memory Efficiency")
215 print("-" * 40)
217 # Test with different cache sizes
218 cache_sizes = [100, 500, 1000, 2000]
220 for size in cache_sizes:
221 settings_memory = NeuberSolverSettings(memoization_precision=0.1)
222 neuber_memory = NeuberCorrection(material=material, settings=settings_memory)
224 # Generate stress values
225 stress_values_memory = [200 + i * 0.1 for i in range(size)]
227 # Calculate all values
228 for stress in stress_values_memory:
229 neuber_memory.correct_stress_values([stress])
231 cache_size = len(neuber_memory.memoization_table)
232 memory_usage = sys.getsizeof(neuber_memory.memoization_table)
234 print(
235 f"Cache size {size:4d}: {cache_size:4d} entries, {memory_usage:6d} bytes "
236 f"({memory_usage/cache_size:.1f} bytes/entry)"
237 )
239 print()
241 # Check final memory state
242 final_memory = get_memory_usage()
243 print("=" * 60)
244 print("BENCHMARK COMPLETE")
245 print("=" * 60)
246 print(
247 f"Final state: {final_memory['instance_count']} instances, "
248 f"{final_memory['total_cache_entries']} cache entries"
249 )
250 print(
251 f"Memory growth: {final_memory['instance_count'] - initial_memory['instance_count']} instances, "
252 f"{final_memory['total_cache_entries'] - initial_memory['total_cache_entries']} cache entries"
253 )
256if __name__ == "__main__":
257 benchmark_memoization_effectiveness()