#include "fiko/dokum/sonuc_katmanlari.hpp" #include #include #include #include "fiko/dokum/gerilme.hpp" #include "fiko/dokum/methoding.hpp" namespace fiko::dokum { namespace { bool metalMi(const VoxelKafes& k, u32 idx) { return k.tip[idx] == HucreTip::Ic || k.tip[idx] == HucreTip::Sinir; } // Önerilen lejant aralığı: geçerli (≥0) değerlerin yüzdelik kırpımı. void lejantAralik(KatmanBilgi& kb, f64 altY, f64 ustY) { std::vector gecerli; gecerli.reserve(kb.alan.size()); for (f32 v : kb.alan) if (v >= 0.0f) gecerli.push_back(v); if (gecerli.empty()) { kb.min = kb.maks = 0; return; } const auto yuzdelik = [&](f64 oran) -> f32 { const size_t n = gecerli.size(); size_t p = (size_t)std::clamp(oran * (f64)(n - 1), 0.0, (f64)(n - 1)); std::nth_element(gecerli.begin(), gecerli.begin() + p, gecerli.end()); return gecerli[p]; }; kb.min = yuzdelik(altY); kb.maks = yuzdelik(ustY); if (kb.maks < kb.min) std::swap(kb.min, kb.maks); } KatmanBilgi katmanYap(std::string ad, std::string birim, std::string aciklama, KatmanKaynak kaynak, bool yaklasik, u32 N) { KatmanBilgi kb; kb.ad = std::move(ad); kb.birim = std::move(birim); kb.aciklama = std::move(aciklama); kb.kaynak = kaynak; kb.yaklasik = yaklasik; kb.alan.assign(N, kKatmanGecersiz); return kb; } } // namespace const KatmanBilgi* SonucKatmanlari::bul(std::string_view ad) const { for (const auto& k : katmanlar) if (k.ad == ad) return &k; return nullptr; } f64 sogumaHiziSdas(f64 sdasUm, const Alasim& alasim) { if (!(sdasUm > 0.0) || !(alasim.sdasK > 0.0)) return 0.0; // λ₂[µm] = K[m·(°C/s)^⅓]·10⁶·Ṙ^(−⅓) → Ṙ = (K·10⁶/λ₂)³ const f64 oran = alasim.sdasK * 1e6 / sdasUm; return oran * oran * oran; } std::vector cepheHiziHesapla(const VoxelKafes& kafes, const std::vector& varisZamani, f64 maksHiz) { const u32 N = kafes.hucreSayi(); std::vector hiz(N, kKatmanGecersiz); if (varisZamani.size() != N || !kafes.gecerliMi()) return hiz; const u32 nx = kafes.nx(), ny = kafes.ny(), nz = kafes.nz(); const f64 h = kafes.h(); // [m] if (!(h > 0.0)) return hiz; // Dolan metal hücre mi (gradyan yalnız dolan komşular üstünden). auto dolu = [&](int i, int j, int k) -> bool { if (i < 0 || j < 0 || k < 0 || i >= (int)nx || j >= (int)ny || k >= (int)nz) return false; const u32 idx = kafes.indeks((u32)i, (u32)j, (u32)k); return metalMi(kafes, idx) && varisZamani[idx] >= 0.0; }; auto t = [&](int i, int j, int k) { return varisZamani[kafes.indeks((u32)i, (u32)j, (u32)k)]; }; // Eksen türevi: merkezi fark; tek taraf dolmuşsa tek-yanlı; hiçbiri → 0. auto turev = [&](f64 t0, bool artiVar, f64 tArti, bool eksiVar, f64 tEksi) -> f64 { if (artiVar && eksiVar) return (tArti - tEksi) / (2.0 * h); if (artiVar) return (tArti - t0) / h; if (eksiVar) return (t0 - tEksi) / h; return 0.0; }; for (int k = 0; k < (int)nz; ++k) for (int j = 0; j < (int)ny; ++j) for (int i = 0; i < (int)nx; ++i) { if (!dolu(i, j, k)) continue; const u32 idx = kafes.indeks((u32)i, (u32)j, (u32)k); const f64 t0 = varisZamani[idx]; const f64 gx = turev(t0, dolu(i + 1, j, k), dolu(i + 1, j, k) ? t(i + 1, j, k) : 0, dolu(i - 1, j, k), dolu(i - 1, j, k) ? t(i - 1, j, k) : 0); const f64 gy = turev(t0, dolu(i, j + 1, k), dolu(i, j + 1, k) ? t(i, j + 1, k) : 0, dolu(i, j - 1, k), dolu(i, j - 1, k) ? t(i, j - 1, k) : 0); const f64 gz = turev(t0, dolu(i, j, k + 1), dolu(i, j, k + 1) ? t(i, j, k + 1) : 0, dolu(i, j, k - 1), dolu(i, j, k - 1) ? t(i, j, k - 1) : 0); const f64 g = std::sqrt(gx * gx + gy * gy + gz * gz); // [s/m] // |∇t|→0 (aynı anda dolan bölge / tek hücre) → kelepçe (fiziksel üst sınır). const f64 v = (g > 1e-12) ? std::min(1.0 / g, maksHiz) : maksHiz; hiz[idx] = (f32)v; } return hiz; } std::vector akisUzunluguHesapla(const VoxelKafes& kafes, const DolumSonuc& dolum) { const u32 N = kafes.hucreSayi(); std::vector uzunluk(N, kKatmanGecersiz); if (dolum.varisZamani.size() != N || !kafes.gecerliMi()) return uzunluk; const f64 hMm = kafes.h() * 1000.0; // [mm] // Varış sırası: dolum.sira varsa kullan; yoksa varış zamanına göre kur. std::vector sira = dolum.sira; if (sira.empty()) { for (u32 i = 0; i < N; ++i) if (metalMi(kafes, i) && dolum.varisZamani[i] >= 0.0) sira.push_back(i); std::sort(sira.begin(), sira.end(), [&](u32 a, u32 b) { return dolum.varisZamani[a] < dolum.varisZamani[b]; }); } const u32 nx = kafes.nx(), ny = kafes.ny(), nz = kafes.nz(); for (u32 idx : sira) { if (idx >= N || !metalMi(kafes, idx) || dolum.varisZamani[idx] < 0.0) continue; const u32 i = idx % nx, j = (idx / nx) % ny, k = idx / (nx * ny); // Öncül: daha erken dolmuş (uzunluğu atanmış) 6-komşuların en kısası. f32 enKisa = -1.0f; const int di[6] = {-1, 1, 0, 0, 0, 0}; const int dj[6] = {0, 0, -1, 1, 0, 0}; const int dk[6] = {0, 0, 0, 0, -1, 1}; for (int f = 0; f < 6; ++f) { const int ni = (int)i + di[f], nj = (int)j + dj[f], nk = (int)k + dk[f]; if (ni < 0 || nj < 0 || nk < 0 || ni >= (int)nx || nj >= (int)ny || nk >= (int)nz) continue; const f32 u = uzunluk[kafes.indeks((u32)ni, (u32)nj, (u32)nk)]; if (u >= 0.0f && (enKisa < 0.0f || u < enKisa)) enKisa = u; } uzunluk[idx] = (enKisa >= 0.0f) ? enKisa + (f32)hMm : 0.0f; // öncülsüz = giriş } return uzunluk; } SonucKatmanlari katmanlariHesapla(const VoxelKafes& kafes, const KatilasmaSonuc& kat, const DolumSonuc* dolum, const Alasim& alasim, const KatmanAyar& ayar) { SonucKatmanlari s; if (!kafes.gecerliMi()) return s; const u32 N = kafes.hucreSayi(); const bool katVar = kat.hazir && kat.katilasmaSuresi.size() == N && kat.sonSicaklik.size() == N && kat.niyama.size() == N && kat.sdas.size() == N && kafes.fs.size() == N; const bool dolumVar = dolum && dolum->hazir && dolum->varisZamani.size() == N; if (!katVar && !dolumVar) return s; const auto ekle = [&](KatmanBilgi kb) { lejantAralik(kb, ayar.lejantAltYuzdelik, ayar.lejantUstYuzdelik); s.katmanlar.push_back(std::move(kb)); }; // ---- Katılaşma perspektifi ----------------------------------------------- if (katVar) { KatmanBilgi sonT = katmanYap("Sıcaklık (Son)", "°C", "Katılaşma sonu sıcaklık alanı", KatmanKaynak::Katilasma, false, N); KatmanBilgi katiOran = katmanYap("Katı Oran", "", "Katı oran fs [0-1] (son durum)", KatmanKaynak::Katilasma, false, N); KatmanBilgi fsTime = katmanYap("Katılaşma Süresi", "s", "Hücrenin tam katılaştığı an (FSTIME)", KatmanKaynak::Katilasma, false, N); KatmanBilgi hotSpot = katmanYap("Hot-Spot (FSTime %)", "%", "Katılaşma süresi / en geç hücre × 100 — %100 bölgeler besleyici adayı", KatmanKaynak::Katilasma, false, N); KatmanBilgi soguma = katmanYap("Soğuma Hızı", "°C/s", "Katılaşma anı soğuma hızı Ṙ — SDAS ilişkisinin tersinden: Ṙ=(K·10⁶/λ₂)³", KatmanKaynak::Katilasma, false, N); KatmanBilgi gradyan = katmanYap("Sıcaklık Gradyanı", "°C/mm", "Katılaşma anı |∇T| — Niyama tanımının tersinden: G=Ny·√Ṙ", KatmanKaynak::Katilasma, false, N); KatmanBilgi lst = katmanYap("Yerel Katılaşma Süresi", "s", "Likidus→solidus yerel süre ≈ ΔT_donma/Ṙ (ProCAST tanımı; yaklaşık)", KatmanKaynak::Katilasma, true, N); KatmanBilgi niyama = katmanYap("Niyama", "(°C·s)^½/mm", "Ny=G/√Ṙ — düşük değer = merkez-hattı çekme porozitesi riski", KatmanKaynak::Katilasma, false, N); KatmanBilgi sdas = katmanYap("SDAS", "µm", "İkincil dendrit kol aralığı λ₂=K·Ṙ^(−⅓) — küçük = ince mikro-yapı", KatmanKaynak::Katilasma, false, N); const f64 dTf = std::max(alasim.tLikidus - alasim.tSolidus, 0.0); const f64 maksFs = std::max(kat.hotSpotSure, 1e-30); for (u32 i = 0; i < N; ++i) { if (!metalMi(kafes, i)) continue; sonT.alan[i] = (f32)kat.sonSicaklik[i]; katiOran.alan[i] = (f32)kafes.fs[i]; if (kat.katilasmaSuresi[i] >= 0.0) { fsTime.alan[i] = (f32)kat.katilasmaSuresi[i]; hotSpot.alan[i] = (f32)(100.0 * kat.katilasmaSuresi[i] / maksFs); } if (kat.sdas[i] > 0.0) { // Niyama örneklenmiş hücre (sdas>0 ⇔ örneklendi) const f64 R = sogumaHiziSdas(kat.sdas[i], alasim); soguma.alan[i] = (f32)R; gradyan.alan[i] = (f32)(kat.niyama[i] * std::sqrt(std::max(R, 0.0))); if (R > 1e-12 && dTf > 0.0) lst.alan[i] = (f32)(dTf / R); niyama.alan[i] = (f32)kat.niyama[i]; sdas.alan[i] = (f32)kat.sdas[i]; } } ekle(std::move(sonT)); ekle(std::move(katiOran)); ekle(std::move(fsTime)); ekle(std::move(hotSpot)); ekle(std::move(soguma)); ekle(std::move(gradyan)); ekle(std::move(lst)); ekle(std::move(niyama)); ekle(std::move(sdas)); // Feedmod [cm] (MAGMA riser doğrulama dili — video v1): Chvorinov tersi // M=√(t/C). C öz-kalibre: parçanın tümü için t_maks = C·M_parça² (Chvorinov // 1940, t = C·(V/A)²; M_parça = methoding parcaModulu V/A — aynı sabit) → // M_i = M_parça·√(t_i/t_maks). mm → cm (MAGMA birimi). Yaklaşık: gerçek // Feedmod kritik-fs besleme-yolu bağlantılılık taraması ister (⏳). const ParcaModul pm = parcaModulu(kafes); if (pm.gecerli && maksFs > 0.0) { KatmanBilgi feed = katmanYap("Feedmod", "cm", "Besleme modülü M=√(t/C) (Chvorinov tersi; C = t_maks/M_parça² — " "methoding V/A modülüyle öz-kalibre). Yüksek modül = en geç katılaşan " "bölge; eşik taramasında en son besleyici kalmalı", KatmanKaynak::Katilasma, true, N); for (u32 i = 0; i < N; ++i) { if (!metalMi(kafes, i) || kat.katilasmaSuresi[i] <= 0.0) continue; feed.alan[i] = (f32)(pm.modul * std::sqrt(kat.katilasmaSuresi[i] / maksFs) * 0.1); } ekle(std::move(feed)); } // Gerilme/çarpılma/sıcak yırtılma Dalga 1 indikatörleri (FEM'siz post- // process; formülasyon + kaynaklar gerilme.hpp banner'ında). const GerilmeSonuc ger = gerilmeHesapla(kafes, kat, alasim); if (ger.hazir) { KatmanBilgi csc = katmanYap("Sıcak Yırtılma (CSC)", "", "Clyne-Davies kırılganlık: κ=t_hassas/t_rahat alaşım oranı × yerel " "hassas-pencere (0.9varisZamani, ayar.maksCepheHiz); akisUz.alan = akisUzunluguHesapla(kafes, *dolum); const bool tVar = dolum->varisT.size() == N; for (u32 i = 0; i < N; ++i) { if (!metalMi(kafes, i) || dolum->varisZamani[i] < 0.0) continue; const f64 varis = dolum->varisZamani[i]; dolumZaman.alan[i] = (f32)varis; if (tVar) cepheT.alan[i] = (f32)dolum->varisT[i]; const f64 yas = std::max(dolum->toplamSure - varis, 0.0); ergiyikYas.alan[i] = (f32)yas; if (kafes.tip[i] == HucreTip::Sinir) duvarTemas.alan[i] = (f32)yas; } ekle(std::move(dolumZaman)); ekle(std::move(cepheT)); ekle(std::move(cepheHiz)); ekle(std::move(akisUz)); ekle(std::move(duvarTemas)); ekle(std::move(ergiyikYas)); // Malzeme Kaynağı (MAGMA Material Trace yaklaşık alan sürümü — video v4): // hücreye İLK ulaşan girişin numarası (dolum Dijkstra'sında kaynak etiketi // yayılır — dolum.cpp girisId). Ayrık değer: giriş 1..n; kademeli lejantla // karışma sınırları okunur. Parçacık-bazlı gerçek tracer SPH sonrası. if (dolum->girisId.size() == N) { KatmanBilgi kaynak = katmanYap("Malzeme Kaynağı", "", "Hücreye İLK ulaşan giriş numarası (Material Trace yaklaşık; " "karışma sınırları = renk sınırları)", KatmanKaynak::Dolum, true, N); bool var = false; for (u32 i = 0; i < N; ++i) { if (!metalMi(kafes, i) || dolum->girisId[i] == kGirisYok) continue; kaynak.alan[i] = (f32)(dolum->girisId[i] + 1); var = true; } if (var) ekle(std::move(kaynak)); } } s.hazir = true; return s; } } // namespace fiko::dokum