// B-Rep → hex kafes köprüsü + UÇTAN UCA analitik kıyas (kutu kirişte eksenel çekme). // Köprü bozulursa FEM doğru ama sonuç yanlış olur (kafes parçayı görmez). #include #include #include #include "fiko/cae/govde_kafes.hpp" #include "fiko/dokum/voxel_kafes.hpp" #include "fiko/dokum/voxelize.hpp" #include "fiko_test.hpp" using namespace fiko; using namespace fiko::cae; FIKO_TEST(govde_kafes_kutuyu_doldurur) { const geometri::BrepGovde kutu = geometri::kutu(100.0, 20.0, 20.0); FIKO_KONTROL(kutu.gecerliMi()); const HexKafes kf = govdedenKafes(kutu, {24, 0.01}); FIKO_KONTROL(kf.gecerliMi()); FIKO_KONTROL(kf.nx == 24u); // en uzun kenar = X u32 dolu = 0; for (u8 v : kf.dolu) dolu += v ? 1u : 0u; // Kutu AABB'sini TAMAMEN doldurur → hemen hemen tüm hücreler dolu olmalı. const f64 oran = (f64)dolu / (f64)kf.hucreSayi(); std::printf(" OLCUM kafes %ux%ux%u h=%.3f dolu orani=%%%.1f\n", kf.nx, kf.ny, kf.nz, kf.h, 100.0 * oran); FIKO_KONTROL(oran > 0.95); } FIKO_TEST(govde_kafes_gecersiz_govde_bos_kafes) { const HexKafes kf = govdedenKafes(geometri::BrepGovde{}, {24, 0.01}); FIKO_KONTROL(!kf.gecerliMi()); } FIKO_TEST(yuz_dugumleri_ve_yayli_kuvvet) { const geometri::BrepGovde kutu = geometri::kutu(100.0, 20.0, 20.0); const HexKafes kf = govdedenKafes(kutu, {20, 0.01}); FIKO_KONTROL(kf.gecerliMi()); const std::vector sol = yuzDugumleri(kf, KafesYuz::EksiX); const std::vector sag = yuzDugumleri(kf, KafesYuz::ArtiX); FIKO_KONTROL(!sol.empty() && sol.size() == sag.size()); // Karşılıklı yüzler AYRI düğüm kümesi olmalı (aynıysa indeksleme bozuk). bool ortak = false; for (u32 a : sol) for (u32 b : sag) if (a == b) ortak = true; FIKO_KONTROL(!ortak); const auto yuk = yuzeYayliKuvvet(kf, KafesYuz::ArtiX, {1000.0, 0.0, 0.0}); FIKO_KONTROL(yuk.size() == sag.size()); f64 toplam = 0.0; for (const DugumKuvvet& y : yuk) toplam += y.kuvvet.x; FIKO_YAKIN(toplam, 1000.0, 1e-9); // kuvvet KORUNMALI } // UÇTAN UCA: gerçek B-Rep kutu → voxel → FEM → σ = F/A (analitik). // Gerilme sabit alan olduğu için ayrıklaştırmadan BAĞIMSIZ tam çıkmalı. FIKO_TEST(govde_kafes_uctan_uca_eksenel_gerilme) { const f64 en = 20.0, boy = 100.0; const geometri::BrepGovde kutu = geometri::kutu(boy, en, en); const HexKafes kf = govdedenKafes(kutu, {20, 0.01}); FIKO_KONTROL(kf.gecerliMi()); const ElastikMalzeme mlz{210000.0, 0.3}; const f64 F = 4000.0; const FemSonuc s = femCoz(kf, mlz, yuzDugumleri(kf, KafesYuz::EksiX), yuzeYayliKuvvet(kf, KafesYuz::ArtiX, {F, 0.0, 0.0})); FIKO_KONTROL(s.yakinsadi); // Kesit alanı kafesin GERÇEK en kesitinden (voxel h ile) hesaplanır. const f64 A = (kf.ny * kf.h) * (kf.nz * kf.h); const f64 analitikGerilme = F / A; const f64 orta = s.vonMises[kf.hucreIndeks(kf.nx / 2, kf.ny / 2, kf.nz / 2)]; const f64 L = kf.nx * kf.h; const f64 analitikUzama = F * L / (A * mlz.elastisite); f64 ucX = 0.0; for (u32 d : yuzDugumleri(kf, KafesYuz::ArtiX)) ucX += s.yer[d].x; ucX /= (f64)yuzDugumleri(kf, KafesYuz::ArtiX).size(); std::printf(" OLCUM gerilme=%.4f (analitik %.4f) uzama=%.6e (analitik %.6e)\n", orta, analitikGerilme, ucX, analitikUzama); FIKO_KONTROL(std::abs(orta - analitikGerilme) / analitikGerilme < 0.01); FIKO_KONTROL(std::abs(ucX - analitikUzama) / analitikUzama < 0.03); } // SİMETRİ SINIR KOŞULU: tam ankastre kök Poisson daralmasını engelleyip modeli // YAPAY sertleştiriyordu (ölçüldü: eksenel uzamada %1.12 eksik). Yön-bazlı kısıtla // (-X'te yalnız X, -Y'de yalnız Y, -Z'de yalnız Z) o yapay sertlik KALKMALI ve // uzama analitik FL/(AE) ile çözücü toleransı kadar örtüşmeli. FIKO_TEST(simetri_kisiti_yapay_sertligi_kaldirir) { const geometri::BrepGovde kutu = geometri::kutu(100.0, 20.0, 20.0); const HexKafes kf = govdedenKafes(kutu, {20, 0.01}); FIKO_KONTROL(kf.gecerliMi()); const ElastikMalzeme mlz{210000.0, 0.3}; const f64 F = 4000.0; const auto yuk = yuzeYayliKuvvet(kf, KafesYuz::ArtiX, {F, 0.0, 0.0}); const f64 A = (kf.ny * kf.h) * (kf.nz * kf.h), L = kf.nx * kf.h; const f64 analitik = F * L / (A * mlz.elastisite); const auto ucOrtalama = [&](const FemSonuc& s) { const auto uc = yuzDugumleri(kf, KafesYuz::ArtiX); f64 t = 0.0; for (u32 d : uc) t += s.yer[d].x; return uc.empty() ? 0.0 : t / (f64)uc.size(); }; // 1) TAM ankastre (eski davranış) — analitikten AZ çıkmalı. const FemSonuc ankastre = femCoz(kf, mlz, yuzDugumleri(kf, KafesYuz::EksiX), yuk); FIKO_KONTROL(ankastre.yakinsadi); const f64 sapmaAnkastre = (ucOrtalama(ankastre) - analitik) / analitik; // 2) SİMETRİ: her yüzde yalnız kendi normali tutulur. std::vector sim = yuzKisitlari(kf, KafesYuz::EksiX, yuzNormalMaske(KafesYuz::EksiX)); for (const KafesYuz y : {KafesYuz::EksiY, KafesYuz::EksiZ}) { const auto ek = yuzKisitlari(kf, y, yuzNormalMaske(y)); sim.insert(sim.end(), ek.begin(), ek.end()); } const FemSonuc simetri = femCoz(kf, mlz, sim, yuk); FIKO_KONTROL(simetri.yakinsadi); const f64 sapmaSimetri = (ucOrtalama(simetri) - analitik) / analitik; std::printf(" OLCUM uzama sapmasi: ankastre=%%%.3f simetri=%%%.3f\n", 100.0 * sapmaAnkastre, 100.0 * sapmaSimetri); FIKO_KONTROL(std::abs(sapmaSimetri) < 1e-3); // analitikle örtüşür FIKO_KONTROL(std::abs(sapmaSimetri) < std::abs(sapmaAnkastre)); // yapay sertlik kalktı } // İNCE CİDAR: merkez-tabanlı sınıflama 2 mm plakayı kaba kafeste TAMAMEN yutuyordu // (ölçüldü 07-21: çözünürlük 12 ve 25'te dolu hücre SIFIR). Konservatif sınıflama // (hücre kutusu yüzeyi kesiyorsa dolu) bunu kurtarmalı — yapısal analizde parçanın // sessizce yok olması en tehlikeli arızadır. FIKO_TEST(konservatif_kafes_ince_cidari_kurtarir) { const geometri::BrepGovde plaka = geometri::kutu(100.0, 100.0, 2.0); for (u32 coz : {12u, 25u, 50u}) { KafesKurulum k; k.enUzunHucre = coz; k.konservatif = false; const HexKafes eski = govdedenKafes(plaka, k); k.konservatif = true; const HexKafes yeni = govdedenKafes(plaka, k); u32 dEski = 0, dYeni = 0; for (u8 v : eski.dolu) dEski += v ? 1u : 0u; for (u8 v : yeni.dolu) dYeni += v ? 1u : 0u; std::printf(" OLCUM coz=%3u h=%.2f merkez=%u konservatif=%u\n", coz, yeni.h, dEski, dYeni); FIKO_KONTROL(dYeni > 0); // parça ASLA yok olmamalı FIKO_KONTROL(dYeni >= dEski); // konservatif hiçbir hücreyi KAYBETMEZ } // Kaba kafeste merkez sınıflaması gerçekten sıfır veriyor mu (kusur hâlâ orada mı)? KafesKurulum kaba; kaba.enUzunHucre = 12; kaba.konservatif = false; u32 sifirMi = 0; for (u8 v : govdedenKafes(plaka, kaba).dolu) sifirMi += v ? 1u : 0u; FIKO_KONTROL(sifirMi == 0); // kusurun varlığı belgeli: bu satır kırmızıya dönerse // merkez sınıflaması düzelmiş demektir (not güncellensin) } // Kalın gövdede konservatif sınıflama hacmi ŞİŞİRMEMELİ (yalnız yüzey kabuğu ekler). FIKO_TEST(konservatif_kafes_kalin_govdede_asiri_sismez) { const geometri::BrepGovde kutu = geometri::kutu(100.0, 20.0, 20.0); KafesKurulum k; k.enUzunHucre = 20; k.konservatif = false; const HexKafes eski = govdedenKafes(kutu, k); k.konservatif = true; const HexKafes yeni = govdedenKafes(kutu, k); u32 dEski = 0, dYeni = 0; for (u8 v : eski.dolu) dEski += v ? 1u : 0u; for (u8 v : yeni.dolu) dYeni += v ? 1u : 0u; std::printf(" OLCUM kalin govde: merkez=%u konservatif=%u\n", dEski, dYeni); FIKO_KONTROL(dYeni >= dEski); FIKO_KONTROL(dYeni <= (u32)(dEski * 1.05 + 1)); // kutu zaten AABB'yi dolduruyor } // ÖLÇÜM: konservatif sınıflamanın maliyeti. Kaba uygulama HER ÜÇGEN için TÜM // hücreleri geziyor (O(üçgen × hücre)) — gerçek modelde patlar. Önce ölç. FIKO_TEST(olcum_konservatif_kafes_suresi) { const geometri::BrepGovde kure = geometri::kure(50.0); for (u32 coz : {16u, 32u, 48u}) { KafesKurulum k; k.enUzunHucre = coz; k.konservatif = false; const auto t0 = std::chrono::steady_clock::now(); const HexKafes a = govdedenKafes(kure, k); const auto t1 = std::chrono::steady_clock::now(); k.konservatif = true; const HexKafes b = govdedenKafes(kure, k); const auto t2 = std::chrono::steady_clock::now(); const f64 msMerkez = std::chrono::duration(t1 - t0).count(); const f64 msKons = std::chrono::duration(t2 - t1).count(); std::printf(" OLCUM coz=%2u hucre=%u merkez=%.1f ms konservatif=%.1f ms (x%.1f)\n", coz, a.hucreSayi(), msMerkez, msKons, msKons / (msMerkez > 0 ? msMerkez : 1)); FIKO_KONTROL(b.gecerliMi()); } } // Maliyet NEREDE? Üçgenleme (OCCT) mi, iç/dış sınıflama (ışın-parity) mı? FIKO_TEST(olcum_kafes_maliyet_dagilimi) { const geometri::BrepGovde kure = geometri::kure(50.0); for (u32 coz : {16u, 32u, 48u}) { const auto t0 = std::chrono::steady_clock::now(); const dokum::UcgenCorba corba = dokum::corbaBrepten(kure, 0.01); const auto t1 = std::chrono::steady_clock::now(); dokum::VoxelKafes vk; vk.kur(corba.sinir, coz, 0.0, 1.0); const auto t2 = std::chrono::steady_clock::now(); dokum::kafesiSinifla(vk, corba); const auto t3 = std::chrono::steady_clock::now(); const auto ms = [](auto a, auto b) { return std::chrono::duration(b - a).count(); }; std::printf(" OLCUM coz=%2u ucgen=%u hucre=%u | corba=%.1f kur=%.1f sinifla=%.1f ms\n", coz, corba.ucgenSayi(), vk.hucreSayi(), ms(t0, t1), ms(t1, t2), ms(t2, t3)); FIKO_KONTROL(vk.gecerliMi()); } } // ÖLÇÜM: FEM çözümünün maliyeti nerede? Kafes kurulumu mu, CG mi? FIKO_TEST(olcum_fem_cozum_suresi) { const geometri::BrepGovde kutu = geometri::kutu(100.0, 20.0, 20.0); for (u32 coz : {20u, 40u, 60u}) { const auto t0 = std::chrono::steady_clock::now(); const HexKafes kf = govdedenKafes(kutu, {coz, 0.01}); const auto t1 = std::chrono::steady_clock::now(); const FemSonuc s = femCoz(kf, {210000.0, 0.3}, yuzDugumleri(kf, KafesYuz::EksiX), yuzeYayliKuvvet(kf, KafesYuz::ArtiX, {0.0, 0.0, -2000.0})); const auto t2 = std::chrono::steady_clock::now(); const auto ms = [](auto a, auto b) { return std::chrono::duration(b - a).count(); }; std::printf(" OLCUM coz=%2u hucre=%u dugum=%u | kafes=%.1f cozum=%.1f ms adim=%d\n", coz, kf.hucreSayi(), kf.dugumSayi(), ms(t0, t1), ms(t1, t2), s.adim); FIKO_KONTROL(s.yakinsadi); } } // ÖLÇÜM: blok (3x3) ön-koşullayıcı skaler Jacobi'ye göre iterasyonu düşürüyor mu? FIKO_TEST(olcum_blok_onkosul_kazanci) { const geometri::BrepGovde kutu = geometri::kutu(100.0, 20.0, 20.0); for (u32 coz : {20u, 40u, 60u}) { const HexKafes kf = govdedenKafes(kutu, {coz, 0.01}); const auto sabit = yuzDugumleri(kf, KafesYuz::EksiX); const auto yuk = yuzeYayliKuvvet(kf, KafesYuz::ArtiX, {0.0, 0.0, -2000.0}); const auto ms = [](auto a, auto b) { return std::chrono::duration(b - a).count(); }; femBlokOnkosul() = false; const auto t0 = std::chrono::steady_clock::now(); const FemSonuc a = femCoz(kf, {210000.0, 0.3}, sabit, yuk); const auto t1 = std::chrono::steady_clock::now(); femBlokOnkosul() = true; const FemSonuc b = femCoz(kf, {210000.0, 0.3}, sabit, yuk); const auto t2 = std::chrono::steady_clock::now(); const f64 fark = std::abs(b.enBuyukYer - a.enBuyukYer) / std::max(1e-30, a.enBuyukYer); std::printf(" OLCUM coz=%2u | skaler adim=%3d %.1f ms | blok adim=%3d %.1f ms | fark=%.2e\n", coz, a.adim, ms(t0, t1), b.adim, ms(t1, t2), fark); FIKO_KONTROL(a.yakinsadi && b.yakinsadi); FIKO_KONTROL(fark < 1e-6); // AYNI çözüm (ön-koşullayıcı sonucu DEĞİŞTİRMEZ) } femBlokOnkosul() = false; // varsayılana dön (ölçüm sonrası KAPALI) } // KALİTE ÖLÇÜSÜ: en ince yöndeki hücre sayısı — kaba kafes sessizce anlamsız sonuç verir. FIKO_TEST(en_incede_hucre_olcusu) { // 100×100×2 plaka: kalınlıkta ancak 1 hücre çıkar (kaba), uzun yönde çok. const geometri::BrepGovde plaka = geometri::kutu(100.0, 100.0, 2.0); const HexKafes ince = govdedenKafes(plaka, {25, 0.01}); FIKO_KONTROL(ince.gecerliMi()); const u32 n1 = enIncedeHucre(ince); std::printf(" OLCUM plaka: en ince yonde %u hucre (h=%.2f)\n", n1, ince.h); FIKO_KONTROL(n1 >= 1u); FIKO_KONTROL(n1 < asgariCidarHucre()); // kaba: uyarı eşiğinin ALTINDA // Küp: her yönde eşit → ölçü çözünürlükle orantılı büyümeli. const geometri::BrepGovde kup = geometri::kutu(50.0, 50.0, 50.0); const u32 n2 = enIncedeHucre(govdedenKafes(kup, {20, 0.01})); std::printf(" OLCUM kup: en ince yonde %u hucre\n", n2); FIKO_KONTROL(n2 >= 15u); // ~20 (kırpma payıyla) FIKO_KONTROL(n2 >= asgariCidarHucre()); FIKO_KONTROL(enIncedeHucre(HexKafes{}) == 0u); // geçersiz kafes → 0 }