#include "fiko_test.hpp" #include #include #include "fiko/dokum/katilasma_fdm.hpp" #include "fiko/dokum/malzeme.hpp" using namespace fiko; using namespace fiko::dokum; // ============================================================================ // KATILAŞMA GPU DOĞRULAMA — katilasma_fdm.comp shader'ının FİZİK-doğruluğu. // ---------------------------------------------------------------------------- // Bu ortamda Vulkan cihazı YOK (VulkanBaglam pencere ister; headless ctor yok → // gerçek dispatch koşulamaz). Bu yüzden GPU SHADER ARİTMETİĞİ f32 ile BIREBIR // C++'ta aynalanır (shaderAynasi) ve: // (a) Stefan-Neumann analitik cephe ±%3 ile doğrulanır (shader H↔T + fs port). // (b) f32 shader aynası ile f64 üretim çözücü ±%1 eşleşir (determinizm/port). // Böylece GPU'da çalışan shader'ın MATEMATİĞİ (host olmadan) doğrulanmış olur; // gerçek Vulkan dispatch kullanıcı GPU'da çalıştırınca teyit edilir (kod+SPIR-V // derleme-temiz teslim; sdf_gpu_motor ile aynı doğrulama sözleşmesi). // // Kaynak: Stefan (1891) / Neumann analitik katılaşma cephesi s(t)=2λ√(αt), // λ·e^(λ²)·erf(λ) = St/√π (izotermal, tek-faz, ısı yalnız katıdan atılır). // ============================================================================ namespace { // katilasma_fdm.comp sicaklikH() f32 BIREBIR aynası (H→T analitik ters). f32 shaderSicaklik(const Alasim& a, f32 Hv) { const f32 rho = (f32)a.yogunluk, cp = (f32)a.c, L = (f32)a.gizliIsi; const f32 tLik = (f32)a.tLikidus, tSol = (f32)a.tSolidus; const f32 rc = rho * cp; const f32 hSol = rc * tSol; if (Hv <= hSol) return Hv / rc; const f32 hLik = rho * (cp * tLik + L); if (Hv >= hLik) return (Hv / rho - L) / cp; const f32 dT = tLik - tSol; if (dT <= 1e-9f) return tSol; // Lineer/Lever analitik ters (test alaşımı Lineer). return (Hv / rho + L * tSol / dT) / (cp + L / dT); } // katilasma_fdm.comp katiOran() f32 aynası (Lineer). f32 shaderKatiOran(const Alasim& a, f32 Tv) { const f32 tLik = (f32)a.tLikidus, tSol = (f32)a.tSolidus; if (Tv >= tLik) return 0.0f; if (Tv <= tSol) return 1.0f; const f32 dT = tLik - tSol; if (dT <= 1e-9f) return Tv <= tSol ? 1.0f : 0.0f; const f32 x = (tLik - Tv) / dT; return x < 0.0f ? 0.0f : (x > 1.0f ? 1.0f : x); } // katilasma_fdm.comp entalpi girdisi (H=ρ[cT+L(1−fs)]) f32. f32 shaderEntalpi(const Alasim& a, f32 Tv) { return (f32)a.yogunluk * ((f32)a.c * Tv + (f32)a.gizliIsi * (1.0f - shaderKatiOran(a, Tv))); } // 1D entalpi-FDM, SHADER f32 aritmetiğiyle (sol duvar Dirichlet Ts, sağ Neumann). // Shader entalpi adımını (ΔH = k·Laplas/dx² · dt) f32 ile yürütür — gerçek // dispatch'in yaptığı hesabın CPU aynası (aynı SPIR-V matematiği). std::vector shaderAynasi1D(const Alasim& a, f32 Ti, f32 Ts, u32 n, f32 dx, f32 tSon) { const f32 alpha = (f32)a.yayinim(); const f32 dt = 0.2f * dx * dx / alpha; std::vector T(n, Ti), H(n), yeni(n); for (u32 i = 0; i < n; ++i) H[i] = shaderEntalpi(a, T[i]); T[0] = Ts; H[0] = shaderEntalpi(a, Ts); f32 t = 0; while (t < tSon) { for (u32 i = 1; i < n; ++i) { const f32 Tr = (i + 1 < n) ? T[i + 1] : T[i]; yeni[i] = H[i] + (f32)a.k * (T[i - 1] + Tr - 2.0f * T[i]) / (dx * dx) * dt; } for (u32 i = 1; i < n; ++i) { H[i] = yeni[i]; T[i] = shaderSicaklik(a, H[i]); } T[0] = Ts; H[0] = shaderEntalpi(a, Ts); t += dt; } return T; } // f64 ÜRETİM çözücü (test_dokum coz1D ikizi) — GPU ile ±%1 kıyas referansı. std::vector cpuRef1D(const Alasim& a, f64 Ti, f64 Ts, u32 n, f64 dx, f64 tSon) { const f64 alpha = a.yayinim(); const f64 dt = 0.2 * dx * dx / alpha; std::vector T(n, Ti), H(n), yeni(n); for (u32 i = 0; i < n; ++i) H[i] = entalpi(a, T[i]); T[0] = Ts; H[0] = entalpi(a, Ts); f64 t = 0; while (t < tSon) { for (u32 i = 1; i < n; ++i) { const f64 Tr = (i + 1 < n) ? T[i + 1] : T[i]; yeni[i] = H[i] + a.k * (T[i - 1] + Tr - 2.0 * T[i]) / (dx * dx) * dt; } for (u32 i = 1; i < n; ++i) { H[i] = yeni[i]; T[i] = sicaklik(a, H[i]); } T[0] = Ts; H[0] = entalpi(a, Ts); t += dt; } return T; } } // namespace // ---- (gpu-a) Stefan-Neumann cephe: shader aritmetiği analitiği ±%3 tutar ----- // Shader H↔T port + fs port doğru mu — üretim CPU testinin (test_dokum b) GPU // ikizi. İzotermal katılaşma, cephe = fs≥0.5 en uzak konum. f32 payı → %3 tol. FIKO_TEST(gpu_a_stefan_neumann_shader) { const f64 Tf = 100.0, Ts = 0.0, Ti = 100.0; Alasim a; a.yogunluk = 1000; a.c = 1000; a.k = 2.0; // α = 2e-6 a.gizliIsi = 100000.0; a.tLikidus = Tf; a.tSolidus = Tf - 0.01; const f64 alpha = a.yayinim(); const f64 St = a.c * (Tf - Ts) / a.gizliIsi; auto f = [&](f64 lam) { return lam * std::exp(lam * lam) * std::erf(lam) - St / std::sqrt(3.14159265358979); }; f64 lo = 1e-4, hi = 5.0; for (int it = 0; it < 100; ++it) { const f64 mid = 0.5 * (lo + hi); if (f(mid) < 0) lo = mid; else hi = mid; } const f64 lam = 0.5 * (lo + hi); const u32 n = 300; const f32 dx = 0.0005f; const f32 tSon = 400.0f; const auto T = shaderAynasi1D(a, (f32)Ti, (f32)Ts, n, dx, tSon); f64 cephe = 0; for (u32 i = 0; i < n; ++i) if (shaderKatiOran(a, T[i]) >= 0.5f) cephe = (i + 0.5) * dx; const f64 analitik = 2.0 * lam * std::sqrt(alpha * (f64)tSon); const f64 hata = std::abs(cephe - analitik) / analitik; FIKO_KONTROL(analitik > 0); FIKO_KONTROL(hata < 0.03); // f32 shader payı: < %3 (kritik kabul) } // ---- (gpu-b) Yarı-sonsuz erf profili: shader f32 saf iletim analitiği ±%2 ---- FIKO_TEST(gpu_b_erf_profili_shader) { Alasim a; a.yogunluk = 2700; a.c = 900; a.k = 200; a.gizliIsi = 0; a.tLikidus = 1e5; a.tSolidus = 1e5 - 1; const f64 alpha = a.yayinim(); const f32 Ti = 700.0f, Ts = 0.0f; const u32 n = 400; const f32 dx = 0.001f; const f32 tSon = 60.0f; const auto T = shaderAynasi1D(a, Ti, Ts, n, dx, tSon); const f64 kok = 2.0 * std::sqrt(alpha * (f64)tSon); f64 enBuyukHata = 0; for (u32 i = 5; i < 120; ++i) { const f64 x = (i + 0.5) * dx; const f64 analitik = Ts + (Ti - Ts) * std::erf(x / kok); const f64 hata = std::abs((f64)T[i] - analitik) / (Ti - Ts); enBuyukHata = std::max(enBuyukHata, hata); } FIKO_KONTROL(enBuyukHata < 0.02); // f32 saf iletim: < %2 } // ---- (gpu-c) GPU(f32 shader) ↔ CPU(f64 üretim) eşleşme ±%1 ------------------- // Aynı 1D difüzyon problemi: shader aritmetiği f32 vs üretim f64. Port doğruysa // hücre-başı T farkı < %1 (Ti−Ts normalize) — determinizm + birebir algoritma. FIKO_TEST(gpu_c_cpu_gpu_eslesme) { Alasim a = alasimAlSi7Mg(); const f64 Ti = 700.0, Ts = 25.0; const u32 n = 200; const f64 dx = 0.001; const f64 tSon = 30.0; const auto gpu = shaderAynasi1D(a, (f32)Ti, (f32)Ts, n, (f32)dx, (f32)tSon); const auto cpu = cpuRef1D(a, Ti, Ts, n, dx, tSon); FIKO_KONTROL(gpu.size() == cpu.size()); f64 enBuyukFark = 0; for (u32 i = 1; i < n; ++i) { const f64 fark = std::abs((f64)gpu[i] - cpu[i]) / (Ti - Ts); enBuyukFark = std::max(enBuyukFark, fark); } FIKO_KONTROL(enBuyukFark < 0.01); // f32↔f64 port eşleşmesi < %1 } // ---- (gpu-d) Katılaşma anı tutarlı: soğuyan hücrede fs monoton artar --------- // Shader fs güncellemesi (H→T→fs) fiziksel: soğuma → fs artışı, katılaşma anı // (fs≥0.99) sonlu ve zamanla ilerler. FIKO_TEST(gpu_d_fs_monoton) { Alasim a = alasimAlSi7Mg(); // Tek hücre, sabit ısı kaybı ile soğuma: H azalır → T düşer → fs artar. f32 T = 700.0f, H = shaderEntalpi(a, T); f32 oncekiFs = shaderKatiOran(a, T); const f32 dHkayip = -2.0e7f; // her adım entalpi kaybı [J/m³] bool katilasti = false; for (int adim = 0; adim < 100000 && !katilasti; ++adim) { H += dHkayip; T = shaderSicaklik(a, H); const f32 fs = shaderKatiOran(a, T); FIKO_KONTROL(fs >= oncekiFs - 1e-4f); // fs monoton artar (soğuma) oncekiFs = fs; if (fs >= 0.99f) katilasti = true; } FIKO_KONTROL(katilasti); // sonlu sürede katılaştı FIKO_KONTROL(T <= (f32)a.tSolidus + 1.0f); // solidus altına indi }