Teknik Makale

Delphi için HotXLS'te Artımlı Formül Yeniden Hesaplama

Yerel Delphi ve C++Builder Excel kütüphanesi olan HotXLS, TXLSXWorkbook.Recalculate aracılığıyla artımlı (incremental) formül yeniden hesaplaması gerçekleştirir. İlk çağrı bir formül bağımlılık grafiği oluşturur ve her formül hücresini değerlendirir; daha sonraki her çağrı, topolojik sırayla, maliyeti çalışma kitabının boyutundan ziyade kirli hücrelerin sayısıyla orantılı olan tek bir geçişte yalnızca son geçişten bu yana değer yazımlarından etkilenen hücreleri yeniden değerlendirir

Bu tek tasarım kararı, düzenlenen bir varsayıma milisaniyeler içinde yanıt veren bir finansal model ile saniyelerce duraklayan bir model arasındaki farktır. Bir avuç girdi hücresinin binlerce alt formülü beslediği raporlar oluşturuyorsanız, bu makalenin geri kalanı grafiğin ne yaptığını, hangi işlevlerin artımlılığın dışında kaldığını ve dairesel başvuruların sonsuza kadar döngüye girmek yerine nasıl raporlandığını açıklamaktadır

Bir hücreyi değiştirmek neden yüz bin formülü yeniden hesaplar?

Basit bir formül motoru kimin kime bağımlı olduğuna dair hiçbir hafızaya sahip değildir, bu nedenle herhangi bir düzenlemeden sonra tek güvenli hamlesi her şeyi yeniden değerlendirmektir. Daha da kötüsü, klasik özyinelemeli (recursive) strateji — A formülü B formülüne başvurduğunda, B'yi anında değerlendir — başvurulan hücreleri, önbelleğe alınmış değerleri yok sayarak koşulsuz olarak yeniden değerlendirir. Her biri bir öncekine başvuran n formülden oluşan bir zincir, tam geçiş başına O(n²) değerlendirme maliyeti getirir ve dairesel bir başvuru özyinelemeyi çıkmaza sokar. Basamaklı bir modeli özyinelemeli bir değerlendiriciye bağlayan her elektronik tablo geliştiricisi, her iki hata modunun da gerçekleştiğini izlemiştir

Bağımlılık grafiği bir düzenlemeyi tek bir geçişe nasıl dönüştürür?

Excel'in kendisi bunu onlarca yıl önce hesaplama zinciriyle çözdü: formül hücrelerinin sırası korunur, böylece bir düzenleme küçük bir hücre kümesini kirli (dirty) olarak işaretler ve motor zincirin yalnızca etkilenen kuyruğunu yürütür. HotXLS aynı fikri, derlenmiş formül ağaçlarından bir kez oluşturulan ve yeniden kullanılan açık bir bağımlılık grafiği olarak uygular. Amaç akıllılık taslamak değildir; yeniden hesaplama maliyetinin çalışma kitabınızın boyutunu değil, düzenlemenizin boyutunu takip etmesi gerektiğidir

HotXLS bağımlılık grafiği, her formül hücresine bir düğüm (node) verir ve kenarlar (edges) öncelden bağımlıya doğru uzanır. Kodunuz bir hücre değeri yazdığında, çalışma kitabı hücreyi kirli olarak kaydeder; Recalculate çalıştığında, kirlilik kenarlar boyunca her alt formüle yayılır ve kirli alt grafik, Kahn algoritması kullanılarak topolojik sırada tam olarak bir kez değerlendirilir. Bir formül asla öncellerinden önce ziyaret edilmediğinden, her düğüm tek bir değerlendirmeye ihtiyaç duyar — geçişi O(dirty) yapan da budur

Topolojik sıra özyineleme sorununu da kökünden çözer. Bir yeniden hesaplama geçişi sırasında motor, başka bir formül hücresine yapılan herhangi bir referansın, o hücreyi yeniden değerlendirmek yerine doğrudan o hücrenin önbelleğe alınmış değerini okuduğu özel bir moda geçer — sıralama önbelleğin zaten taze olduğunu garanti eder. Aynı mekanizma, bir referans döngüsünün sınırsız özyinelemeyi tetikleyemeyeceği anlamına gelir: geçiş içindeki hiçbir şey komşu bir hücre için değerlendiriciye tekrar girmez

