Batchrun draaien

Een reeks zichtjaren voor meerdere varianten duurt uren en past niet in een GUI-sessie. Daarvoor draait het model in batch, met GeoDmsRun.exe in plaats van de GUI en met een PowerShell-script dat de stappen op volgorde zet, elke stap toetst en een spoor achterlaat waarmee je achteraf kunt zien wat er is gebeurd.

Deze pagina beschrijft hoe je zo’n reeks start, ook op een machine waar het nog niet eerder is gedaan. Voor het draaien vanuit de GUI, zie Installatie tutorial.

Welk script

In batch/ staan meerdere scripts. Ze zijn niet gelijkwaardig.

Script Waarvoor
Run2120.ps1 de reeks zelf: basisdata, variantdata en de allocatie per zichtjaar
RunIndicatoren.ps1 de indicatorenexport, leest de stand uit de tifs en hoeft dus niet te alloceren
ToetsOplevering.ps1 beoordeelt na afloop of de uitkomst opleverbaar is, zonder opnieuw te rekenen
MaakOplevering.ps1 stelt uit de ruwe uitvoer een opleveringsmap samen
RunAll.cmd en de andere .cmd-scripts ouder, en niet meer geschikt voor een reeks

De .cmd-scripts missen drie dingen die de PowerShell-scripts wel hebben. Ze weten niet dat een variant de stand van een andere variant kan lenen, dus ze alloceren zo’n variant alsnog en dat kost uren voor een uitkomst die identiek wordt aan die van de uitlener. Ze toetsen alleen de exitcode en niet het staplog, en ze schrijven alle stappen in een gedeeld logbestand. En ze zetten LocalDataProjDir op een vast pad, wat leest alsof het de uitvoer stuurt terwijl die variabele wordt genegeerd: de werkelijke uitvoermap volgt uit de mapnaam boven cfg. Gebruik ze niet voor een productiereeks.

De naam Run2120 komt uit de NL2120-toepassing, maar het script is daar alleen in zijn standaardwaarden aan gebonden. Scenario, varianten, zichtjaren en paden zijn allemaal parameters.

Wat er op de machine moet staan

GeoDMS

Dezelfde build als waarop de rest van de reeks draait, geinstalleerd onder C:\Program Files\ObjectVision. Niet een build uit Visual Studio: die wordt opnieuw gecompileerd zonder dat de configuratie verandert, dus een run kan halverwege van engine wisselen en dan is een verschil in uitkomst niet meer toe te wijzen.

De configuratie bewaakt zelf een ondergrens. In cfg/main.dms staat een IntegrityCheck die GeoDMSVersion() >= 17.0904 eist, dus alles onder 17.9.4 valt meteen om met een melding die zegt dat je een nieuwere versie nodig hebt. Dat is de ondergrens en niet het advies: de NL2120-productieruns van augustus en september 2026 draaien op GeoDms20.17.0.m.

Draai je een reeks op twee machines tegelijk, controleer dan dat beide op dezelfde build staan. Dat is de eerste vraag die je achteraf niet meer kunt beantwoorden.

De werkkopie

Een clone van de repo, op de branch van de toepassing. De naam van de map boven cfg is niet vrij: GeoDMS leidt %LocalDataProjDir% af uit LocalDataDir plus die mapnaam, dus een werkkopie in C:\ProjDir\RSopen_NL2120_productie schrijft naar C:\LocalData\RSopen_NL2120_productie. Kies dezelfde naam als op de andere machine, anders komen de ontkoppelde bestanden en de standtifs ergens anders terecht en begint de machine van voren af aan.

De brondata en de instellingen

GeoDMS bewaart LocalDataDir en SourceDataDir per gebruiker en per machine in het register, onder HKCU:\SOFTWARE\ObjectVision\<MACHINENAAM>\GeoDMS. Zet ze via de GUI, Tools en dan Options; GeoDmsRun leest dezelfde sleutel, dus de batchrun neemt ze vanzelf over. Nakijken kan zo:

Get-ItemProperty "HKCU:\SOFTWARE\ObjectVision\$env:COMPUTERNAME\GeoDMS" |
  Select-Object LocalDataDir, SourceDataDir