var
  Book: TXLSXWorkbook;
  Inputs, Model: TXLSXWorksheet;
begin
  Book := TXLSXWorkbook.Create;
  try
    Inputs := Book.Sheets.Add('Inputs');
    Model  := Book.Sheets.Add('Model');

    Inputs.Cells[2, 2].Value := 0.05;                 // growth assumption
    Model.Cells[2, 2].Formula := 'Inputs!B2*1000';    // XLSX formulas take no leading '='
    Model.Cells[3, 2].Formula := 'B2*(1+Inputs!B2)';
    // ... thousands more rows cascading off the same assumption ...

    Book.Recalculate;                 // first call: builds the graph, full evaluation

    Inputs.Cells[2, 2].Value := 0.07; // one edit marks one cell dirty
    Book.Recalculate;                 // second call: only the downstream chain runs
  finally
    Book.Free;
  end;
end;

Her sonuç hücrenin önbelleğe alınmış Value alanına düşer, bu nedenle Recalculate döndükten sonra çıktıları diğer tüm hücreleri okuduğunuz gibi okursunuz. Bir rapor oluşturma döngüsünde kalıp tam olarak yukarıdaki kod gibidir: modeli bir kez yükleyin veya oluşturun, ardından birkaç girdi hücresi yazmak ile Recalculate çağırmak arasında geçiş yapın ve yalnızca neyin değiştiğine gerçekten bağımlı olan formüller için ödeme yapın

Hangi Excel işlevleri her geçişte yeniden hesaplamayı zorunlu kılar?

HotXLS; NOW, TODAY, RAND, OFFSET ve INDIRECT işlevlerini kararsız (volatile) olarak ele alır: bunlardan birini içeren herhangi bir formül, yukarı akışta bir şey değişip değişmediğine bakılmaksızın her Recalculate geçişinde yeniden değerlendirilir. İlk üçü, Excel'de olduklarıyla aynı nedenden dolayı kararsızdır — sonuçları diğer hücrelere değil, değerlendirme anına bağlıdır. OFFSET ve INDIRECT ise daha ince bir nedenden dolayı kararsızdır: okudukları hücreler çalışma zamanında hesaplanır, bu nedenle grafik onlar için hangi kenarların çizileceğini statik olarak bilemez

Aynı ihtiyatlı kural, grafik oluşturucunun tek bir dikdörtgene sabitleyemediği referanslar için de geçerlidir. Çok alanlı tanımlanmış bir aralıktan geçen veya harici bir çalışma kitabına başvuran bir formül de benzer şekilde kararsızlığa düşürülür ve her geçişte yeniden değerlendirilir. Bu politika kasıtlıdır: ekstra bir değerlendirme biraz zamana mal olur, ancak eksik bir bağımlılık kenarı gönderilen bir raporda sessizce bayat kalmış bir değer anlamına gelir ve bu çok daha kötü bir hatadır. Modeliniz çalışma kitabı düzeyindeki adlara dayanıyorsa, tanımlanmış adlar ve sayfalar arası formüller hakkındaki yardımcı makale tek alanlı adların nasıl çözümlendiğini kapsar — bunlar grafiğe normal şekilde katılır

Pratik rehberlik doğrudan bunu takip eder. Büyük bir modelin sıcak yollarını grafiğin işini yapabileceği düz hücre ve aralık referanslarında tutun ve OFFSET ile INDIRECT işlevlerini gerçekten dinamik adreslemeye ihtiyaç duyan birkaç yerle sınırlayın. Bin kararsız formüle sahip bir model, düzenleme ne kadar küçük olursa olsun her geçişte o bin formülü yeniden çalıştırır — tam da Excel kullanıcılarının "her tuşa basışta yeniden hesaplanan" çalışma kitaplarından bildiği davranış

HotXLS dairesel başvuruları (circular references) nasıl rapor eder?