Staat de sleutel er niet, dan is de GUI op die machine nog nooit met een configuratie gestart. Doe dat eerst, want zonder deze twee paden zoekt de run de brondata op een standaardplek.

Ruimte en geheugen

De brondata is de grootste post. Daarnaast schrijft het model zijn ontkoppelde bestanden, standtifs en indicatoren in LocalData: voor een volle reeks met drie varianten tot 2120 is dat orde vijftig gigabyte. Reken op minstens 64 GB werkgeheugen; de machine waarop de NL2120-productieruns draaien heeft er 128.

PowerShell 7

Zie de valkuil verderop. Kort: Run2120.ps1 draait wel onder Windows PowerShell 5.1, maar handelt daar een gefaalde stap verkeerd af.

De reeks starten

Alle padparameters hebben een standaardwaarde die naar de productiemachine wijst. Op een andere machine kloppen die niet, dus geef ze expliciet mee:

pwsh -NoProfile -File C:\ProjDir\RSopen_NL2120_productie\batch\Run2120.ps1 `
  -Exe       'C:\Program Files\ObjectVision\GeoDms20.17.0.m\GeoDmsRun.exe' `
  -Cfg       'C:\ProjDir\RSopen_NL2120_productie\cfg\main.dms' `
  -LocalData 'C:\LocalData\RSopen_NL2120_productie' `
  -LogDir    'C:\ProjDir\RSopen_NL2120_productie\batch\log\run2120' `
  -Scenario  'WLO_hoog' `
  -Varianten BAU,BAU2,NbSGenuanceerd `
  -DiagnoseNaZichtjaar Y2040,Y2120

Scenario en varianten in dit voorbeeld horen bij de NL2120-toepassing; zie Toepassing: NL2120 voor wat die namen betekenen.

Twee parameters verdienen toelichting.

-LocalData stuurt niet waar GeoDMS schrijft. Dat volgt uit de mapnaam hierboven en is niet met een omgevingsvariabele te verleggen; op 1 september 2026 is gemeten dat LocalDataProjDir als omgevingsvariabele domweg wordt genegeerd. Het script gebruikt -LocalData alleen om te kijken of er al standen van deze reeks staan en om de dictionary-controle te doen. Geef je er een andere map aan dan waar GeoDMS werkelijk schrijft, dan kijken die controles naar de verkeerde plek en melden ze niets.

-DiagnoseNaZichtjaar laat het diagnoseharnas meedraaien na de genoemde zichtjaren. Dat kost ongeveer acht minuten per zichtjaar per variant en het is de moeite waard: wat er tijdens de run niet is weggeschreven, kun je achteraf niet beoordelen zonder opnieuw te rekenen. Het eerste zichtjaar vangt een systematische fout na een uur in plaats van na zeven, het laatste is het zichtjaar dat wordt opgeleverd.

De zichtjaren zelf hoef je niet op te geven. Het script haalt ze uit de configuratie, zodat de lijst niet uit de pas kan lopen met Model_FirstZichtjaar en Model_FinalYear.

Wat het script controleert voordat het rekent

Vier toetsen, allemaal bedoeld om een reeks van uren niet op een verkeerde aanname te laten beginnen.

De schakelaar ModelParameters/OntkoppelStedelijkeKlasses wordt naast de bedoeling van de aanroeper gelegd. Staat die op TRUE, dan alloceert alleen de landbouw en nemen de stedelijke sectoren hun ingangsstand over. Dat is een geldige route, maar niets in het log zegt het, dus het script eist dat je die bedoeling met -AlleenLandbouw bevestigt.

Leent een variant zijn stand van een andere, dan wordt de aanname daaronder omgekeerd getoetst: de kolommen waarop de twee varianten verschillen moeten precies de bekende zijn, en de sector landbouw mag niet alloceren. Komt er ooit een afwijkende kolom bij, dan stopt de reeks in plaats van stil een verkeerde stand te lenen. De uitlener moet wel in dezelfde aanroep meedraaien.

Staan er al standen van deze reeks, dan weigert het script de basisdata of de variantdata opnieuw weg te schrijven. Verse invoer halverwege een reeks laat de vroege en de late zichtjaren met verschillende gegevens rekenen, en dat is achteraf niet aan de uitkomst te zien. Ververs die bestanden dus tussen twee complete reeksen door en nooit ertussenin. Bewust opnieuw beginnen kan met -HerbouwBasedata.

Na de basisdata worden de dictionarybestanden gecontroleerd op relatieve paden. Een vers geschreven 0Dictionary.dms kan een relatief pad krijgen en is dan bij het teruglezen onvindbaar.

Meekijken en herstarten

Elke stap krijgt een eigen logbestand met tijdstempel in de logmap, plus een regel in status.tsv met tijd, stapnaam, itempad, exitcode en het aantal seconden.

Get-Content C:\ProjDir\RSopen_NL2120_productie\batch\log\run2120\status.tsv -Tail 10 -Wait

De exitcode alleen is geen bewijs. Een ontbrekend bronbestand komt als GDAL-fout in het log terwijl de exitcode 0 blijft, en de gevraagde waarde komt dan leeg terug. Het script toetst daarom ook op regels met [E] in het staplog, en meldt een stap als mislukt zodra een van beide iets zegt. Draai je een stap met de hand, doe dat dan ook.

Het decimaalteken in status.tsv volgt de landinstelling van de shell die de reeks startte. In een bestand dat over meerdere runs is volgeschreven kunnen dus komma’s en punten door elkaar staan. Lees het niet met een vast scheidingsteken uit.

Stopt de reeks halverwege, dan pak je hem op met de stapnaam uit status.tsv:

... -StartBij allocatie-BAU-Y2070 -SkipBasedata -SkipVariantData

De stapnamen zijn basedata-run1, basedata-run2, variantdata-<variant>-run1 en -run2, allocatie-<variant>-<zichtjaar> en diagnose-<variant>-<zichtjaar>. Alles voor de genoemde stap wordt overgeslagen.

Rekentijd

Gemeten op de reeks van 9 september 2026, negen zichtjaren van 2040 tot en met 2120:

Stap Tijd
basisdata, eenmalig 8 minuten
variantdata, per variant 5,5 minuten
allocatie, per zichtjaar tot en met 2060 43 tot 52 minuten
allocatie, per zichtjaar vanaf 2070 11 tot 21 minuten
allocatie, hele reeks per allocerende variant 3,5 tot 4,5 uur
indicatorenexport, per variant, ketens in een proces 44 tot 70 minuten
indicatorenexport met -Ontkoppeld: reeks van acht zichtjaren, per variant 68 minuten, eenmalig per allocatie
indicatorenexport met -Ontkoppeld: het exportzichtjaar, per variant en per herhaling 18 minuten

De late zichtjaren zijn sneller doordat ze met een verkorte allocatieopzet rekenen; die staat in ModelParameters/Skeleton en gold in deze reeks vanaf 2070. De spreiding komt verder vooral van wat er nog meer op de machine draait en niet van de vordering van de reeks: op een drukke avond loopt een zichtjaar op tot het dubbele van wat het op een lege machine kost. Een variant die zijn stand leent, kost niets.

Na de reeks

De indicatorenexport draait los van de allocatie, want die leest de stand uit de tifs:

pwsh -NoProfile -File ...\batch\RunIndicatoren.ps1 -Varianten BAU,BAU2 -Zichtjaren Y2120 -IndicatorRegio Landschappen -Ontkoppeld

Landschappen draait de vier aangeleverde landschapsgebieden van #760 na elkaar, een proces per gebied; de eerste draagt de volledige export, de drie volgende alleen hun tabel. De partitie Landschap van #705 is sinds 13 september 2026 geen optie meer.

-Ontkoppeld is de opzet van #824. De cumulatieve indicatoren (contante waarden per periode, koolstof, methaan, sterfte, SOMERS) lezen het vorige zichtjaar, dus zonder de schakelaar rekent een export van 2120 alle zichtjaren vanaf 2040 in een proces mee, en elke tabel die je opnieuw wilt is dan weer drie kwartier. Met de schakelaar draait het script per variant eerst de zichtjaren voor het exportzichtjaar, elk in een eigen proces: het item Zichtjaren/<jaar>/Tijdreeks schrijft de 38 grootheden die het volgende zichtjaar nodig heeft als tif in Indicatoren/<casus>/Ketens/ (4,3 GB per variant), plus de kaarten van de tijdreeks. De export van het exportzichtjaar leest dan alleen de ketentifs van zijn voorganger. Het bewijs dat dit dezelfde uitvoer geeft staat in #824: alle 545 bestanden van een volledige export van WLO_hoog_BAU bit voor bit gelijk.

De reeks hoort opnieuw zodra de allocatie opnieuw is gedaan. Er is met opzet geen fingerprint: een bestaande ketentif wordt gelezen, en het script waarschuwt alleen als een ketentif ouder is dan de stand van dat zichtjaar. Voor een herhaling van het exportzichtjaar alleen, bijvoorbeeld een tabel na een reparatie, is er -AlleenExportZichtjaar.

Daarna beoordelen met ToetsOplevering.ps1, dat de diagnose-uitdraaien van de run leest en per toets PASS, FAIL of GEEN DATA meldt. GEEN DATA is nadrukkelijk geen PASS. Samenstellen van de opleveringsmap gaat met MaakOplevering.ps1.

Zet ten slotte een tag op de commit die heeft gedraaid. Een reeks beslaat uren en loopt vaak op meerdere machines tegelijk, terwijl de branch ondertussen doorloopt; zonder tag is achteraf niet meer vast te stellen welke code een uitdraai heeft gemaakt. Draaiden er meerdere machines, zet dan in de tagboodschap welke variant waar draaide, en gebruik git rev-parse HEAD op elke machine als bewijs.

Waar het misgaat

Windows PowerShell 5.1 in plaats van PowerShell 7

Windows brengt van huis uit powershell.exe mee, versie 5.1. PowerShell 7 is een aparte installatie en heet pwsh.exe. Welke je hebt:

$PSVersionTable.PSVersion

De scripts in batch/ worden door beide versies foutloos ingelezen, dus je merkt het verschil niet aan de start. Het verschil zit in wat er gebeurt als een stap faalt. De scripts draaien met $ErrorActionPreference op Stop en vangen de uitvoer van GeoDmsRun op met 2>&1. Onder 5.1 maakt PowerShell van elke regel die een programma naar de foutuitvoer schrijft een afbrekende fout, onder 7 niet. En GeoDmsRun schrijft daar wel degelijk naartoe: een mislukte aanroep meldt op de foutuitvoer welke configuratie hij niet kon lezen.

Onder 5.1 klapt het script daardoor uit met een kale uitzondering op precies het moment dat het zijn werk zou gaan doen. Je krijgt de regel MISLUKT niet, niet het pad naar het staplog, niet de eerste twintig foutregels, en er komt ook geen regel in status.tsv. Een stap die als niet-fataal is bedoeld, zoals de diagnose, neemt onder 5.1 de hele reeks mee in zijn val. Beide verschillen zijn op 10 september 2026 nagemeten.

Installeer dus PowerShell 7:

winget install --id Microsoft.PowerShell

Werkt winget niet op de machine, dan staat er een MSI-installatie op de releasepagina van PowerShell op GitHub, en er is een zip-versie die je zonder beheerrechten kunt uitpakken en direct aanroepen.

Kan het echt niet, dan moet het script mee. Schermt de aanroep zelf af met een eigen voorkeur, dan gedragen 5.1 en 7 zich hetzelfde en blijft de exitcode bewaard:

$oud = $ErrorActionPreference
$ErrorActionPreference = 'Continue'
& $Exe "/L$log" '/S1' '/S2' '/S3' $Cfg $Item 2>&1 | Out-Null
$code = $LASTEXITCODE
$ErrorActionPreference = $oud

Loopt het aanroepen van een .ps1 stuk op het uitvoerbeleid van de machine, start dan met -ExecutionPolicy Bypass erbij. Gebruik -NoProfile sowieso, zodat een profiel op die machine de run niet stilletjes anders instelt.

De brondata staat op een andere plek dan op de buurmachine

In cfg/main/ConfigSettings.dms staan twee paden dubbel, met een machinenaam als commentaar erachter en telkens een van de twee uitgeschakeld. De reden is dat de bronmap op de ene machine een niveau dieper hangt dan op de andere. Voor je begint controleer je dus of de actieve regel op jouw machine naar een bestaande map wijst.

Een omgevingsvariabele met dezelfde naam helpt hier niet. Op 10 september 2026 is RSo_DataDir als omgevingsvariabele op een niet-bestaand pad gezet en daarna een item opgevraagd dat een bestand uit die map leest: de run slaagde gewoon, dus de configuratiewaarde won. Het is dus of de mappenstructuur gelijktrekken, of de regel in de configuratie omzetten. Kies je het tweede, commit dat dan niet: het is een machine-instelling en geen modelwijziging.

De brondata is een synchronisatiemap

De bronmap synchroniseert met een cloudmap in de virtuele-bestandenmodus van Windows. Twee gevolgen.

Een bestand dat nog een placeholder is, wordt bij het eerste gebruik opgehaald, en de run wacht daarop. Bij honderdduizenden bestanden gaat elke opening door het cloudfilter; op een drukke dag zijn er ruim vijftien openingen per seconde gemeten, waar de synchronisatieclient van verzadigt. Pauzeer de synchronisatie voor een zware run.

De map is bovendien schrijf-eenmaal: een eenmaal geplaatst bestand mag niet worden gewijzigd of verwijderd, en overschrijven levert een conflictkopie op. Elk bronbestand draagt daarom een datum in de naam en het volledige pad staat als parameter in ModelParameters. Loopt een run vast op cannot open dataset, zoek het bestand dan eerst met een recursieve zoekopdracht op een deel van de naam voordat je in de configuratie gaat zoeken. Het staat vaker ergens anders dan dat het echt weg is, en de vervolgfouten noemen namen die niets met de bron te maken hebben.

Twee runs op dezelfde LocalData

Draai geen stap die ontkoppelde bestanden genereert zolang er een ander GeoDMS-proces op dezelfde %LocalDataProjDir% actief is, ook geen open GUI. De generator verwijdert het doelbestand en schrijft het opnieuw; houdt een ander proces het open, dan blijft er een stub achter en falen latere runs op een IntegrityCheck van een heel ander item. Andersom valt een run die zo’n bestand alleen leest om met een melding over een klassetabel, die naar de configuratie wijst terwijl er niets mis is.

Kijken wat er draait:

Get-CimInstance Win32_Process -Filter "Name='GeoDmsRun.exe'" | Select-Object ProcessId, CommandLine

Ruim daarbij nooit op procesnaam op. Get-Process -Name GeoDmsRun | Stop-Process raakt elk GeoDmsRun-proces op de machine, ongeacht welke werkkopie eronder draait, en op een gedeelde machine kost dat een ander uren. Filter op het volledige configuratiepad tot cfg, of gebruik het procesnummer.

Let er ook op dat een lege processenlijst niet betekent dat de machine vrij is. Tussen twee stappen zit een gat van enkele seconden waarin er niets draait. Kijk naar de wijzigingstijd van het logbestand van de ander, of vraag het.

De werkkopie verversen tijdens een run

Doe dat niet. Elke stap laadt de configuratie opnieuw bij het starten, dus een pull halverwege laat opeenvolgende zichtjaren of varianten op verschillende code draaien zonder dat iets dat meldt. Moet het toch, bepaal dan eerst of de gewijzigde items boven of onder de ontkoppellijn liggen: boven betekent de ontkoppelde bestanden opnieuw maken en de allocatie over.

De sessie afsluiten

Een reeks van uren draait in de sessie waarin je hem hebt gestart. Werk je via een extern bureaublad, koppel de sessie dan los en meld je niet af: afmelden beeindigt de processen. Wil je er helemaal niet bij hoeven blijven, start de reeks dan als geplande taak.

Een sector aan- of uitzetten vlak voor een run

De bestandsnaam van de standtifs bevat het aantal subsectoren van de actieve sectoren. Zet je een hele sector aan of uit in ModelParameters/SectorAllocRegio, dan schuift dat getal en is elke bestaande allocatie in een klap onbereikbaar, ook voor de indicatorenkant. Dat is dus geen instelling die je vlak voor een levering nog even meeneemt.