TXLSXWorkbook.Recalculate temiz bir geçişte lxOk, bir referans döngüsü tespit ettiğinde ise lxErrorRef döndürür. Döngü üyeleri topolojik sıralama sırasında tanımlanır — bunlar Kahn algoritmasının asla serbest bırakamayacağı düğümlerdir — ve döngüye girmek yerine atlanırlar: önbelleğe alınmış değerleri neyse o şekilde kalırken, döngü dışındaki her formül normal olarak sırayla değerlendirilmeye devam eder. Çağrı siteniz bir kilitlenme yerine kesin bir hata kodu alır

case Book.Recalculate of
  lxOk:
    SaveReport(Book);
  lxErrorRef:
    // a reference cycle exists; cycle members kept their previous
    // cached values and everything outside the cycle is up to date
    LogWarning('Circular reference detected - review model inputs');
end;

Hangi hücrelerin döngüyü oluşturduğunu bulmak bir hata ayıklama işidir ve formül değerlendirme izleyicisi bunun için doğru araçtır: şüpheli formülü izleyin ve kendi üzerine katlanan referans zinciri adım adım görünür hale gelsin. Gerçek modellerdeki döngüler neredeyse her zaman bir yazım hatasıdır — bir özet satırının yanlışlıkla kendi SUM aralığına dahil edilmesi gibi — bu nedenle yeniden hesaplama zamanında gürültülü bir hata kodu tam olarak istediğiniz şeydir

Dizi formülleri, kirli izleme (dirty tracking) ve grafiğin ne zaman yeniden oluşturulduğu

CSE dizi formülleri, hücre başına bir düğüm değil, tüm sabitlenmiş dikdörtgen için tek bir düğüm alır. Kök formül geçiş başına bir kez değerlendirilir; ortaya çıkan matris doğrudan every üye hücreye yazılır ve yalnızca sol üst çıpayı değil, sabitlenmiş aralık içindeki herhangi bir hücreyi referans alan bir formül, o kök düğümden bir bağımlılık kenarı alır. Skaler sonuçlar, Excel'in eski dizi semantiginin öngördüğü şekilde dikdörtgen boyunca yayılır

Kirli izleme (dirty tracking), sıradan özellik ayarlayıcılarına (setters) bağlanır, bu nedenle kodunuz hakkında hiçbir şey değişmez. Bir hücreye Value yazmak çalışma kitabını uyarır ve bağımlıları kirli olarak işaretler; yeni bir Formula atamak yapısal bir değişikliktir, bu nedenle tüm grafiği bayat olarak işaretler ve bir sonraki Recalculate değerlendirmeden önce onu yeniden oluşturur. Sayfa eklemek, silmek veya taşımak da grafik düğüm kimliği sayfa dizinini kodladığından grafiği geçersiz kılar. Hiçbir grafik aktif olmadığında — yani Recalculate yöntemini hiç çağırmadığınız bir çalışma kitabında — kancalar atama başına tek bir nil kontrolüne mal olur, böylece düz okuma-yazma iş yükleri etkilenmez

Dürüstçe belirtilmesi gereken bir sınır: grafik hücreler arasındaki bağımlılıkları izler, bu nedenle OnUserFunction aracılığıyla kaydedilen kullanıcı tanımlı bir işlev, argümanlarını besleyen hücreler değiştiğinde diğer formüller gibi yeniden değerlendirilir. Motoru bu şekilde genişletiyorsanız, HotXLS formül motorundaki özel işlevler makalesi geri çağırma sözleşmesini ve argüman değerlerinin nasıl ulaştığını açıklar

Artımlı yeniden hesaplama, formül hesaplayıcısı, tanımlanmış adlar ve hızlandırdığı içe/dışa aktarım hattının yanı sıra HotXLS Delphi Excel Bileşeni içindeki standart XLSX motorunun bir parçasıdır. Delphi veya C++Builder uygulamanız yaşayan modeller — fiyatlandırma tabloları, konsolidasyon çalışma kitapları, rapor basamakları — barındırıyorsa, Recalculate bir çalışma kitabını yeniden hesaplamak ile bir düzenlemeyi yeniden hesaplamak arasındaki farktır