Ben Wilbrink
SCO-Kohnstamm Instituut, Universiteit van Amsterdam
Dit colloquium is georganiseerd door Henk Groen, verbonden aan het Cito. Het was voor mij een belangrijke stimulans om een eerste afronding van het algemene toetsmodel te maken. Omdat een droge presentatie van het model ten onrechte de indruk kan geven dat het gewoon een andere manier is om tegen toetsen aan te kijken, en dat instanties zoals het CITO daar geen boodschap aan hoeven te hebben, heb ik geprobeerd via een historische analyse de receptie van een heel andere manier van analyseren van toetssituaties voor te bereiden. Dit receptie-probleem is tot op de dag van vandaag gebleven. De historische analyse is later uitgebreid en strakker gemaakt, en gepubliceerd (zie onderaan deze badzijde). Henk Groen heeft een verslag van het colloquium gegeven in het huisperiodiek van het CITO gescand. Met dank aan Henk Groen, en aan de talrijke aanwezigen voor hun belangstelling, volgt hier het schriftelijke materiaal dat voor het colloquium is gebruikt. Dat ik, voor de vuist weg sprekend, hiervan nogal ben afgeweken en uit de tijd ben gelopen heeft destijds hopelijk niet al te storend gewerkt.
Deze voordracht is opgebouwd rond zes stellingen. De eerste stellingen hebben een inleidend karakter, de laatste dekken een belangrijk deel van de thematiek waarop ik een proefschrift aan het voorbereiden ben. Als introductie, en om in stijl te blijven, een keuzevraag als opgave.
Over deze vraag valt lang te discussieren. In ieder geval zijn de volgende punten van belang:
1) subjectief
Het is een objectief scoorbare keuzevraag met een evident subjectieve scoringssleutel. Wat denkt u overigens dat het juiste antwoord is?
NB: de vraag is niet wat gebruikelijk is, maar wat juist is.
Iedere docent zou hier eigenlijk een bevredigend antwoord op moeten kunnen geven, en een bevredigend antwoord betekent tenminste dat alle docenten het over het juiste antwoord met elkaar eens zijn (anders zijn studenten en leerlingen aan willekeur overgeleverd).
2) beslisbaar?
Wat heet subjectief: stel dat een student in beroep gaat bij het College van Beroep voor de Examens aan de RU Leiden omdat zijn docent, Leo van der Kamp, stelt dat c) de juiste manier is, en de student heeft argumenten dat b) de juiste manier is. Kan het College komen tot een uitspraak over wat de juiste manier is?
3) deskundige verschillen
De beide beoordelaars zijn ongetwijfeld deskundig op het gebied van de psychometrie. Waar zij tot een verschillend oordeel komen, is er kennelijk verschil van inzicht tussen deskundigen. Dat moet kunnen, dergelijke verschillen zijn eer regel dan uitzondering in het wetenschappelijke bedrijf. Hoe zijn studenten tegen die verschillen te beschermen?
4) leerling superdeskundige?
In de opleiding tot deskundige spelen dergelijke verschillen natuurlijk ook. Hoe werkt dat uit bij beoordelingen en examens? Dat is nog een lastig te beantwoorden vraag. Tenminste moet gelden dat het oordeel van de ene deskundige niet omlaag kan worden gehaald door een afwijkend oordeel van een andere deskundige. Tenslotte is een student geen superdeskundige die verschillen tussen deskundigen kan voorzien; en dan nog zou het meestal onmogelijk zijn te antwoorden zonder zichzelf te benadelen.
5) deskundige waardering
Er is niets gezegd over de scoring: daarin zouden beide deskundigen het nog met elkaar eens kunnen zijn, om vervolgens toch tot een andere waardering te komen. Als een dergelijke waardering zinnig is, wat we moeten aannemen omdat beide deskundigen door het aanvaarden van hun opdracht kennelijk menen dat ze niet onzinnig bezig zijn, dan verandert dat niets aan het boven gestelde.
6) of die waardering afschaffen?
Wie het met 5) niet eens is, moet pleiten voor het afschaffen van cijfers.
7) deskundig scoren
Wanneer deskundige beoordelaars verschillend scoren, dan blijft de regel gelden dat de hoogste gegeven score de toe te kennen score is. Vanzelfsprekend wordt dit per gescoorde opgave bepaald, niet op de somscore van de toets. Een aardig voorbeeld, waaraan Bar-Hillel en Falk (1982) een uitvoerige studie hebben gewijd omdat het allerminst evident is wat hier het juiste antwoord is:
8) helpt de psychometrie hier?
De psychometrie is niet behulpzaam bij het oplossen van dit probleem, integendeel: door het zonder verdere reflexie te bestempelen als een betrouwbaarheidsprobleem, komt ze tot de onjuiste manier c), omdat middelen de invloed van toevalsfluctuaties vermindert. Wat heet toeval. Zie de soms zeer uitvoerige (Edgeworth, 1988; Hartog & Rhodes, 1936) studies van meer dan een eeuw resp. halve eeuw geleden over het gebrek aan 'betrouwbaarheid' van oordelen over examenwerk, en de meer recente van Hofstee (1983) over het beoordelen van ZWO-subsidieaanvragen.
9) oefening in doorzichtigheid
Het voorgaande is een oefening in het toepassen van De Groot's beginsel van doorzichtigheid. Zodra deskundigen tot verschillende oordelen komen, is de doorzichtigheid geschonden. Het is vervolgens alleen maar behoorlijk, in bestuursrechtelijke zin, de beoordeelde daarvan niet de dupe te laten worden: zie bijvoorbeeld het kenbaarheidsbeginsel van Job Cohen (1981).
Een ezelsbruggetje: waar deskundigen verschillend oordelen, zou het middelen van deze oordelen een ernstige belemmering zijn bij het behalen van een '10,' overigens ook wanneer er in feite er maar een beoordelaar is.
Mijn onderzoek is gepositioneerd buiten de hoofdstroom van toetsontwikkeling, buiten de 'Received View,' de Geaccepteerde Theorie, die als psychometrisch is te typeren. Mijn eerste stelling is dan ook:
voorbeeld
'Psychometrie in de praktijk' (Cito, 1993) is een geprononceerd voorbeeld van werk binnen de Geaccepteerde Theorie. Er komt bijvoorbeeld geen verwijzing in voor naar het begrip 'doorzichtigheid' dat door Adriaan de Groot (1970) (noot 2) is geïntroduceerd en dat buiten de Geaccepteerde Theorie ligt.
Dat ik buiten de hoofdstroom werk wil niet zeggen dat ik mij ertegen afzet: een confronterende stijl van wetenschapsbeoefening is niet altijd het meest vruchtbaar, en zou in dit geval onnodig suggereren dat psychometrische methoden in het onderwijs helemaal niet thuishoren. Toch zal ik, juist vanwege die geprononceerde aanwezigheid van de Geaccepteerde Theorie, belangrijke verschillen met die Geaccepteerde Theorie wel aanduiden.
Ik zal de term 'Geaccepteerde Theorie' blijven gebruiken voor de theoretische basis vanwaaruit de meeste handboeken over 'educational measurement' zijn geschreven. Ik wil daarmee ook aangeven dat het niet aangaat voor alternatieve theorieën rechtvaardigingen te verlangen die voor de Geaccepteerde Theorie nooit zijn gegeven: de laatste theorie is geaccepteerd, wat betekent dat er geen kritische vragen bij worden gesteld, en dat de antwoorden op eventuele kritische vragen niet (meer) voorhanden zijn. Zie bijvoorbeeld Owen (1986). Zo is er van 'Toetsvragen schrijven' wel eens gezegd dat het een controversieel boek is, terwijl de vragen die daarin opgeworpen juist van de Geaccepteerde Theorie op bepaalde punten een controversiële theorie maakt.
voorbeeld
Een voorbeeld: de stelling, in 1977 (ORD), dat keuzevragen in wezen even subjectief zijn als open vragen. Ik kan mij bij een aanval op deze stelling verdedigen door te zeggen dat wie de scoringssleutel vaststelt uiteraard een subjectieve beslissing neemt. Maar ik kan ook in de tegenaanval gaan en zeggen dat de Geaccepteerde Theorie nooit heeft aangetoond dat naast objectieve scoorbaarheid er nog meer objectiviteit in keuzevragen zou kunnen schuilen.
De hedendaagse beoordelingspraktijk kan minder doelmatig zijn dan in het soms verre verleden, wanneer oude beoordelingsvormen zijn blijven bestaan terwijl de samenleving ingrijpend is veranderd. Het is zeker niet vanzelfsprekend dat het onderwijs altijd in wezenonveranderd is gebleven, en beoordelingsvormen langs de rationele weg van evaluatie en bewuste veranderingen zijn 'verbeterd.'
voorbeeld (zittenblijven)
Een pregnant voorbeeld is het zittenblijven: schoolleiders kunnen de rationale van dat zittenblijven niet aangeven, evenmin als oplossing voor wat vrijwel iedereen wel als een probleem ziet (Wald, 1985). De kosten van zittenblijven drukken met honderden miljoenen guldens jaarlijks op de begroting van Onderwijs en Wetenschappen; onderzoek laat consistent zien dat niemand beter wordt van zittenblijven; in tal van landen, o.a. in Scandinavië, bestaat dit zittenblijven niet (Bos, 1984). Al in 1940 schreef Posthumus dat het kennelijk zo is dat het percentage zittenblijvers in de Hoger Burgerschool vrijwel constant is, ongeacht ontwikkelingen in de wereld over driekwart eeuw.
De kracht van de traditie is juist in het onderwijs bijzonder groot. Organisatorische vormen kunnen blijven bestaan, ondanks ingrijpende veranderingen in de samenleving. Na de Kerk is de Universiteit de oudste maatschappelijke institutie in Europa. Het is daarom te verwachten dat vele beoordelingsvormen een behoorlijk lange geschiedenis hebben. Maar er is nauwelijks onderzoek naar deze geschiedenis gedaan, anders dan in de marge van breder geschiedkundig onderzoek, of als specialistische studie naar een bepaalde tijd en plaats. Toch zou alleen al een globaal overzicht van kenmerkende beoordelingsvormen uit het verleden kunnen leiden tot nieuwe inzichten in de hedendaagse praktijk.
Ik verwacht dat historisch onderzoek een belangrijke opbrengst kan hebben bij het achterhalen van wat nu eigenlijk de essentiële kenmerken van dat beoordelen zijn, kenmerken die in een theorie over dat beoordelen een hoofdrol moeten spelen.
voorbeeld (afwezigheid van historisch besef)
Aandacht voor de historische ontwikkeling achter hedendaagse toetspraktijken ontbreekt in vele handboeken, ook in 'Vijven en zessen.' Adriaan de Groot heeft mij geschreven dat hij sterk gemotiveerd was de gekte van het vijven en zessen in het middelbaar onderwijs aan de kaak te stellen, en misschien juist daardoor nooit is toegekomen aan de vraag wat de historische wortels van dit beoordelingsstelsel zijn.
De beweging van het authentiek toetsen (noot 3) is een beweging die eveneens zonder historisch bewustzijn is: het is een beweging die louter negatief gedefinieerd lijkt, gericht op onderscheiden naar prestaties zonder daarbij gebruik te maken van klassiek psychometrische methoden.
voorbeeld (belang van historisch inzicht)
In de geschiedenis van het onderwijs blijken beoordelen en rangordenen vaak hand in hand te gaan. Dat rangordenen is anders van karakter, al naar gelang van tijd en cultuur, maar is vaak pregnant aanwezig. Vroeger werd die rangorde ook sterk benadrukt in ceremoniële presentaties tegenover de buitenwacht. Dat dit uitbundige rangordenen plaats heeft gemaakt voor de zo anders lijkende stelsels van cijfergeven en nu van gestandaardiseerde toetsing wil niet zeggen dat het rangordenen nu uit het onderwijs is verdwenen. De studie van de historische wortels van het beoordelen in het onderwijs maakt juist duidelijk dat het wel eens zou kunnen zijn dat het cijferstelsel een meer verhulde vorm van rangordenen is. De Geaccepteerde Theorie verbergt op geen enkele manier dat zij is gebaseerd op het streven de 'ware rangorde' van leerlingen in getallen af te beelden, en toch ontstaat daarover geen brede maatschappelijke discussie.
Op zoek naar de wortels van het cijferen vallen snel twee dingen op: dat cijferen de vervanger is van rangordenen, en dat cijfers goed lijken te passen bij (eind-)examens als vertaling van judicia. De intrigerende vraag is: is er een verband tussen deze twee observaties?
rangordenen
Tot ver in de negentiende eeuw, en soms tot in de twintigste, was in het middelbaar onderwijs rangordenen aan de orde van de dag, en dat was in de meeste westerse landen het geval. Als typerend voorbeeld van hoe dat rangordenen in zijn werk gaat kan de uiteenzetting van Van Herwerden (1947, p. 40-41) dienen over het stelsel van fouten en notae zoals dat nog in 1900 in Groningen fungeerde.
In Leiden werd in de 17e eeuw over dit puntenstelsel geklaagd door Wensinck in een schrijven aan curatoren van het Leids Gymnasium: "'wie de minste fouten in de thema heeft' ontvangt de prijs, 'maar in 't land der blinden is eenoog koning, soms zijn er leerlingen met 300 of 400 fouten, en zo krijgt wie 200 fouten heeft een prijs!.'" (Coebergh van den Braak, 1988, noot 330)
de hiërarchische verhoudingen van de feodale middeleeuwen
De wortels van het rangordenen liggen in de nog dominant feodale samenleving van de middeleeuwen. Bij uitstek blijkt dat bij de universitaire examens. Dit zijn examens waarvoor eigenlijk altijd iedereen slaagt. Bij deze examens werd een rangorde van de geslaagden gemaakt, de locatus, waarvan vaak wordt gezegd dat deze rangorde competitief bepaald is (o.a. Rashdall, Prahl). Hoogstwaarschijnlijk echter is de locatus in de eerste plaats een rangordening naar maatschappelijke positie, in latere instantie naar criteria zoals duur van de inschrijving. Academische prestaties spelen bij de locatus geen hoofdrol. Dat is een belangrijk gegeven, en het is niet zonder betekenis. In andere samenlevingen tezelfdertijd, zoals de Chinese en de Moslimse, speelt competitie wel degelijk een rol, en zelfs een zeer dominante, zij het in ieder van die samenlevingen op geheel andere wijze. Het in de middeleeuwse universiteit ontbreken van competitieve examens doet de vraag rijzen waar, wanneer en waarom in Europa competitieve examens zijn ontstaan.
Toch was het idee van rangorde zowel als competitie in het middeleeuwse hoger onderwijs niet afwezig. In de dagelijkse lessen en overhoringen die de meester met zijn studenten speelde beloning en straf een rol: een prijs voor de beste, en een asinus voor de slechtste prestatie. Ook in de Latijnse school van de late middeleeuwen werd wel op deze manier beloond en bestraft. Een belangrijke onderwijsvernieuwer was Joan Cele, de rector van de school van Zwolle aan het eind van de 14e eeuw, ten tijde van het ontstaan van de beweging van de Broeders des Gemenen Levens van Geert Groote. Cele geldt als de uitvinder van het klassikaal georganiseerde onderwijs, zowel naar leerstof, als naar groepering van de leerlingen. Cele werd hierin door nood gedreven: hij zag zich geconfronteerd met vele honderden leerlingen, tot zelfs 900 toe, die hij met slechts de steun van twee Parijse magisters les moest zien te geven. De onderwijsmethodiek van Joan Cele is van doorslaggevende betekenis geweest op die van de Universiteit van Parijs en op die van de Jezuieten. De laatste hebben zeker in Frankrijk een sterk stempel op het middelbaar onderwijs gedrukt.
Het humanisme heeft in het onderwijs de nadruk gelegd op het belonen, het prijzen en het prijzen uitreiken, onder afkeuring van het straffen, vooral van het lijfelijk straffen. De jezuïeten kunnen ook in dit opzicht tot de humanistische stroming worden gerekend. Het rangordenen en prijzen is bij de humanisten vooral een middel om de leerlingen te motiveren, voor de bevordering tot hogere klassen was de rangorde niet doorslaggevend. Overigens was het in de systematiek van het laat-middeleeuwse onderwijs gebruikelijk om dezelfde cursus meermalen te volgen, en in die zin bestond er dus niet zoiets als 'zittenblijven.' Zo geven de examenreglementen van de Universiteit van Parijs opsommingen van de boeken waarover men een collegecyclus moest hebben gevolgd, en hoevaak diezelfde cyclus gevolgd moest zijn, meestal twee of drie keer.
examens
Zodra er voor maatschappelijke posities ook gerecruteerd gaat worden op basis van gevolgd onderwijs, gaan afsluitende examens een belangrijke rol spelen. Dat is ook het geval waar kunstmatig schaarste wordt gecreëerd, zoals in Duitsland waar in de 19e eeuw de toegang tot de universiteiten werd afgeknepen door het afsluitende examen van het gymnasium, de 'Abitur.' Dergelijke examens zijn dus uitdrukkelijk selectief bedoeld en vragen als legitimatie een objectieve beoordelingsmaatstaf. Een cijfersysteem komt daar als pseudo-normatief stelsel nog het dichtst bij, zeker wanneer die cijfers worden toegekend door commissies bestaande uit onafhankelijke deskundigen.
Voor Nederland doet zich rond 1870 de gelegenheid voor het eindexamen van de dan opgerichte Hogere Burgerschool te reglementeren. De eerste ervaring met de afname van het nieuwe eindexamen was dat men dat van provincie tot provinsie op eigen wijze had afgenomen, reden voor de overheid om zowel de inhoud van het examen als de beoordeling te standaardiseren. Voor de beoordeling werd de cijferschaal van 1 tot 10 voorgeschreven, met de vijf als 'even voldoende' middenpunt. De examencommissie besliste, zonodig bij meerderheid van stemmen, over de kandidaten met een of meer onvoldoende resultaten.
Het is niet onwaarschijnlijk dat onder druk van deze examenbeoordeling ook voor de beoordeling van het reguliere schoolwerk steeds meer voor het cijfersysteem is gekozen. Systematisch onderzoek hiernaar is mij niet bekend. Het is zeker niet zo dat het cijfersysteem meteen gemeengoed in het onderwijs is geworden. In ieder geval de gymnasia hebben het nog langere tijd volgehouden bij hun rangenstelsel te blijven.
Een cruciaal gegeven is de situatie in Frankrijk in het midden van de 19e eeuw. Tot dan heeft het rangordenen de overhand, en is het onderwijs gericht op de 'concours,' de rangordening die aan het eind van het schooljaar wordt aangebracht. Daarna krijgt het eindexamen, het 'baccalauréat,' een bepalende invloed op de inhoud van het onderwijs. Dat suggereert dat het rangordenen niet goed verenigbaar is gebleken met een toenemend belang van het eindexamen, en dat het rangordenen is vervangen door een beoordelingssysteem dat een afspiegeling was van dat gebruikt voor dat eindexamen.
culturele bepaaldheid
Het rangordenen in het onderwijs is dus een pregnant in de Europese cultuur aanwezige pedagogische opvatting. Het cijferen is ook geen breuk met de essentie van het rangordenen, wel met enkele van de uiterlijke verschijnselen daarvan. Zelfs de breuk is niet absoluut: nog heel lang is er gerangordend op basis van cijfers, dat is mijzelf nog overkomen in de eerste klas van de Koning Willem III school in Apeldoorn, in 1949: de plaats in de banken werd bepaald door behaalde cijfers. Iedere cijferschaal is nog steeds een schaal met een rangorde. De pretenties van de cijferschaal zijn veel groter dan die van de eenvoudige rangorde: de cijferschaal heeft de pretentie algemeen geldig te zijn. Een cijfer krijgen betekent voortaan: gerangordend te zijn binnen de groep die in Nederland in klas 3 van de Hogere Burgerschool zit, niet meer alleen maar binnen de eigen klas. Omdat de pretentie van algemeenheid niet valt waar te maken, is er met het cijferen irrationaliteit ingeslopen in het onderwijs: De Groot's Vijven en zessen zet de irrationaliteit van het cijferstelsel in de beklaagdenbank.
kan het zonder rangordenen?
Wel of niet rangordenen in het onderwijs is een keuze: we kunnen dat samen willen of juist niet. Het is mogelijk zonder rangordenen te werken, en vanuit het recht bezien zou dat ook moeten. Beoordelingen treffen de beoordeelde rechtstreeks in zijn of haar belang, en dat geldt zeker voor beoordelingen die zich uitstrekken over een groot aantal jaren. Het is een eenvoudig beginsel van behoorlijk bestuur dat het beoordelen gebeurt op basis van de verdienste van de beoordeelde persoon, en niet op die van anderen. Daar zijn uitzonderingen op, zoals bij personeelsselectie, maar die tasten het beginsel niet aan. Het moet dus zonder rangordenen kunnen.
een algemeen toetsmodel moet het 'zonder' kunnen
Het voorgaande leidt tot de eis dat een algemeen toetsmodel niet gebaseerd mag zijn op rangordenen, dus niet op verschillen tussen leerlingen. Dat is niet alleen op basis van de juridische eisen aan behoorlijk beoordelen, maar ook gezien het feit dat rangordenen zozeer cultureel bepaald is. De Geaccepteerde Theorie valt hier dan meteen af. Er is uiteraard geen bezwaar tegen later alsnog met individuele verschillen rekening te houden, maar het strookt niet met de pretentie van algemeenheid van het te presenteren model om individuele verschillen te vooronderstellen.
toepasbaar op een enkele leerling
Aan deze eis is te voldoen door een model te ontwikkelen dat op een enkele leerling toepasbaar is. Tot deze klasse van toetsmodellen behoren de tentamenmodellen van Van Naerssen, waarin op geen enkele manier sprake is van meerdere gelijktijdige deelnemers, laat staan van verschillen tussen deze deelnemers. Toch zijn deze tentamenmodellen in beginsel buitengewoon geschikt om geïntegreerde onderwijs-toets-situaties te ontwerpen, en om het werkelijke functioneren van onderwijs en toetsen te onderzoeken en bij te sturen. Deze modellen gaan uit van besliskundige technieken. Het bijzondere is dat het in deze modellen niet gaat om een beslissing van de docent over de leerling, maar om een beslissing van de leerling over haar studiestrategie.
het tentamenmodel is een specifieke toepassing
Het algemene toetsmodel is in zekere zin een uitgeklede versie van een tentamenmodel. Van Naerssen heeft een model ontwikkeld voor een heel specifieke situatie: het modelleren van de optimale studiestrategie wanneer studenten een onbeperkt aantal gelegenheden hebben om tentamen af te leggen tot zij daarvoor zijn geslaagd. De probleemstelling van Van Naerssen was: hoe kan de docent greep krijgen op de studiestrategieën van studenten, en wat zijn eigenlijk optimale strategieën voor studenten onder deze omstandigheden? De docent kan de sturen in de optimale studiestrategieën die voor studenten beschikbaar zijn.
kenmerken van het algemene model
De essentiële punten in het algemene toetsmodel zijn:
De onderwerpen 2 tot en met 5 zijn hierna uitgewerkt.
Met dit algemene toetsmodel als instrument moet het mogelijk zijn voor de studenten een beoordelingssituatie te creëren waarin zij naar absolute normen worden gewaardeerd, niet naar relatieve. Omdat in deze wereld niets echt absoluut is, behalve dat wij sterflijk zijn, zijn absolute normen alleen voor gelegenheid van deze toets of dit examen te creëren, maar dat is vanuit de eis van doorzichtigheid voldoende. Tijdelijk absolute normering is een antwoord op welles-nietes discussie over relatief versus absoluut beoordelen, zoals enkele decennia geleden gevoerd tussen Wienand Wijnen en Egbert Warries.
De test of in een bepaald geval aan de eis is voldaan: is het mogelijk dat alle beoordeelden slagen, dat alle beoordeelden hetzelfde cijfer krijgen, dat er geen stoppen meer doorslaan wanneer een toets niet blijkt te spreiden, maar dat het nog steeds mogelijk is dat ook grote prestatieverschillen tussen leerlingen verenigbaar zijn met een in alle opzichten goed functionerende beoordelings-situatie?
competitie
Het zou een illusie zijn te menen dat met deze klasse van toetsmodellen individuele verschillen geen rol meer spelen. Natuurlijk blijven individuele verschillen van belang. Studenten verschillen in motivatie en capaciteiten, en in hun levensdoelen buiten de studie waaraan zij in verschillende mate tijd willen besteden die niet tegelijk aan de studie kan worden besteed. Niet alle verschillen komen tot uitdrukking in verschillende toetsresultaten, of: dezelfde toetsresultaten kunnen uitdrukking zijn van grote persoonlijke verschillen. Is hier de Geaccepteerde Theorie dan toch weer het relevante model? Ik dacht het niet, omdat deze situatie te complex is voor deze rechtlijnige theorie. Hier is de theorie van James Coleman voor het modelleren van sociale systemen relevant. Omdat deze theorie bij uitstek geschikt is om te gebruiken in competitieve situaties, is ze ook toepasbaar overal waar in het onderwijs relatieve beoordeling en selectie plaatsvindt (en dat is op veel plaatsen, zie bijv. Wilbrink en Dronkers, 1993). Ik heb die toepassing beproefd op een geschikte dataset uit het universitair onderwijs, maar ik kan daar in het kader van deze voordracht niet verder op ingaan (zie de Literatuur voor de betreffende papers).
Voor beoordelen zijn vele verschillende modellen beschikbaar, die ook altijd wel ergens gebruikt worden, vaak terecht, vaak ook ten onrechte.
de wedstrijd
Historisch is het wedstrijd-model altijd van belang geweest. Vele examens zijn in het verleden uitdrukkelijk volgens dat model ingericht, zoals in het voorgaande behandeld. Het sollicitatie-model, de beoordeling bij personeelsselectie, is een wedstrijdmodel (noot 4). Een verwant model is dat van het twistgesprek, vooral het juridische twistgesprek; de beslisregels zijn hier natuurlijk complexer. Het twistgesprek is overigens een in de middeleeuwen zeer dominante onderwijs- en examenvorm, ook in andere faculteiten, waaronder die van de artes. De huidige academische promotie is daar naar de vorm een flauwe en enigszins omgekeerde afspiegeling van.
het meten
Een dominant hedendaags model is het natuurwetenschappelijke model van de afbeelding van de werkelijkheid in geschikte getallen. De Geaccepteerde Theorie hoort in deze klasse thuis. De vooronderstelling is dat de wereld zich niets van de meting aantrekt. Helaas gaat die vooronderstelling niet altijd op, zelfs niet in de fysica, maar zeker niet wanneer er aan mensen wordt gemeten (noot 5).
het contract
Een interessant model voor de beoordeling is het contract, waarbij partijen met elkaar afspraken maken over prestaties en tegenprestaties. Om een contract op te kunnen stellen, zijn ondubbelzinnige criteria nodig: welk loon krijgt de dagarbeider voor welke prestatie, hoe wordt die prestatie vastgesteld (gemeten, gewogen, beoordeeld), waar bestaat het loon uit en hoe wordt dat gemeten, gewogen of geteld? En aan wie kan het toezicht worden opgedragen, wie mag controleren of de maatstok van de landheer niet te kort is? In de middeleeuwen waren dergelijke 'meetproblemen' een dagelijkse bron van ellende en conflicten, en om die reden ook een prikkel om procedures te verfijnen in zowel juridisch als fysisch opzicht.
Welnu, beoordelen in het onderwijs heeft verrassend veel gemeen met dit contractmodel zoals het vroeger in de landbouw en huisnijverheid werd gebruikt (noot 6). Daar komt nog bij dat de machtsverhouding tussen de contractpartijen in het onderwijs wel erg ongelijk is. De leerling kan een als oneerlijk ervaren beoordeling lang niet altijd tot een oplossing brengen die bevredigend is naar de rechtsmaatstaven die in het gewone maatschappelijke verkeer gelden. Zo is het buitengewoon moeilijk voor de leerling een onafhankelijk onderzoek te laten doen naar de maatstok van zijn docent, of deze nog wel de afgesproken lengte heeft. Maar de docent kan er veel aan doen om het vertrouwen van de leerling te winnen: een goede ontwerpmethode voor zowel de toetsvragen als de toets zelf kunnen de beoordeling transparanter maken.
Voor de kwaliteit van toetsvragen is het van belang dat ze zijn geconstrueerd volgens heldere regels gebaseerd op een verdedigbaar didactisch concept. De cognitieve taxonomie van Bloom c.s. valt dus af als een instrument. Deze taxonomie kan in de praktijk niet consistent worden toegepast. Dat is ook niet verwonderlijk omdat Bloom c.s. uitgaan van het denken van de leerling, een psychologisme, want dat denken is niet direct waarneembaar. Het kan zijn dat Bloom c.s. door de Geaccepteerde Theorie op het verkeerde been van de 'ware beheersing' als ideaal-begrip zijn gezet. Het alternatief voor Bloom's cognitie is echter niet noodzakelijk Mager's behaviorisme.
begripsmatige leerstofanalyse
Ik heb gekozen voor begripsmatige uiteenlegging van de leerstof als beschrijving van het kennisdomein. Die keuze is ook door van Westrhenen gemaakt, zij het niet met uitsluiting van Bloom's taxonomie. Het gaat om de beschrijving van een vakgebied of een wetenschap, niet om wat zich in het denken van zijn beoefenaars afspeelt. Met dat kennisdomein moeten de beoefenaars van het vak op een bepaalde manier om kunnen gaan. Ze moeten in ieder geval de 'taal' van het vak kunnen spreken. De leerling die een inleiding in het vak krijgt, moet een goed omschreven deel van die 'taal' kunnen gebruiken. Deze benadering heeft meer verwantschap met de wetenschapstheorie die probeert te omschrijven wat de inhouden van een bepaald vak zijn dan met de psychologie. Ik maak een uitzondering voor delen van de cognitieve psychologie die zich met begripsmatige kennis bezighouden, een specialisme dat sterk is geïnspireerd door het latere werk van Wittgenstein.
De ontwerpmethode, zoals uitgewerkt in mijn 'Toetsvragen schrijven,' leidt tot een systematiek die tot resultaat heeft dat de toetsvragen in zekere zin worden gestandaardiseerd naar aard. Ik kan dat het makkelijkst als volgt duidelijk maken. Veronderstel dat de te toetsen leerstof enkele kernbegrippen bevat die de leerling goed moet beheersen. Beheersen wil zeggen dat de leerling is staat is het begrip adequaat toe te passen op aangereikte voorbeelden, situaties, casus, etc., en daarbij een onderscheid kan maken tussen wat nog wel, en wat niet meer onder dat begrip valt, of wat onder dit an wel onder een ander begrip valt. Keuzevragen zijn hier in principe heel erg geschikt voor. Alles wat met diagnostiek heeft te maken is in deze vraagvorm te brengen.
bij het voorbeeld
Het voorbeeld is een glasheldere vraag, alle alternatieven zijn relevant, de vraag zou in open vorm even glashelder zijn. Belangrijker is dat de vraag een belangrijk stuk van de leerstof bestrijkt, en dat een onbeperkt aantal varianten is te ontwerpen met behulp van transcripties van gesprekken met mensen met deze verschillende vormen van afasie. Ieder tentamen kan een of meer van deze vragen bevatten. De studenten zullen door deze vraag nooit verrast zijn, en telkens toch voor een interessante opgave kunnen staan, afhankelijk van hoe moeilijk de docent de voorbeelden wil maken. Het is ook een voorbeeld van de wijze waarop naar aard dezelfde toetsvragen toch telkens nieuw kunnen zijn.
bij de afrader
Met excuus aan de docent die deze vraag voor mij heeft bedacht: hier gaat van alles helemaal mis omdat de vraag niet is bedacht vanuit een helder ontwerp. De vraag is naar mijn ervaring representatief voor de kwaliteit van keuzevragen zoals deze door docenten in het universitair onderwijs worden ontworpen. Wat mankeert er zoal aan deze vragen, zodat studenten er gestresst van raken in plaats van er het idee aan over te houden dat ze kernbegrippen van het vak inderdaad beheersen? Het belangrijkste is wel dat het niet duidelijk is hoe studenten zich op dergelijke vragen doelmatig kunnen voorbereiden. Andere problemen zijn:
Vragen van het afrader-type kan de docent de leerling niet aandoen, ze zouden buiten het contract moeten vallen. De leerling heeft bescherming nodig tegen dergelijke rampzalige toetsvragen, maar kan die bescherming eigenlijk nergens krijgen. Pogingen als van het NIP met zijn Richtlijnen, van Job Cohen met zijn codificering van algemene regels van behoorlijk onderwijs geënt op die van behoorlijk bestuur, van de Raad voor het Jeugdbeleid, onderwijskoepels en het departement om het instituut 'leerlingenstatuut' van de grond te krijgen hebben daaraan nog weinig tot niets veranderd. In de Verenigde Staten zijn diverse verenigingen van docenten bezig codes te ontwikkelen voor 'assessment,' maar dat staat nog heel ver af van de praktijk in de klas.
Op het niveau van de toets zijn er andere problemen, die op dezelfde manier zijn te benaderen door vanuit de leerling te denken. Is de toets zo ontworpen dat de leerlingen hun score als fair beschouwen? De leerling heeft zich ingespannen in het onderwijs, en waarschijnlijk vooral in de voorbereiding op de toets, en mag door de toetsing niet teleurgesteld worden: het contract moet immers worden nagekomen dat inspanningen naar de mate van die inspanning worden beloond. Er is hier een moeilijkheid: niet de inspanning, maar het resultaat van die inspanning wordt beloond. Maar dat is niet wezenlijk anders voor de middeleeuwse dagloner die immers niet naar zijn gewerkte uren, maar naar zijn gemaakte productie werd betaald. Het gaat hier om een gegeven dat beide contractpartners heel goed begrijpen, en hoewel ook de eerlijkheid van die regel ter discussie is te stellen, wil ik dat hier vooral niet doen. Het komt er dus op neer dat de toets zo moet worden ontworpen dat meer inspanning zich direct vertaalt in een merkbaar betere kans op een hogere score. En het zou heel goed van pas komen wanneer dat niet alleen maar achteraf in de beleving van de leerling zo is, maar ook al vooraf zodat de leerling zijn inspanning kan richten op een voorspelbaar resultaat.
Welnu, dit model is gelukkig in overzichtelijk wiskundige termen te vertalen, en bij grafische uitbeelding van een en ander blijkt hopelijk dat de specifieke formules voor de gebruikers in een black box mogen blijven zitten. (noot 7)
prealabel: toetsvragenverzameling en ware beheersing
Ooit heb ik de ontwerpmethode voor toetsvragen ontwikkeld om docenten in staat te stellen op doelmatige wijze telkens nieuwe toetsvragen te maken. De behoefte daaraan werd in de tachtiger jaren sterk vergroot omdat door het inzagerecht van studenten het niet meer mogelijk was te proberen toetsvragen geheim te houden. Met dezelfde ontwerpmethode is het mogelijk een grote verzameling nog niet gebruikte toetsvragen aan te leggen, en wel op zo'n manier dat de docent met overtuiging kan zeggen dat die verzameling precies dekt wat hij met de leerstof beoogt. Als zo'n verzameling eenmaal beschikbaar is, is het mogelijk de 'ware beheersing' van studenten op die verzameling (operationeel) te definiëren. Figuur 1 is daar een 'afbeelding' van. In de praktijk is het voldoende dat de ontwerpregels duidelijk genoeg zijn om voor iedere toets vragen te kunnen maken die niet te onderscheiden zouden zijn van werkelijk uit een verzameling getrokken vragen.
voorspelbaarheid: de proeftoets als informatie
De leerling kan alleen voorspellen op grond van relevante informatie. Voor die informatie is ook een handige operationele definitie nodig. De proeftoets is een prachtig instrument om di informatie te definiëren. De proeftoets wordt getrokken uit dezelfde verzameling als de toets, zdat de score op de proeftoets relevante informatie voor de voorspelling oplevert. Laten we afspreken dat zo'n proeftoetsresultaat ook de enige relevante informatie is.
Dat het algemene toetsmodel werkt vanuit de optiek van de leerling heeft een belangrijk gevolg. Voor de leerling ziet iedere toets eruit als was deze speciaal voor haar willekeurig getrokken uit een grotere verzameling van mogelijke toetsvragen. Dat blijft zo, ook al krijgen medeleerlingen dezelfde toets voorgelegd. Voor iedere leerling ziet de toets eruit alsof deze willekeurig is getrokken. Het maakt onder dit model niet uit of andere leerlingen dezelfde toets krijgen, of een toets die gebaseerd is op een andere trekking. Dat is een interessante constatering, want het ziet ernaaruit dat het niet nodig is ervoor te zorgen dat de toets dit jaar even moeilijk of makkelijk is als vorig jaar, omdat het volstaat ervoor te zorgen dat de toets mag worden opgevat als getrokken uit dezelfde verzameling. Voor de leerling is zoiets als de moeilijkheid van afzonderlijke vragen een irrelevant begrip. Dit is een stand van zaken waar de Geaccepteerde Theorie niet mee kan leven. In de Geaccepteerde Theorie is het om een of andere reden wenselijk dat verschillende leerlingen een identieke toets krijgen voorgelegd, althans versies van de toets die 'even moeilijk' zijn. Het kan zijn dat dit een erfenis is van de bemoeienis die psychometrici ook hebben met gestandaardiseerde tests. Het is evenmin uitgesloten dat de drang tot standaardiseren een overblijfsel is uit tijden dat het rangordenen eerlijkheidshalve vroeg om identieke opgaven. Interessant is dat Rothblatt (1993) suggereert dat ten tijde van de opkomst van examens in Engeland juist de onomstreden kennis bij uitstek het onderwerp van onderwijs en zeker van examineren werd geacht: die onomstreden kennis was in zekere al gestandaardiseerd.
voorspelbaarheid gegeven de ware beheersing
Als de leerling de eigen ware beheersing zou kennen, is de voorspelling van de toetsscore de binomiaalverdeling, de kansverdeling voor het uit de verzameling trekken van vragen die goed worden beantwoord. Dat is een eenvoudig model, maar toch zitten daar voldoende haken en ogen aan om er op zich een proefschrift over te kunnen schrijven (van den Brink, 1982). De figuur laat de binomiaalverdeling als voorspelling zien, gegeven dat de ware beheersing 80 % is, en de toets 20 vragen heeft. De voorspelling is niet zo fraai wat nauwkeurigheid betreft, dat komt omdat het aantal vragen klein is. De proeftoets komt hier niet in beeld, wie zijn ware beheersing kent heeft aan proeftoetsen geen behoefte.
voorspelbaarheid gegeven de proeftoets
In werkelijkheid kent niemand de eigen ware beheersing, en blijft er ook na een proeftoets onzekerheid over die eigen beheersing. Die onzekerheid is nu nauwkeurig af te beelden, omdat onder de veronderstellingen van het model een kansverdeling is te geven voor die ware beheersing. Bovenstaande figuur geeft de kansverdeling voor de ware beheersing, gegeven dat de informatie daarvoor bestaat uit een score van 8 goed op een proeftoets met 10 vragen. Die kansverdeling is de betaverdeling met parameters 8 en 2. Volgens dezelfde statistische regels waar ook de Bayesiaanse statistiek op is gebaseerd, is de voorspellende verdeling voor de score op de toets een beta-binomiaalverdeling met als parameters het aantal goed en fout op de proeftoets en het aantal items in de nog af te leggen toets. Deze verdeling staat ook bekend onder andere namen als de negatief hypergeometrische verdeling, of de Polya-verdeling. Hoe dat ook zij, de verdeling is te berekenen, en de plot is gegeven in de onderstaande figuur. Vergelijking met de voorspelling bij gegeven ware beheersing leert dat de onzekerheid over die eigen beheersing leidt tot een minder nauwkeurige voorspelling.
Terugkerend naar de stelling kunnen we constateren dat de docent zich niet zomaar op het standpunt kan stellen dat het bij het beoordelen er alleen maar om gaat een indicatie te krijgen van wat de leerling kan. Het laatste is de fictie dat het er in het geheel niet toe doet welke indruk de leerling over de eigen beheersing heeft. De leerling is onzeker over de eigen beheersing, en kan onder die onzekerheid komen tot verkeerde beslissingen over haar studiestrategie. De docent draagt ook verantwoordelijkheid voor die studiestrategie, en kan daar in zijn toetsontwerp rekening mee houden.
Ook onderzoekers zouden zich de stelling aan kunnen trekken. Het is een wonderlijk fenomeen te zien hoe ook sociaal-wetenschappelijke onderwijsonderzoekers in hun analyses met-of-zonder-LISREL vrijwel altijd leerlingen neerzetten als objecten, als wezens zonder eigen doelen, zonder eigen levensontwerp. De belangrijke uitzondering zijn de theoretici van het menselijk kapitaal, maar economen gaan dan ook veel makkelijker om met het begrip van de mens als een rationeel wezen, die met zijn keuzen welvaart en welzijn optimaliseert. Mensen streven doelen na. Leerlingen zijn mensen, ook leerlingen streven doelen na. Het is beter daar maar rekening mee te houden: de volgende stelling.
De wereld van het onderwijs ritselt van de belangen: iedereen wil altijd wel wat met het onderwijs. Karel de Grote was bepaald niet de eerste die grootse plannen met het onderwijs had, en niet de laatste. Hij had dat onderwijs gewoon nodig, en bovendien in een vorm die nog nauwelijks bestond: om de mensen op te leiden die hij voor zijn eigen administratie nodig had. Magister Alcuin kreeg de opdracht om dat onderwijs vorm te geven. Nog steeds is het zo dat de overheid van alles wil met het onderwijs, daarbij vaak bepaald niet ondersteund door tal van belangengroepen die hun eigen doelen met het onderwijs nastreven. Het kan dus geen vreemd idee zijn dat ook leerlingen hun eigen doelen hebben, en zich daarnaar strategisch gedragen. Dat strategisch gedrag is vooral van belang op die momenten waar de greep van de school minder totalitair is: bij het huiswerk, dus bij de voorbereiding op toetsen en examens.
De conclusie moet zijn dat iedere onderwijstheorie, ook die over beoordelen, uitdrukkelijk plaats moet inruimen voor dergelijk strategisch gedrag, van zowel leerlingen als van andere direct betrokken partijen. Een theorie die dat niet doet gaat niet over de werkelijkheid van het onderwijs, en biedt geen van de actoren in dat onderwijs houvast voor het uitzetten van de eigen koers in dat onderwijs. In deze zin is de Geaccepteerde Theorie geen adequate onderwijstheorie.
de redelijke mens
Economen gaan makkelijk om met de doelen van bijvoorbeeld de handelaren op de markt. Voor hun modellen veronderstellen zij dat kopers en verkopers rationeel handelen, en dat is ook de reden waarom de tucht van de markt zo hard kan zijn. Maar in de speltheorie en de besliskunde staat het rationele handelen wel heel expliciet op de voorgrond. Ook psychologen hebben zich al vroeg de mogelijkheden gerealiseerd van theorievorming waarbij de eigen doelen van hun proefpersonen een nadrukkelijke eigen plaats kregen. Het proefschrift van Van Naerssen over de selectie van chauffeurs in het leger was hier een baanbrekende studie, die hem later bracht tot het opstellen van zijn tentamenmodellen. Er is binnen de psychologie een ijverige sectie die bestudeert hoe mensen in werkelijkheid kiezen, en hoe die keuzen soms opvallend ver verwijderd zijn van wat de econoom rationele keuzen zou noemen; de namen van Tversky en Kahneman zijn hier nauw mee verbonden.
de tucht van de campus
Drie sociologen hebben in 1960 een dieptestudie gedaan naar het campusleven aan een Amerikaanse universiteit (Kensington). Becker, Geer en Hughes hebben hun studie Making the grade gedoopt. Heel expliciet geven de auteurs aan hoe de student in zijn klas in feite over de cijfers (de grades) aan het onderhandelen is met de docent , en er voortdurend uit is te achterhalen wat er nodig is om een behoorlijk cijfer te kunnen scoren. Strategisch koersen dus, in een situatie waar studenten bijzonder in het onzekere worden gehouden over welke prestaties hoe beoordeeld zullen worden en hoe verschillende oordelen uiteindelijk in het cijfer voor het betreffende vak zullen resulteren. Voor deze studenten is de situatie buiten de klas heel belangrijk: hun activiteiten in organisaties en besturen, het lidmaatschap van een fraternity. Zij vinden dat deze activiteiten even belangrijke kwalificaties opleveren als hun cijfers. Maar er is een zeer nauwe band tussen academisch presteren en deze andere campus-activiteiten: meestal zijn er voorwaarden gesteld aan het grade-point-average om lid te kunnen worden van bepaalde fraternities en besturen, die op hun beurt een pittg beslag op de tijd van de student leggen. Deze studenten zijn dus voortdurend bezig hun beperkte tijd zo goed mogelijk te verdelen over de studie en de activiteiten daarbuiten. Deze wereld is door en door competitief.
een formeel model
Het campusleven zoals door Becker c.s. beschreven is een complex sociaal systeem. Met de typische lineaire sociaal-wetenschappelijke modellen is een dergelijke competitieve wereld niet adequaat te beschrijven. Coleman (1990) heeft aan de micro-economie ontleende modellen verder ontwikkeld om sociale situaties als deze te kunnen beschrijven.
Het gaat nu om het doelgerichte handelen zoals dat in de theorie van Coleman en in de besliskunde een plaats heeft gekregen. Het is mogelijk om bijvoorbeeld de doelen van de leerling te operationeliseren zodat er in het algemene toetsmodel mee is te rekenen. Het gaat erom een operationalisatie te vinden voor het nut dat de leerling toekent aan cijfers van verschillende hoogte, en aan de tijd die zij aan de studie en aan andere activiteiten wil besteden.
De studie van Becker et. al. laat zien dat de campusstudent de opbrengst van hun productieve tijd maximaliseert, en dat daaruit een bepaalde verdeling volgt van tijd dvoor de studie en tijd voor buitenschoolse activiteiten.
nut van het toetsresultaat
Een nutsfunctie over toetsresultaten is een persoonlijke functie; van leerling tot leerling kan die dan ook verschillen. In deze versie van het algemene toetsmodel is de beta-functie gebruikt, dat is een functie die heel plooibaar is, al naar gelang de keuze van de waarden voor de parameters, en die in zijn wiskundige eigenschappen handig past bij de beta-binomiaalverdeling.
De nutsfunctie gaat over scores op de toets, omdat in het onderwijs die scores tellen, niet de ware stofbeheersing. (noot 9) Er is echter niets op tegen om de nutsfunctie over de ware beheersing te specificeren. (noot 10) De bovenstaande figuur geeft een voorbeeld van een tamelijk steile nutsfunctie, een leerling voor wie eigenlijk alleen een score van tenminste 15 telt, en die het niet nodig vindt hoger dan die 15 uit te komen. Er zijn tal van varianten mogelijk in steilheid en plaats van de nutsfunctie, al naar gelang de voorkeuren van de leerling, en de aard van de beoordelingssituatie. Er zijn twee uitersten: de drempel-functie wanneer alleen slagen of zakken telt, en de lineaire functie wanneer ieder punt extra evenveel waard is als ieder ander punt extra. Geen van beide extremen komt in de praktijk voor. De drempelfunctie komt niet voor omdat er altijd wel enige compensatie van punten of cijfers is, of een maatschappelijk gevolg van hogere cijfers dan anderen. De lineaire functie komt niet voor omdat bijvoorbeeld aan het eind van het schooljaar er eisen zijn aan het gehaalde cijfergemiddelde.
Het nut is geschaald van 0 tot 1 omdat dat makkelijk is. (noot 11)
verwacht nut van het toetsresultaat
Met nutsfunctie en voorspelling van de toetsscore is het mogelijk het verwachte nut voor de komende toetsgelegenheid te berekenen. Dat verwachte nut krijgt pas betekenis wanneer het is te vergelijken met het verwachte nut van een alternatief, bijvoorbeeld na een extra week studeren. Daarom moet het nu nog statische toetsmodel dynamisch worden gemaakt door er de kosten van tijd en een leermodel in op te nemen.
een leermodel
Om tijd in het model te kunnen zetten, is natuurlijk een leermodel nodig waarmee tot uitdrukking komt dat uiteindelijk extra uren studie steeds minder extra stofbeheersing opleveren. Daar mag ieder aannemelijk model voor gekozen worden, ik gebruik in de nu volgende voorbeelden het halfwaardemodel. In dit leermodel kost het telkens evenveel tijd om de helft van de nog niet beheerste te stof onder de knie te krijgen. Is er een week nodig om tot een beheersing van 50 % te komen, dan is na nog een week de beheersing 75 %, en zo verder. De boenstaande figuur laat een plot van deze curve zien.
het nut van leren
Het is nu mogelijk om het leermodel te gebruiken om het verwachte nut te berekenen over de tijdschaal die is gedefinieerd door de halfwaardetijd. De bovenstaande figuur laat het resultaat zien. Natuurlijk is het zo dat na een bepaad moment de toename in het verwachte nut vermindert. Deze plot bevat op zich dus geen verrassing. Er is echter nog geen rekening gehouden met het nut van de te besteden tijd.
Deze wijze van afbeelden van de ontwikkeling in het verwachte nut na extra bestde tijd kent enige complicaties. Zo is het gebruikte leermodel deterministisch, terwijl voor de leerling ook de verwachte leerwinst na extra inspanning een onzekere factor blijft. Het leermodel is conservatief, omdat het deze onzekerheden verbergt. Een onzekerheid is bovendien dat de leerling niet weet op welk punt van de leercurve zij zich bevindt, anders dan dat af te leiden uit het resultaat op een proeftoets. Er moeten op dit punt in de toekomst ongetwijfeld betere mogelijkheden tot modelleren te vinden zijn. Voorlopig zijn deze plots heel goed bruikbaar, omdat het vooral gaat om het vergelijken van de posities vn de leerling bij de proeftoets met die welke na enige extra studie is te bereiken.
het nut van tijd
In de vroege middeleeuwen was tijd onbeperkt, en toch had niemand er echt de beschikking over: tijd behoorde aan de Heer. De heilige Benedictus stelde kloosterregels op waarin het leven strikt naar tijd werd ingedeeld, naar wat wij vandaag klokketijd zouden noemen, maar dat gebeurde niet vanuit het idee dat tijd kostbaar is. Het idee dat tijd waarde heeft is meer een geleidelijk bij kooplieden ontstaan inzicht, waarover ook de nodige problemen met de kerk zijn ontstaan. In het middeleeuwse onderwijs speelde tijd een rol zoals in het klooster: als middel om de dagelijkse bezigheden van iedereen op elkaar af te stemmen. Het is verleidelijk om de stelling te poneren dat deze vorm van tijdrekening nog steeds wordt gehanteerd door het schoolmanagement, terwijl de leerlingen meer als kooplieden met hun eigen tijd omgaan, afwegend aan welke bezigheden zij met het meeste profijt hun tijd kunnen besteden. Een botsing van een middeleeuwse met een hedendaagse wereld.
Een algemeen toetsmodel moet dus rekening houden met de tijdsinvestering van de leerling, vanuit het besef dat de leerling tijd ook in andere bezigheden kan investeren dan in de studie. Dit is dus een heel andere benadering dan de 'time on task' theorie van Carroll en vele anderen met hem, waarin tijd behandeld wordt alsof deze niets kost.
Eigenlijk moet in het model worden opgenomen wat het nut is van de dingen die de leerling anders zou kunnen doen. Om de daaruit volgende complicaties te vermijden, is gekozen voor de fictie dat de tijd zelf nut heeft. Het aan tijd toegekende nut verwijst naar het nut dat bij een alternatieve tijdbesteding wordt geboekt.
een conservatieve aanname: tijd heeft constant nut
Een eenvoudige manier om tijd in het model te brengen is door te veronderstellen dat iedere eenheid bestede tijd, of dat nu in minuten of in jaren wordt gemeten, evenveel 'alternatief nut' op kan leveren wanneer deze niet aan de studie wordt besteed. Dit is een conservatieve veronderstelling die ten gunste van het onderwijs uitpakt. In het algemeen moet immers gelden dat naarmate meer tijd aan de studie besteed is, de tijd die nog over is kostbaarder is omdat er gewoon minder van is. Het is niet altijd nodig om echt te weten hoe hoog dat het nut van tijd is, omdat modelstudies gewoon voor een aantal varianten gedaan kunnen worden.
Figuur 1. De voor de af te leggen toets verwachte utilitet E(u) als functie van de tijdsbesteding t (links); De E(u) na aftrek van de kosten van tijdsbesteding (Rechts), eveneens als functie van t. Het leermodel is t = 1 - 0,5t. De toets bestaat uit 100 vragen, de proeftoets waarop de voorspellingen zijn gebaseerd bestaat eveneens uit 100 vragen. De utiliteitsfunctie over toetsscores is de drempelfunctie 70 %.
het volledige toetsmodel
Alle ingrediënten voor het algemene toetsmodel zijn nu beschikbaar. De bovenstaande figuur laat zien wat de gevolgen voor het verwachte nut zijn wanneer tijd niet meer gratis is, maar in rekening wordt gebracht. De bovenste curve het verwachte nut is de situatie dat de docent denkt dat het tijdsbudget van zijn leerlingen onbeperkt is. Wie meent dat tijd gratis is, verwacht maximale studieprestaties omdat extra tijd altijd extra nut oplevert, hoe weinig misschien ook. En altijd zal er de teleurstelling zijn dat leerlingen aan die verwachting niet voldoen, misschien omdat ze over onvoldoende wilskracht beschikken.
Natuurlijk is tijd niet gratis, de figuur laat een plot zien van vijf varianten op de kosten van tijd.
Ook zonder enige empirische gegevens laten deze grafieken zien dat het zeer onredelijk zou zijn van leerlingen inspanningen te verwachten die ze zouden brengen tot de sub-optimale resulaten rechts van het optimum. Ergo, dit algemene toetsmodel moet een fundamentele bouwsteen kunnen zijn voor een ontwerptechnologie die rationele evaluatie en bijsturing van de belangrijkste parameters van onderwijs en toetsing mogelijk maakt. De doorzichtigheid van de toets en toetssituatie is zeker zo"n belangrijke parameter, en die hangt onder andere af van het aantal vragen in de toets. Onderstaande figuur laat een variant zien waarin de proeftoets 100 vragen en de toets 200 vragen bevat. Het groter maken van de steekproef maakt de toets doorzichtiger, en stelt de leerling in staat een doelmatiger studiestrategie te hanteren. Dat wisten we ook zonder dit model al, het model maakt dit inzicht precies, en beter hanteerbaar bij het ontwerpen van het onderwijs en zijn toetsingsmomenten.
Het model is niet alleen 'algemeen' genoemd omdat het vrij is van de culturele lading van individuele verschillen, maar ook omdat het een bouwsteen is voor modellen die op specifieke situaties zijn gericht, zoals tentamenmodellen, en modellen voor cesuurbepaling. De software-implementatie maakt het mogelijk tal van varianten door te laten rekenen, om er op deze manier achter te komen welke mogelijke invloed bepaalde veranderingen in onderwijs en beoordeling zouden kunnen hebben op de studiestrategieën van de leerlingen. Natuurlijk kunnen dergelijke sensitiviteitsstudies niet alleen richtinggevend zijn bij het onderwijsbeleid, daar is ook de controle tegen de empirische gegevsn voor nodig, maar ze kunnen aanwijzingen opleveren voor de richting waarin belangrijke verbeteringen te realiseren zijn. En deze analyses zijn buitengewoon goedkoop: het berekenen en plotten is een kwestie van minuten.
Een caveat tot slot. Het model veronderstelt niet dat de normen voor de toets tevoren zijn vastgelegd. Het is duidelijk dat voortgaan op de weg van het relatief beoordelen, het gehoorzamen aan de Wet van Posthumus, bij voorbaat de opbrengst van verbeteringen in de onderwijssituatie teniet dreigt te doen, ook vele van de verbeteringen die zijn in te boeken wanneer de leerling als volwassen partij bij de beoordeling wordt behandeld.
-- Studiestrategieën. Amsterdam: COWO (nu SCO-Kohnstamm Instituut), 1978 800k pdf. Hierin een gedetailleerde uitwerking van binomiale modellen en tentamenmodellen.
-- Enkele radicale oplossingen voor kriterium gerefereerde grensskores. Tijdschrift voor Onderwijsresearch, 1980, 5, 112-125 html. Vooral van belang voor de daarin ontwikkelde techniek om met utiliteitsfuncties te werken. Het artikel laat zien dat het voor besliskundige cesuurbepaling niet nodig is de cesuur op de onderliggende dimensie van 'ware beheersing' te kennen, een misvatting die in 1980 het abrupte einde van een veelbelovende onderzoeklijn betekende.
De toepassing van Coleman's theorie voor sociale systemen is elders al gepresenteerd:
-- Modelling the connection between individual behaviour and macro-level outputs. Understanding grade retention, drop-out and study-delays as system rigidities. In Plomp, Tj., Pieters, J. M., & Feteris, A. (1992) European Conference on Educational Research. Enschede: University of Twente. pp. 701-704. Het uitgebreide paper: SCO-Kohnstamm Instituut. html
-- The first year examination as negotiation; an application of Coleman's social system theory to law education data. In Plomp, Tj., Pieters, J. M., & Feteris, A. (1992) European Conference on Educational Research. Enschede: University of Twente. pp. 1149-1152. Het uitgebreide paper: SCO-Kohnstamm Instituut. html
Andere besproken thema's zijn te vinden in:
-- Toetsvragen schrijven. Utrecht: Het Spectrum, 1983 1.4 Mb pdf (Onderwijskundige reeks voor het hoger onderwijs, Aula 809). Dit boek geeft een ontwerptechnologie, niet alleen voor de vorm zoals ook bij Roid en Haladyna te vinden, maar vooral voor de inhoud. Het boek is in 2006 in herziening genomen, zie bv. hfdst 1
-- Toetsen en testen in het onderwijs. In S.V.O. Jaarverslag / Jaarboek 1985. Den Haag: S.V.O., 1986, 275-288 html. Deze bijdrage gaat over de belangrijke verschillen tussen toetsen en testen, en de implicaties daarvan. Zie ook het hoofdstuk Toetsen (geschreven door Han Starren) in de Richtlijnen etc. van het NIP.
-- met Dronkers, J. (1993). Dilemma's bij de groei van de deelname aan hoger onderwijs. Zoetermeer: reeks Achtergrondstudies van het Ministerie van Onderwijs en Wetenschappen. ('s-Gravenhage: DOP) html. Wie in het algemene toetsmodel het competitie-element node mist, kan hier de schade inhalen met een studie over de sociale competitie via het onderwijs om het schaarse aantal aantrekkelijke maatschappelijke posities.
Francis Y. Edgeworth (1888). The statistics of examinations. Journal of the Royal Statistical Society, 51, 599-635. (profile)
M. Bar-Hillel and R. Falk (1982). Some teasers concerning conditional probabilities. Cognition, 11, 109-122.
P. J. van Herwerden (1947). Gedenkboek van het stedelijk gymnasium te Groningen. Groningen: Wolters.
Figuur 1. Een toetsvragenverzameling van 8000 vragen (hier als blokjes), denkbeeldig allemaal beantwoord door de leerling met een score van 75 % goed (wit).
Ben Wilbrink (1998). Inzicht doorzichtig toetsen. html In Theo H. Joostens en Gerard W. H. Heijnen (Red.). Beoordelen, toetsen en studeergedrag. Groningen: Rijksuniversiteit, GION - Afdeling COWOG Centrum voor Onderzoek en Ontwikkeling van Hoger Onderwijs, 13-29.
Deze bijdrage geeft in kort bestek een overzicht van het Algemene Toetsmodel, tot en met het voorlaatste moduul belang voorspellen, en presenteert voor het eerst de inbouw van inzicht als extra parameter voor bruikbare leermodellen. De gegeven theoretische onderbouwing is summier, wijst naar oorsprongen in Van Naerssen (1970: tentamenmodel) en De Groot (1970: transparency). Ieder van de onderscheiden modulen vraagt een eigen theoretische inkadering, maar levert ook meteen praktische gevolgen en inzichten op. De gekozen operationalisatie van inzicht als gelijktijdig paraat hebben van benodigde kennis maakt het mogelijk situaties te modelleren die anders lastig zijn te analyseren. Voorbeelden daarvan: hoe moeilijk is het eigenlijk om dwarsverbanden door de stof te leggen, iets dat makkelijk van studenten wordt gevraagd? En wat betekent het voor de slaagkansen van studenten wanneer toetsvragen meer inzicht vergen dan wat in het onderwijs is geoefend?
Project: Het Algemene Toetsmodel in 2004.
In de engelstalige projectpagina's is de meest recente samenvatting van het tot nu toe gepubliceerde werk te vinden. De theorie of het model is nog niet afgerond. Op de belangrijkste onderdelen zijn evenwel toepassingsprogramma's in de vorm van applets beschikbaar. Deze applets draaien binnen uw browser, u kunt ze dus meteen uitproberen, te beginnen met applet 1 applet 1.
PROGRAMMATUUR ALGEMEEN TOETSMODEL BEN WILBRINK Op deze schijf vind je drie versies van het programma Algemeen ToetsModel (ATM), in de werk-versie zoals gebruikt voor het colloquium op 23 juni 1994. Ik heb alleen het interface gebruiksvriendelijker gemaakt, hopelijk ben ik daar een beetje in geslaagd. Applicatie ATM BenW 68000 ATM BenW 68000 is bedoeld voor Macintosh computers met een 68000 chip, zeg maar de klassieke modellen. Het is dan ook een verschrikkelijk traag programma, en hoewel je het kunt gebruiken op een snellere Macintosh, is dat niet aan te raden. Applicatie ATM BenW 68030 ATM BenW 68030 is bedoeld voor Macintosh computers met een 68020 of 68030 chip, zonder mathematische coprocessor. Applicatie ATM BenW math copr ATM BenW math copr is bedoeld voor Macintosh computers met een 68030 chip met een mathematische coprocessor. Als het even kan moet je deze versie dus gebruiken. Ik verwacht dat eventuele problemen met het draaien van deze programmatuur alleen leiden tot het zachtjes sneuvelen van de applicatie, die vervolgens gewoon opnieuw opgestart kan worden. Voor de zekerheid raad ik aan eventuele openstaande documenten in andere applicaties eerst te bewaren. Het gaat hier om een ruwe werk-versie van het programma waarin een aantal dingen nog niet werken of niet naar behoren werken. Wat in ieder geval niet werkt: een kopie van de geplotte curven maken; je kunt ze wel printen. Eventueel kun je met de utility ‘Flash it’ het hele outputwindow kopiëren en in het clipboard of het scrapbook laten zetten. Een kopie van Flash it heb ik op de schijf gezet. Wat in het clipboard of het scrapbook staat kun je vervolgens kopiëren naar een tekstfile waar je de plaatjes in op wilt nemen. Het kan zijn dat er met kleur problemen ontstaan bij printen e.d.; mocht dat zo zijn, stel de monitor dan op zwart-wit in, althans niet op kleur, met een beetje geluk gaat alles dan goed. STARTEN Starten doe je op de gwone manier, in de Macintosh-omgeving betekent dat: dubbel-klikken op het icoon van de applicatie die je wilt gebruiken, of via het archief-menu ‘open’ kiezen. Vervolgens gebeurt er heel weinig, dat komt omdat je onder het archief menu ‘kies parameters’ moet kiezen (of Command-K). Dan verschijnt er een window waarin van alles en nog wat valt in te stellen en te kiezen. KIES AANTAL PIXELS VERTICAAL Voor de hoogte van de te plotten figuren geef je het aantal pixels op. Standaard heb ik 240 pixels ingesteld, om de figuren op het scherm goed te kunnen bekijken. Voor een print op A-4 formaat is 440 heel geschikt. KIES AANTAL PIXELS HORIZONTAAL Door het aantal pixels te verminderen, wordt de af te beelden figuur smaller gemaakt. Omdat voor verwachte utiliteit de berekeningen afhankelijk zijn van de opgegeven breedte, leidt een halvering van de breedte daarvoor ook tot een halvering van de benodigde rekentijd. KIES AANTAL ITEMS IN TOETS EN PROEFTOETS Het aantal items in de toets en in de proeftoets is vrij te kiezen, het maximum is 1000. Het aantal items in de toets bepaalt de rekentijd nodig voor het berekenen van de verwachte utiliteit: afhankelijk van de snelheid van je Macintosh zul je voor 1000 items zo‘n vijf tot tien minuten moeten wachten (met mathematische coprocessor). Het programma is erg reken-intensief omdat ingewikkelde discrete waarschijnlijkheidsverdelingen worden geëvalueerd. Kies het aantal items in de toets 1) in overeenstemming met het werkelijke aantal in een specifieke toets (zeg, centraal schriftelijk Engels), of 2) naar een ophoging of vermindering van dat aantal, omdat je daarvan juist de mogelijke consequenties wilt onderzoeken. Je kunt onderzoeken wat er gebeurt wanneer het aantal toetsvragen heel groot wordt gemaakt, maar dat levert aanzienlijke rekentijden op. Kies het aantal items in de proeftoets in overeenstemming met wat je denkt dat de sterkte van de informatie is die de leerling op een bepaald moment heeft over de eigen beheersing. Een vuistregel is misschien dat dit aantal items niet meer dan een factor twee of drie verschilt van het aantal in de toets te gebruiken items, maar niets weerhoudt je om meer extreme situaties te onderzoeken. KIES AANTAL GOED IN DE PROEFTOETS Het aantal goed op de proeftoets bepaalt de meest waarschijnlijke verdeling voor de op de vragenverzameling gedefinieerde stofbeheersing. Als je de bijbehorende verdeling wilt zien, kies dan uit de PLOT-mogelijkheden alleen de ‘betaverdeling beheersing’ door het hokje ervoor aan te klikken, en OK te klikken (of een enter te geven). Je onderzoekt altijd de beslissingssituatie waar een individuele leerling voor staat, waarbij het aantal goed op een proeftoets met een gegeven aantal items de de informatie representeert die de leerling over e eigen beheersing heeft. Het relatieve aantal goed geeft natuurlijk een indicatie voor de beheersing, het aantal items in de proeftoets correspondeert met de sterkte of de nauwkeurigheid van die indicatie. NIET GEïMPLEMENTEERD Wat hier ‘niet geïmplementeerd’ is, is de mogelijkheid om in plaats van aantal goed en aantal proeftoetsitems meteen de parameters a en b voor de proeftoetsscoreverdeling op te geven. Je mist daar dus niets aan. KIES PARAMETERS VOOR DE UTILITEITSFUNCTIE Voor de utiliteitsfunctie, een cumulatieve beta-functie, moeten twee parameters worden opgegeven. De bijbehorende functie kun je laten plotten door uit de PLOT-mogelijkheden ‘utiliteit over scores’ te kiezen door het hokje ervoor aan te klikken, en OK te klikken (of een enter te geven).. . In plaats van een hoop uitleg kun je het beste een aantal verschillende waarden proberen. Standaard heb ik 70 en 30 gekozen; probeer ook eens de veel vlakkere functie met 3 en 7, en de dichter bij drempelverlies komende 300 en 700. DREMPELVERLIES De mogelijkheid om in plaats van een beta-functie als utiliteitsfunctie een drepelfunctie op te geven heb ik nog niet geïmplementeerd. Dat is overigens geen beperking, omdat het altijd mogelijk is de beta-functie zo te kiezen dat in feite de gewenste drempelfunctie ontstaat. Kies bijv. a=300 en b=700, dan heb je een drempel die voor kleinere toetsen al goed genoeg is. Zonodig kies je nog grotere waarden, bijv. 4000 en 6000. Je kunt altijd de utiliteit even plotten om te zien hoe je uitkomt, en zonodig verander je de opgave voor de parameters in de richting die je uit wilt. Vertrouw niet op wat je denkt dat de vorm van de opgegeven utiliteitsfunctie is, maak altijd een plot. Omdat de utiliteit gaat over scores, en dus een discrete verdeling is, kunnen er onverwachte sprongen in zitten, zeker wanneer de betaverdeling wordt gebruikt om drempelverlies te benaderen. KIES DE TE PLOTTEN VERDELING Er zijn zeven mogelijkheden voor te kiezen verdelingen of curven. Voorzover de aanduidingen niet voor zich spreken, wat ik me heel goed voor kan stellen: gewoon laten plotten en de bijbehorende tekst lezen, of de figuren in het colloquium-paper van 23 juni er bij nemen. Er kunnen meerdere verdelingen tegelijk (over elkaar heen) worden geplot door meerdere tegelijk aan te klikken. Dat levert niet altijd even zinvolle plots op, maar ik zou moeite moeten doen om in het programma bepaalde combinaties te verbieden. Het over elkaar heen printen van schaalwaarden en verschillende rasters is te vermijden door die opties uit te schakelen. Ik wil nog proberen in bepaalde gevallen figuren NAAST elkaar te laten printen, als me dat niet te veel hoofdbrekens kost. Wat in deze versie jammer genoeg niet kan, is enkele varianten van dezelde curve in dezelfde figuur plotten (next best thing: plot op overheadsheets die je over elkaar kunt leggen). SCHAALWAARDEN EN RASTER Spreekt voor zich; standaard worden zowel schaalwaarden als raster getoond. Voor de betaverdeling voor beheersing worden geen vertical schaalwaarden geprint, omdat deze verdelingen genormeerd zijn op een oppervlakte = 1. Het voordeel is dat verschillende betaverdelingen dan direct vergelijkbaar zijn met elkaar, wat voor de voorspellende toetsscoreverdelingen niet het geval is (althans, in deze versie). Een raster is soms vervelend, dat kun je dan uitschakelen. Bij het over elkaar heen printen van meer dan twee figuren kun je ervoor kiezen de soms conflicterende schaalwaarden helemaal weg te laten. RAADKANS KENNISPLAFOND VOORKENNIS Deze parameters zijn continu in te stellen van 0 tot 100 procent. Het effect blijkt het duidelijkst door de leercurve te laten plotten. Het gaat echter om verfijningen in het model, dus gebruik je deze opties pas wanneer je een meer levensechte afbeelding voor een bestaande situatie wilt maken waarin deze zaken een rol spelen. RAADKANS De raadkans is een parameter die in het Algemene Toetsmodel in essentie geen eigen leven leidt. Het binomiale model voor trekken uit de verzameling specificeert alleen de kans dat een willekeurig getrokken item behoort tot de groep items die ‘goed’ wordt beantwoord door de betreffende leerling. Of dat ‘goed’ is gebaseerd op raden of op weten, doet niet terzake. Anders gezegd: ‘beheersing’ is gedefinieerd INCLUSIEF eventuele raadkansen. De mogelijkheid om een raadkans te specificeren heb ik ingebouwd om het model realistisch te houden ook voor de situatie waarin keuzetoetsen gemodelleerd worden. Raadpleeg de tekst uit 1991 voor de wijze waarop die raadkans in het model is opgenomen; het komt erop neer dat met een raadkans van 25 % er in feite een soort ‘voorkennis’ van 25 % is (zie hierbeneden bij voorkennis). Is er tegelijk ook een kennisplafond lager dan 100 %, houd er dan rekening mee dat de raadkans proportioneel is ten opzichte van het kennisplafond (zie hierbeneden). KENNISPLAFOND In werkelijkheid is 100 % beheersing een ideaal dat lang niet altijd bereikbaar is, en in ieder geval meestal niet door iedereen bereikbaar is. Omdat de utiliteitsfunctie over scores wel doorloopt tot de maximale score, is het niet onbelangrijk een correctie op het ‘ideale’ plafond van 100 % aan te brengen. Voor de leerfunctie heeft een eventueel plafond een over de hele tijdas neerdrukkend effect. NB: raadkans en voorkennis zijn in het model gedefinieerd op het leermodel, dus bij een plafond van 80% en een raadkans van 40% begint de leerling niet met een verwachte score van 40% op de toets, maar van 40% van 80% ofwel 32%. Om hetzelfde effect te bereiken als een raadkans van 40% bij een kennisplafond van 100% kun je 50% kiezen. VOORKENNIS In werkelijkheid beginnen leerlingen zelden op een niveau waarop ze geen enkele vraag uit de verzameling goed zouden kunnen beantwoorden (vergelijk de voortgansgtoets bij Geneeskunde in Maastracht). Sommige leerlingen beginnen misschien al op een relatief hoog niveau van relevante voorkennis. Omdat de voorkennis in theorie en in praktijk best zo hoog kan zijn dat het voor de leerling niet loont nog verder te investeren in de voorbereiding op de toets, is het belangrijk deze parameter in het model mee te nemen. TIJDSCHAAL Standaard staat de tijdschaal op 5 ingesteld, dat is een tamelijk hoge waarde: vijf keer de studietijd nodig om de helft te leren van wat nog niet wordt beheerst. Dat kan dus korter worden gekozen, of langer, al naar gelang wat wenselijk is. Denk er altijd aan dat deze parameter genormaliseerd is: individuele verschillen in leersnelheid tussen leerlingen worden als het ware gewoon gelijkgetrokken. Dat is in zekere zin erg jammer, omdat de ene leerling makkelijk twee, vier of tien keer zo snel kan leren als een andere eerling in dezelfde klas of groep. Maar deze verschillen kun je op een andere manier wel teruglezen uit de geplotte curven: voor de langzamere leerling zijn ceteris paribus de kosten van tijd HOGER dan ze voor de snellere leerling zijn. De langzamere leerling zit dus in termen van de verwachte utiliteit MIN de kosten van de tijd (de plot met zes curven onder elkaar) op een LAGERE curve. Ook zullen snellere en langzamere leerlingen verschillen van elkaar in de utiliteit die zij aan hogere en lagere scores toekennen. VERWACHTE UTILITEIT MIN KOSTEN TIJD Een bijzonderheid van de plot van deze set curven is dat de curven worden geplot vanaf het leertijdstip dat overeenkomt met de verwachte waarde van de beheersing, gegeven de proeftoetsscore. Om de curven vanaf een vroeger punt te kunnen plotten, kan een heel lage proeftoetsscore worden opgegeven (het model zit wiskundig zo in elkaar dat de plot van de verwachte utiliteit over het aangegeven bereik van extra tijdbesteding gelijk blijft aan wat bij opgave van een hogere proeftoetsscore wordt verkregen). PLOT BEWAREN VOOR OVERLAY De mogelijkheid om over de plot voor een bepaalde curve heen nog een plot van dezelfde curve te maken maar met andere waarden voor de parameter(s) is nog niet behoorlijk uitgewerkt. Wat redelijk gaat is bijvoorbeeld de waarschijnlijkheidsverdeling voor de beheersing printen bij twee verschillende proeftoetsresultaten. Dat gaat als volgt: 1. stel gewenste parameters in, bijv. 8 goed uit 10; klik ‘betaverdeling beheersing’ aan; klik ‘plot bewaren voor overlay’ aan, klik OK. ER VERSCHIJNT GEEN PLOT, dus wacht daar niet op. 2. Kies opnieuw parameters. Verander 8 goed uit 10 in bijvoorbeeld 5 goed uit 10; klik ‘plot bewaren voor overlay’ uit, klik OK. Dit plot zowel de betaverdeling behorend bij 8 uit 10 goed als die behorend bij 5 uit 10 goed. Bij het printen van twee voorspellende toetsscoreverdelingen (de binomiaal of betabinomiaal) zijn er problemen met de verticale schaal, waardoor verdelingen niet goed vergelijkbaar zijn. Deze optie nog maar even voorzichtig gebruiken dus. De optie is juist weer heel goed bruikbaar bij het plotten van verwachte utiliteit, want daardoor is meteen een heldere vergelijking te maken van de uitkomsten onder de twee verschillende opgegeven sets van condities. ALLES NAAR WENS INGESTELD? KLIK DAN ‘OK’ Al naar gelang de gegeven opdracht is er merkbare of soms zelfs lange rekentijd nodig. Uiteindelijk verschijnt er een plot op het scherm, tenzij je vergeten hebt een keuze te maken uit de mogelijk te plotten curven. De figuur kan wel worden geprint, maar niet gecopiëerd of op andere manier bewaard. Dat is niet omdat ik dit programma als een demo beschouw, met expres beperkte mogelijkheden, maar omdat ik er nog niet toe ben gekomen dit te implementeren. ENKELE SUGGESTIES VOOR GEBRUIK 1) De verwachte utiliteit min kosten van tijdbesteding is het belangrijkste resultaat van het Algemeen Toetsmodel. Dat laat nog open op welke manieren je van dat resultaat gebruik kunt maken, dus hoe je het interpreteert in het kader van de toetssituatie die je hebt afgebeeld. De verwachte utiliteit wordt horizontaal van links naar rechts pas geplot vanaf het beheersingspunt waarop de leerling zich waarschijnlijk bevindt op het moment van de proeftoets. De interpretatie is eenvoudig: bij stijgende verwachte utiliteit zal de leerling doorgaan met studeren (althans volgens dit normatieve model) totdat het optimum bereikt is. Misschien wil de leerling tussentijds nog eens een proeftoets afleggen om de voortgang te checken. Welke van de zes geplotte curven moet je daarvoor uitkiezen? In ieder geval niet de bovenste curve want die geldt voor de fictieve situatie dat tijd niets kost. Voor een ‘langzame’ leerling zijn de kosten hoger dan voor een snellere leerling. Maar de kosten kunnen ook specifiek voor de leersituatie zijn, bijv. naarmate de toets een groter onderdeel van het curriculum bestrijkt, worden de kosten van de tijdbesteding groter (meer stof opgeven verhoogt de kosten voor de leerling). Naarmate het resultaat op de toets belangrijker is (bij gelijkblijvende omvang van de leerstof) nemen de relatieve kosten van de tijdbesteding af. Etcetera. De horizontale schaal is gegeven in tijdseenheden, die kunnen natuurlijk ook worden vervangen door beheersing: 1 = beheersing 0,5; 2 = 0,75; 3 = 0,875 etc., alles onder de aanname dat er geen raadkans, kennisplafond < 100%, of voorkennis is. 2) Doorzichtigheid, in de specifieke betekis van voorbereidbaarheid. Het Algemene Toetsmodel zoals in dit programma geïmplementeerd, gaat uit van perfecte voorbereidbaarheid. Immers, de proeftoets geeft informatie over de mate van beheersing, het gespecificeerde leermodel geeft aan dat de leerling altijd in staat is door verdere inspanning de beheersing te verhogen. Een gebrek aan voorbereidbaarheid kan op verschillende manieren toch in het model worden afgebeeld. - wanneer de halfwaardetijd door gebrekkige voorbereidbaarheid langer is dan nodig, waardoor de kosten van tijdbesteding relatief hoger zijn, en de optimale voorbereiding een lagere beheersing oplevert. - wanneer door gebrekkige voorbereidbaarheid het kennisplafond lager komt te liggen dan nodig. 3) Doorzichtigheid in de specifieke betekenis van voorspelbaarheid. Dit is een thema waarbij enige voorzichtigheid is geboden. Voorspelbaarheid heeft te maken met de kwaliteit van de toets als steekproef, dus met het aantal vragen in de toets. Nu heeft het aantal vragen op de toets geen invloed op de voorspelbaarheid in de betekenis van op de toets verwachte score, want die is niet gevoelig voor het aantal toetsvragen (behalve de afwijkingen die ontstaan door het discrete karakter). Wat wel van belang is, is de spreiding. Maar die spreiding in de voorspellende toetsscoreverdeling wordt pas van belang in de weging met de utiliteitsfunctie. Dat is misschien het best in te zien bij drempelverlies: voorspelbaarheid is dan de kans een score te behalen die ‘voldoende’ is, gegeven je proeftoetsresultaat en eventueel nog een tijdje doorstuderen. Doorzichtigheid in de specifieke betekenis van voorspelbaarheid is op zich dus niet interessant, want die voorspelbaarheid is er altijd. Wat De Groot in werkelijkheid bedoelt is dat de leerling in staat moet worden gesteld het risico om te zakken (in de situatie van drempelverlies) te beheersen, te reduceren tot een voor hem of haar aanvaardbaar niveau. Het hangt dus van de toetssituatie in zijn geheel af of in deze opvatting de doorzichtigheid voor een bepaalde leerling, of groep leerlingen, voldoende is. In deze zin is een toets met een heel klein aantal vragen dus al gauw ondoorzichtig, omdat de leerling in de situatie komt dat de kans om te slagen in de orde van grootte van de stofbeheersing komt te liggen (bij een toets met 1 vraag, cesuur = 1, is de slaagkans gelijk aan de stofbeheersing). Je zou, om voorspelbaarheid zinvol te operationaliseren, een relatieve maat kunnen gebruiken, zoeits als twee maal de standaarddeviatie van de voorspellende toetsscoreverdeling, gedeeld door het aantal items in de toets. Maar niets is zo duidelijk als plotten van die voorspellende toetsscoreverdelingen voor toetsen met verschillende lengte. SUCCES Althans, plezier ermee. program Utiliteit_Leren; { 21 juli 1991; 18 oktober 1991 } { Maakt gebruik van de structuur, procedures etc. van programma 'Simuleer' } { Voor een agenda van nog in te bouwen en uit te werken procedures etc., } { zie eind van deze file } uses BerekenEnPlotDeze; { Press.lib is een bibliotheek van procedures uit Press c.s. (1989) } procedure Initialiseer; var i, j: INTEGER; begin ArrayInit; { array met strings voor de parameterwaarden } WindowInit; MenuBarInit; DialogInit(gSD); { NewFont(Times, 9); Het font instellen nadat window en picture zijn geopend } (*basis := 88; *) { 'basis' moet als eerste een waarde krijgen } for i := 0 to gNItems do begin gSA[i] := 0; gUA[i] := 0; end; fitScores := gSA; { array voor gefitte frequentieverdeling } yScale := 50; if cumulatiefPlotten then yScale := 240; { default waarde, ter bepaling van de verticale schaal } gBoven := 140; gLinks := 90; error := FALSE; idum := TickCount; (* writeln ( f 'De seed voor de random getallen generator is ',idum); *) Ran3Init(idum); { Initialiseert array nodig voor random getallen generator; unit Press } for j := 0 to 200 do gla[j] := -1; GammLnInit; FactLNInit; end; procedure Mainloop; var DoneOnce: BOOLEAN; begin gDone := FALSE; gIfNew := FALSE; gWNEImplemented := (NGetTrapAddress(WNE_TRAP_NUM, ToolTrap) <> NGetTrapAddress(UNIMPL_TRAP_NUM, ToolTrap)); { Is WaitNextEvent geïmplementeerd of niet? Mark & Reed p. 135 raden aan dit } { altijd te programmeren volgens de laatste gegevens in APDA's Programmer's } { Guide to Multifinder, omdat deze code nogal eens is veranderd } while gDone = FALSE do begin HandleEvent; { signaleer gebeurtenissen, en handel ze af } if gIfNew then begin WindowInit; OpenNuHetWindow(gWindowOpen); { om te plotten in QuickDraw } { t.z.t. vervangen door een behoorlijke windowInit procedure } DitProgramma; ClosePicture; { Hierna gaan QuickDraw commando's weer naar het scherm } SetWindowPic(gWPtr, gMyPicture); { Attach the picture to the window } (* Let op!!! Ik moet natuurlijk alles wat niet initialiseren betreft onderbrengen *) (* in de MainLoop en de gebeurtenissen die daarin worden afgehandeld. Dat komt *) (* in orde wanneer ik alles in menu's onderbreng *) gIfNew := FALSE; { anders gaat ie maar door als gekke Henkie } end; end; end; { ----------------------- hoofdprogramma --------------------------- } begin Initialiseer; {als er niets gebeurt, in Run Options use resources aanzetten: gebruik rscr file} Mainloop; end. unit BerekenEnPlotDeze; { 21 juli 1991; 18 oktober 1991; 17 juni 1994 } { Maakt gebruik van de structuur, procedures etc. van programma 'Simuleer' } interface uses Press, Wlbr; { Press.lib is een bibliotheek van procedures uit Press c.s. (1989) } const CumulatiefPlotten = TRUE; { Frequentieverdelingen etc. als cumulatieve verd. plotten? } maxNItems = 500; { Maximaal aantal items waaruit de te voorspellen toets } { of de (gecumuleerde) proeftoets bestaat } tellen = FALSE; { WERKT NIET GOED, HANGT OP. In TellerWindow de berekeningen bijhouden } type SAT = array[0..maxNItems] of LONGINT; { Frequenties gesimuleerde toetsscores, of gecumuleerde frequenties; } { er kunnen bij declaratie als INTEGER dus niet meer dan MaxInt runs worden } { gedaan zonder risico op fouten: dus ofwel afvangen, ofwel LONGINT declareren } UAT = array[0..440] of REAL; { array voor bewaren utiliteitsfunctie } var aEstg, bEstg: REAL; { De parameters voor de Betaverdeling van domeinbeheersing } { zoals geschat vanuit gesimuleerde toetsscores. } gSA, fitScores: UAT; (*gla: array[0..200] of REAL; *) { nodig in Press' function FactLn } (* Hoe heb ik dit array dan gedeclareerd in de UNIT? *) (*idum: LONGINT; *) { bewaart laatste pseudorandom getal } error: BOOLEAN; gTijdschaal: REAL; { aantal t (halfwaardetijd) waarover te plotten } gVoorkennis, gRaadkans, gPlafond: REAL; { parameters voor leermodel } gHorU, gVertU: LONGINT; { definieert raam voor plotten utiliteitsfuncties } (* yScale: INTEGER;*) gAPrT, gBPrT: REAL; { proeftoetsresultaat , ook halve puntjes e.d. toegestaan } gAU, gBU: REAL; { voor utiliteitsfuncties als cumulatieve beta } hellingU, locatieU: REAL; { parameters voor logistische utiliteitsfunctie; } { vgl. discrimerend vermogen en moeilijkheid in IRT-modellen } gUA, gEUA: UAT; { globaal array voor bewaren utiliteitsfunctie, resp. E(U) waarden } gHistogram, gUOverToetsscores: BOOLEAN; ch: CHAR; gNItems, gNPrItems: LONGINT; { aantal items in de toets, resp. de proeftoets } procedure DitProgramma; implementation procedure DitProgramma; type stringsT = array[1..8] of string; var countDownWindow: WindowPtr; { voor schermteller } procedure PlotThis (horSchaal, yLo, yHi: REAL; cumulatief: BOOLEAN; arr: UAT; sA: stringsT); { De plaats van de figuur in het window wordt bepaald door links en onder. } { gHorU en gVertU: de breedte resp. hoogte van de figuur in pixels. } { scale: voor figuren waarin tijdbesteding op de horizontale as staat } { (aantal pixels per 1 t ), voor de andere is scale = gHorU te stellen. } { sA: 8 tekststrings die onder de figuur worden geprint. } { De verticale schaalwaarden worden verondersteld als 0 .. 1, tenzij dat anders is, } { zoals voor de plot van verschil tussen E ( U ) en gewaardeerde t, dat wordt met } { originx en yLo, yHi aangegeven. } var myRect: Rect; teller, i, j, nPunten, x, y, pendikte, pep, laatsteY: INTEGER; k: LONGINT; s1, s2, s3: str255; vertFactor, schaal: REAL; begin if gHistogram then nPunten := gNItems { histogram plotten } else nPunten := gHorU; { ELSE per pixel plotten } if cumulatief then begin { cumulatieve functie van maken : } for i := 1 to nPunten do { niet vanaf 0, er wordt maar nPunten keer iets opgeteld } arr[i] := arr[i] + arr[i - 1]; { cumulatieve } if arr[nPunten] = 0 then begin { foutmelding } writeln('Berekende oppervlakte onder de curve is nul, in Procedure PlotThis'); readln(ch); end; { normaliseer tot oppervlakte 1: } for i := 0 to nPunten do arr[i] := arr[i] / arr[nPunten]; end; { kader, raster en schaalwaarden printen } Pendikte := 2; Pensize(pendikte, pendikte); myRect.left := gLinks - pendikte; myRect.right := gLinks + gHorU + pendikte; myRect.top := gBoven - pendikte; { Altijd tegen de bovenkant van het window printen } myRect.bottom := gBoven + gVertU + pendikte; gOnder := gBoven + gVertU; FrameRect(myRect); { eerst omtrek van de figuur tekenen } Pensize(1, 1); { horSchaal geeft het aantal pixels dat voor 1 schaaleenheid, te beginnen bij 0, } { beschikbaar is. } teller := -1; if horschaal = gHorU + 1 then begin { schaal van 0 tot 1 } moveto(gLinks, gOnder + 20); DrawString('0'); moveto(gLinks + gHorU - 3, gOnder + 20); DrawString('1'); end else for i := 0 to gHorU do begin if horSchaal < 2 then pep := 25 { schaalwaarden om de 25 pixels printen } else if horSchaal < 5 then pep := 20 { idem om de 3.2*20 als horschaal = 3.2, etc. } else if horSchaal < 11 then pep := 10 else if horSchaal < 16 then pep := 5 else if horSchaal < 21 then pep := 5 { komt goed uit bij 400 pixels en 20 items } else if horSchaal < 41 then pep := 2 else pep := 1; { Bij het opgeven van de breedte in pixels kunnen strategische getallen worden gekozen} { zoals bij een toets van 100 vragen 400 pixels, ipv 440 } if (i mod (round(pep * horschaal)) = 0) or (i = gHorU) then begin teller := teller + 1; moveto(gLinks + i, gOnder + 2); if dialRadio^[SCHAAL_WAARDEN] = 1 then lineto(gLinks + i, gOnder - 2); PenPat(gray); { verticale lijnen in de figuur trekken } if dialRadio^[RASTER_OF_NIET] = 1 then if (teller <> 0) and (round(teller * horSchaal) <> gHorU) then lineto(gLinks + i, gOnder - gVertU); moveto(gLinks - 1 + i + round(10 / pep - 7), gOnder + 20); if teller * pep <> nPunten + 1 then if dialRadio^[SCHAAL_WAARDEN] = 1 then DrawInteger(teller * pep); PenPat(black); end; end; { schaalwaarden verticaal : } teller := 11; for i := 0 to gVertU do if i mod (round(gVertU / 10)) = 0 then begin teller := teller - 1; moveto(gLinks - 2, gOnder - gVertU + i); { markering tekenen } if dialRadio^[SCHAAL_WAARDEN] = 1 then lineto(gLinks + 2, gOnder - gVertU + i); PenPat(gray); if dialRadio^[RASTER_OF_NIET] = 1 then if (teller <> 0) and (teller <> 10) then { horizontale lijnen intekenen } lineto(gLinks + gHorU, gOnder - gVertU + i); PenPat(black); if dialRadio^[SCHAAL_WAARDEN] = 1 then begin NumToString(trunc(yHi), s3); if yHi <> 1 then begin if yHi > 1 then NumToString(Round(10 * (yHi - Trunc(yHi))), s1) else NumToString(Round(100 * (yHi - Trunc(yHi))), s1); if yHi < 50 then s3 := Concat(s3, ',', s1); end; NumToString(trunc(yLo), s1); s2 := '0'; moveto(gLinks - 35, gOnder + 4 - gVertU + i); if teller = 10 then DrawString(s3) else if teller = 0 then begin if yLo < 0 then moveto(gLinks - 40, gOnder + 4 - gVertU + i); DrawString(s1) end else if teller = 5 then begin { alleen de waarde voor het middel van de schaal printen } if yHi - (1 - teller / 10) * (yHi - yLo) = 0 then DrawString(s2); if yHi >= 1 then begin if yHi >= 100 then NumToString(Abs(round((yHi - (1 - teller / 10) * (yHi - yLo)))), s1) else NumToString(Abs(round(10 * (yHi - (1 - teller / 10) * (yHi - yLo)))), s1) end else begin NumToString(Abs(round(100 * (yHi - (1 - teller / 10) * (yHi - yLo)))), s1); if Length(s1) = 1 then s1 := Concat('0', s1); { om 0,05 ook als 0,05 geschreven te krijgen, niet als 0,5 } end; if (yHi - (1 - teller / 10) * (yHi - yLo)) >= 0 then begin if (yHi - (1 - teller / 10) * (yHi - yLo)) < 1 then s1 := Concat('0,', s1); moveto(gLinks - 35, gOnder + 4 - gVertU + i); end else begin s1 := Concat('-0,', s1); moveto(gLinks - 40, gOnder + 4 - gVertU + i); end; DrawString(s1); { print tussenliggende waarden met 1 decimaal } end; end; end; for j := 1 to 7 do for i := 7 downto 1 do if sA[i] = '' then begin sA[i] := sA[i + 1]; sA[i + 1] := ''; end; for i := 1 to 8 do begin Moveto(gLinks - 10, gOnder + 25 + i * 16); DrawString(sA[i]); end; if horSchaal = 0 then writeln('Schaalfactor is 0 in Procedure PlotThis.'); x := gLinks; y := gOnder - round(arr[0] * gVertU); Moveto(x, y); vertFactor := gVertU / (yHi - yLo); { aantal pixels gedeeld door het schaalbereik } for i := 0 to nPunten do begin { er moeten nPunten + 1 'kolommen' worden geplot } { en dan nog een laatste horizontale streepje } laatsteY := gOnder - round((arr[i] - yLo) * vertFactor); { arr bevat de juiste functiewaarden, om te kunnen plotten worden die } { vertaald naar aantal pixels dat van gOnder moet worden afgetrokken } if not cumulatief and (nPunten = gHorU) then begin schaal := 1; { voor leercurve heeft 'scale' andere betekenis } { dan per pixel berekend en te plotten } x := gLinks + round(i * schaal); end else x := gLinks + round(i * horSchaal); if nPunten <> gHorU then begin if arr[i] >= yLo then begin Lineto(x, laatsteY); { bij histogrammen nodig: horizontale lijn } end; end else begin x := gLinks + i; { ook bij drempel nodig: kleine horizontale lijn: } if i > 0 then if i + 1 <= gHorU then if arr[i] - arr[i - 1] = 1 then if arr[i - 1] >= yLo then begin Lineto(x, laatsteY); end; end; y := gOnder - round((arr[i] - yLo) * vertFactor); if (arr[i] >= yLo) and (arr[i] <= yHi) then Lineto(x, y) else { zet de pen op het volgende punt neer zonderlijn te trekken } if i < nPunten then begin { voorkom floating point error bij i = nPunten } if nPunten <> gHorU then Moveto(gLinks + round((i + 1) * horSchaal), gOnder - round((arr[i + 1] - yLo) * vertFactor)) else Moveto(gLinks + i, gOnder - round((arr[i + 1] - yLo) * vertFactor)); end; end; { FOR i := 0 TO nPunten DO } end; { PlotThis } function fBeta (a, b, x: REAL): REAL; { betaverdeling } { Evalueert de betaverdeling beta ( a, b ) voor argument x; } { maakt gebruik van procedures GammLn of FactLn uit Press.lib } begin { de ln over de formule voor de betaverdeling, en daar dan weer de exp over } fBeta := EXP(-GammLn(a) - GammLn(b) + GammLn(a + b) + (a - 1) * LN(x) + (b - 1) * LN(1 - x)); (* fBeta := EXP ( - FactLN ( a - 1 ) - FactLN ( b - 1 ) + FactLN ( a + b -1 ) *) (* + ( a - 1 ) * LN ( x ) + ( b - 1 ) * LN ( 1 - x )); *) end; { fBeta } function arcsin (x: real): real; { A.R. Miller. Pascal programs for scientists and engineers. Dusseldorf: Sybex, } {1981 p. 9, gechecked op tabel 18 Novick & Jackson. Hoewel N&J uitgebreid } { gebruik maken van de arcsin transformatie, zijn zij niet helder over de } { daartoe te gebruiken formules; in plaats daarvan geven zij tabellen. De } {uitkomsten van de formule van Miller stemmen overeen met de waarden in } { tabel 18a van N&J. N&J maken gebruik van een enigszins misleidende notatie. } begin if x = 0 then arcsin := 0 else arcsin := arctan(x / sqrt(1.0 - sqr(x))) end; { arcsin } procedure CumBeta (a, b: REAL; n: INTEGER; var arr: UAT); { Plot cumulatieve betaverdeling als 'continue' utiliteitsfunctie, } { zoals past bij domeinscores } var sA: stringsT; s1, s2, s3: str255; horschaal, yLo, yHi: REAL; cumulatief: BOOLEAN; i: INTEGER; procedure Utiliteit_CumBeta (a, b: REAL; n: INTEGER; var arr: UAT); var x: REAL; { parameters betafunctie } i, j, nPunten: INTEGER; begin yLo := 0; yHi := 1; cumulatief := TRUE; if gUOverToetsscores then begin { utiliteit over TOETSscores nemen } nPunten := n; horschaal := gHorU / nPunten; { niet delen door nPunten + 1, maar door nPunten } end else begin nPunten := gHorU; horSchaal := 1; { utiliteit over DOMEINscores nemen } end; for i := 1 to 8 do sA[i] := ''; arr[0] := 0; { per definitie heeft score 0 utiliteit 0 } { Voor raden hoeft de utiliteitsfunctie niet te worden aangepast: } { voor een gegeven toetsresultaat is immers niet bekend welk deel } { van de score door raden is verkregen. } { 'Correctie voor raden' is een groepscorrectie! } if (a = 1) and (b = 1) then { lineaire utliteit } (* t.z.t. de mogelijkheid inbouwen de helling en locatie voor lineaire utiliteit op te geven *) for i := 1 to nPunten do arr[i] := 1 / nPunten else if a + b > 100000 then { de bedoeling is dan een drempelfunctie } begin { bij drempelverlies, dus als grote waarden voor gAU en gBU opgegeven, } { en utiliteit over toetsscores, dan wordt drempel .8 bij 10 toetsvragen } { zo geinterpreteerd dat score 8 utiliteit 1 heeft. } for j := 1 to nPunten do arr[j] := 0; i := round(nPunten * a / (a + b)); { wat is de eerste arr [ i ] die utiliteit 1 oplevert: } arr[i] := 1; { PlotThis maakt er nu wel een cumulatieve verdeling van } end else begin for i := 1 to nPunten do arr[i] := 0; x := a / (a + b); { plotten gebeurt in twee delen, telkens vanuit het gemiddelde } i := round((nPunten + 1) * x); {'basis' is gelijk het aantal pixels in de basis } repeat { voor iedere pixel / vraag rechts van het gemiddelde } x := (i + 0.5) / (nPunten + 1); { evalueren op middenpunten histogrammen, normaliseren van nPunten + 1 } { naar 1; dit kan onnauwkeurig zijn bij kleine aantallen toetsvragen, maar } { kleine aantallen toetsvragen leveren zelf ook onnauwkeurigheid op } arr[i] := fBeta(a, b, x); i := i + 1; until (round(arr[i - 1] * gVertU) = 0) or (i = nPunten + 1); { doelmatigheid: bij bereiken extreme waarde verdere berekeningen stoppen } { voor i = nPunten is x = 1, dan geen beta evalueren! } x := a / (a + b); i := round((nPunten + 1) * x); repeat { voor iedere pixel links van het gemiddelde } x := (i + 0.5) / (nPunten + 1); arr[i] := fBeta(a, b, x); i := i - 1; until (round(arr[i + 1] * gVertU) = 0) or (i = 0); { voor i = 0 is arr [ i ] al gelijk 0 gesteld } end; for i := 1 to nPunten do arr[i] := arr[i] + arr[i - 1]; { cumulatieve verdeling } for i := 0 to nPunten do arr[i] := arr[i] / arr[nPunten]; { normaliseer tot oppervlakte 1 } end; { BetaUtiliteit } begin for i := 1 to 8 do sA[i] := ''; Utiliteit_CumBeta(a, b, n, arr); horschaal := gHorU / n; { niet delen door n + 1, maar door n } sA[1] := 'NUTSFUNCTIE'; if gUOverToetsscores then begin NumToString(gNItems, s2); sA[3] := Concat('over de scores op een toets met ', s2, ' vragen'); end else sA[3] := 'over domeinscores'; if a + b > 100000 then begin RealToString(a / (a + b), 2, s1); s1 := Concat('drempel ', s1); sA[2] := Concat(sA[1], s1); end else if (a = 1) and (b = 1) then sA[2] := 'lineaire ' else begin sA[2] := 'de functie is de cumulatieve beta ('; if Trunc(a) = a then NumToString(Trunc(a), s1) else RealToString(a, 1, s1); { in 1 decimaal is wel voldoende } if Trunc(b) = b then NumToString(Trunc(b), s2) else RealToString(b, 1, s2); sA[2] := Concat(sA[2], s1, ', ', s2, ')'); end; cumulatief := FALSE; { verdeling is al cumulatief } if gUOverToetsscores then gHistogram := TRUE else gHistogram := FALSE; if dialRadio^[PLOT_UTIL_CURVE] = 1 then PlotThis(horSchaal, yLo, yHi, cumulatief, arr, sA); { Bij gebruik van deze procedure door andere procedures } { moet de cumulatieve functie worden teruggegeven: } end; procedure Leercurve; { Deze procedure is alleen bedoeld om een plot van de leercurve te maken } { De algemene formule: domeinbeheersing p = 1 - .5 ** t = 1 - exp ( ln t * ( 0.5 ) ) } { De specifieke waarde 0.5 in het halfwaardemodel : } { omdat de tijdseenheid t niet is gespecificeerd, is 0.5 een perfect algemeen model } { De leercurven die met deze procedure kunnen worden geplot zijn eerder behandeld } { in Studiestrategieën, bijlage D. } { voorkennis is de kennis die de deelnemer al had, de leercurve } { vangt op tijdstip t = 0 hoger aan. } { Hoofdstuk 4, leermodel, geeft een eenvoudige formule, formule (3): } { geleerd := voorkennis + ( 1 - voorkennis ) * ( 1 - voorkennis ( t * ln ( 0.5 ))) } { = 1 - ( 1 - voorkennis ) * exp ( t * ln ( 0.5 )); } { raadkans wordt wiskundig op dezelfde manier behandeld als voorkennis, } { maar is daaraan niet identiek ! geleerd := 1 - ( 1 - guess ) * exp ( t * ln ( 0.5 )). } { Een aanloopperiode is niet expliciet gemodelleerd. Voor eventuele herkansingen is } { deze niet van belang. Er kan altijd verondersteld worden dat de aanloopperiode al } { voorbij is. Er is altijd een plafond aan bereikbare domeinbeheersing dat lager is } { dan 1, bijv. door dubbelzinnige vragen, door niet altijd volledige concentratie, } { door herinneringsfouten, e.d. } { De formule voor domeinbeheersing wordt gewoon met 'plafond' vermenigvuldigd. } var i, j, teller, nPunten: INTEGER; sA: stringsT; s1, s2, s3: str255; tijdschaal, yLo, yHi: real; cumulatief, overToetsscores: BOOLEAN; arr: UAT; function Geleerd (t: REAL): REAL; { Formule [ 6 ] in Hoofdstuk 4, leermodel } begin geleerd := gPlafond * (1 - (1 - gVoorkennis) * (1 - gRaadkans) * exp(t * ln(0.5))); end; begin tijdschaal := gHorU / gTijdschaal; { aantal pixels beschikbaar per tijdseenheid } yLo := 0; yHi := 100; { plot percentages } for i := 1 to 8 do sA[i] := ''; cumulatief := FALSE; { Procedure PlotThis moet geen cumulatieve verdeling maken } for i := 0 to gHorU do { voor iedere pixel op de horizontale as } arr[i] := 100 * geleerd(i / tijdschaal); { percentages } sA[1] := 'LEERCURVE'; sA[2] := 'verticaal: beheersing in %; horizontaal:aantal keer halfwaardetijd'; if gRaadkans <> 0 then begin sA[3] := ('raadkans van toetsvragen is '); RealToString(gRaadkans, 2, s2); sA[3] := Concat(sA[3], s2); end; if gVoorkennis <> 0 then begin sA[4] := ('niveau voorkennis is '); RealToString(gVoorkennis, 2, s1); sA[4] := Concat(sA[4], s1); end; if gPlafond <> 1 then begin sA[5] := ('kennisplafond is '); RealToString(gPlafond, 2, s3); sA[5] := Concat(sA[5], s3); end; gHISTOGRAM := FALSE; PlotThis(tijdschaal, yLo, yHi, cumulatief, arr, sA); end; { PlotLeercurve } procedure BepaalVTV (a, b: REAL; n: INTEGER; var VTV: UAT); { Bepaalt Voorspellende Toetsscoreverdeling VTV als BetaBinomiaalverdeling, } { slaat de frequentieverdeling op in array sInt. De Procedure PlotFrequenties } { kan voor plotten van de verdeling worden gebruikt. } { BetaBinomiaalVerdeling met parameters nI: aantal toetsvragen, } { x: aantal goed, a en b de parameters van de BetaVerdeling. } { a en b kunnen ook reeel worden gekozen, maar dan moet de procedure } {FactLN worden aangepast, vergelijk functie fBeta. } { De procedure is gebaseerd op de procedure BeBiFit. } var i: INTEGER; Fn, FAB, Fa, Fb, Fabn: EXTENDED; begin { reële parameterwaarden worden gebruikt om de betabinomiaal te bepalen, } { en dat is de enige manier om geen 'jagged' plots te krijgen: } Fn := FactLN(n); Fab := GammLn(a + b); Fa := GammLn(a); Fb := GammLn(b); Fabn := GammLn(a + b + n); for i := 0 to n do { voor iedere mogelijke score } begin VTV[i] := round(1000000 * EXP(Fn - FactLN(i) - FactLN(n - i) + Fab - Fa - Fb + GammLn(a + i) + GammLn(b + n - i) - Fabn)); end; { De grote vermenigvuldigingsfactor is bedoeld om niet op een array of real te hoeven overgaan } end; { BepaalVTV } procedure Betabinomiaal; { Bereken betabinomiaal op opgegeven parameters, voor een plot } var VTV: UAT; sA: stringsT; s1: str255; horschaal, yLo, yHi: real; e: extended; i: INTEGER; cumulatief: BOOLEAN; { Moet PlotThis cumuleren voordat de verdeling wordt geplot? } begin for i := 0 to gNItems do VTV[i] := 0; for i := 1 to 8 do sA[i] := ''; BepaalVTV(gAPrT, gBPrT, gNItems, VTV); { aantal goed en fout in proeftoets, n items in toets } sA[1] := 'VOORSPELLENDE TOETSSCOREVERDELING'; sA[2] := 'het proeftoetsresultaat is '; NumToString(Round(gAPrT), s1); sA[2] := Concat(sA[2], s1, ' goed uit '); NumToString(Round(gAPrT + gBPrT), s1); sA[2] := Concat(sA[2], s1); sA[3] := 'de voorspelde toets heeft '; NumToString(gNItems, s1); sA[3] := Concat(sA[3], s1, ' vragen'); horschaal := gHorU / gNItems; { niet delen door gNItems + 1, maar door gNItems } yLo := 0; { Voor de hoogte van de figuur moet ik yHi geschikt kiezen naar het aantal items in de toets: } if gNItems < 4 then yHi := 1 else if gNItems < 11 then yHi := 0.5 else if gNItems < 21 then yHi := 0.4 else if gNItems < 41 then yHi := 0.3 else if gNItems < 101 then yHi := 0.2; e := 0; { oppervlakte onder curve normeren op 1 } for i := 0 to gNItems do e := e + VTV[i]; for i := 0 to gNItems do VTV[i] := VTV[i] / e; for i := 0 to gNItems do if VTV[i] > yHi then yHi := 1.05 * VTV[i]; gUOverToetsscores := TRUE; gHistogram := TRUE; cumulatief := FALSE; PlotThis(horschaal, yLo, yHi, cumulatief, VTV, sA); end; procedure Binomiaal; { Bereken binomiaal op opgegeven parameters, voor een plot } var BINO: UAT; sA: stringsT; s1: str255; horschaal, yLo, yHi: real; e: extended; i: INTEGER; cumulatief: BOOLEAN; { Moet PlotThis cumuleren voordat de verdeling wordt geplot? } procedure BepaalBINO (p: REAL; n: INTEGER; var BINO: UAT); { Bepaalt Voorspellende Toetsscoreverdeling als Binomiaalverdeling, } { slaat de frequentieverdeling op in array BINO. } { kan voor plotten van de verdeling worden gebruikt. } { BinomiaalVerdeling met parameters nI: aantal toetsvragen, } { ware beheersing p gebaseerd op de parameters van de BetaVerdeling. } { Deze procedure ontleend aan wat ik in VergelijCumbetas heb gebruikt. } var x, i: INTEGER; r, t: EXTENDED; begin { kans op x successen: } for x := 0 to n do begin r := 1; for i := 0 to x - 1 do r := r * ((n - i) / (x - i)) * p; for i := 1 to n - x do r := r * (1 - p); BINO[x] := r; end; end; { BepaalBINO } begin for i := 0 to gNItems do BINO[i] := 0; for i := 1 to 8 do sA[i] := ''; BepaalBINO(gAPrT / (gAPrT + gBPrT), gNItems, BINO); { aantal goed en fout in proeftoets, n items in toets } sA[1] := 'BINOMIALE VOORSPELLENDE TOETSSCOREVERDELING'; sA[2] := 'bij veronderstelde WARE BEHEERSING '; NumToString(Round(100 * gAPrT / (gAPrT + gBPrT)), s1); sA[2] := Concat(sA[2], s1, ' %'); sA[3] := 'de voorspelde toets heeft '; NumToString(gNItems, s1); sA[3] := Concat(sA[3], s1, ' vragen'); horschaal := gHorU / gNItems; { niet delen door gNItems + 1, maar door gNItems } yLo := 0; { Voor de hoogte van de figuur moet ik yHi geschikt kiezen naar het aantal items in de toets: } if gNItems < 4 then yHi := 1 else if gNItems < 11 then yHi := 0.5 else if gNItems < 21 then yHi := 0.4 else if gNItems < 41 then yHi := 0.3 else if gNItems < 101 then yHi := 0.2; e := 0; { oppervlakte onder curve normeren op 1 } for i := 0 to gNItems do e := e + BINO[i]; for i := 0 to gNItems do BINO[i] := BINO[i] / e; for i := 0 to gNItems do if BINO[i] > yHi then yHi := 1.05 * BINO[i]; gUOverToetsscores := TRUE; gHistogram := TRUE; cumulatief := FALSE; PlotThis(horschaal, yLo, yHi, cumulatief, BINO, sA); end; procedure Beta; { Bereken betaverdeling op opgegeven parameters, voor een plot } var BETA: UAT; sA: stringsT; s1, s2: str255; horschaal, yLo, yHi: real; e: extended; i: INTEGER; cumulatief: BOOLEAN; { Moet PlotThis cumuleren voordat de verdeling wordt geplot? } procedure BepaalBETA (a, b: REAL; var BETA: UAT); var x: REAL; i: INTEGER; begin for i := 1 to gHorU - 1 do begin x := i / gHorU; BETA[i] := fBeta(a, b, x); end; BETA[0] := 0; BETA[gHorU] := 0; end; { BepaalBETA } begin for i := 0 to gNItems do BETA[i] := 0; for i := 1 to 8 do sA[i] := ''; BepaalBETA(gAPrT, gBPrT, BETA); sA[1] := 'KANSVERDELING VOOR DE WARE BEHEERSING'; sA[2] := 'horizontaal: ware beheersing van 0 tot 100 %'; sA[3] := 'de oppervlakte onder de kansverdeling = 1'; sA[4] := 'het proeftoetsresultaat is '; NumToString(Round(gAPrT), s1); sA[4] := Concat(sA[4], s1, ' goed uit '); NumToString(Round(gAPrT + gBPrT), s1); sA[4] := Concat(sA[4], s1); horschaal := 1; { niet delen door gNItems + 1, maar door gNItems } yLo := 0; yHi := 0; { Voor de hoogte van de figuur moet ik yHi geschikt kiezen naar het aantal items in de toets: } e := 0; { oppervlakte onder curve normeren op 1 } for i := 0 to gHorU do e := e + BETA[i]; for i := 0 to gHorU do BETA[i] := 0.05 * gHorU * gVertU * BETA[i] / e; yHi := gVertU; gHistogram := FALSE; cumulatief := FALSE; PlotThis(horschaal, yLo, yHi, cumulatief, BETA, sA); end; procedure SluitTellerWindow; begin (* diverse QuickDraw instellingen herstellen, zie OpenTellerWindow *) DisposeWindow(countDownWindow); BeginUpdate(WindowPtr(gWPtr)); (*DrawMyPicture(WindowPtr(gWPtr));*) EndUpdate(WindowPtr(gWPtr)); ShowWindow(gWPtr); { Toon het window waarin wordt geplot } while not button do begin end; end; procedure EvalueerExpU (tijdschaal: REAL; var arrEU: UAT); { Nadat de proeftoetsscore is verkregen, wordt de verwachte utiliteit geplot tegen de} { tijdbesteding die daarvoor nodig is. Om te beginnen worden de schaalwaarden absoluut } { gegeven, dus niet als extra (tijd) en winst (in verwachte utiliteit) t.o.v. het moment } { van de proeftoetsafname.} { aUt en bUt zijn de parameters voor de utiliteitsfunctie in de vorm van een cumulatieve beta;} { aPr is aantal vragen goed in de proeftoets, a + b is aantal vragen in de proeftoets;} { n is het aantal vragen in de te voorspellen toets;} { gVertU en gHorU geven de omvang in pixels van de te plotten figuur;} { pre is voorkennis, guess is de raadkans, top is het kennisplafond voor de leercurve.} { } label 123; var i, j: INTEGER; at, bt, beheersing, tijdbesteding, constante: REAL; VTV: UAT; { voorspellende toetsscoreverdeling } arrUf: UAT; { voor ophalen utiliteitsfunctie } s1: str255; nPunten: INTEGER; scale: REAL; function VerwachteUtiliteit (n: INTEGER; VTV, arr: UAT): REAL; var i: INTEGER; expU: extended; begin ExpU := 0; for i := 0 to n do ExpU := ExpU + VTV[i] * arr[i]; VerwachteUtiliteit := ExpU / 1000000; end; function Geleerd (t: REAL): REAL; begin geleerd := gPlafond * (1 - (1 - gVoorkennis) * (1 - gRaadkans) * exp(t * ln(0.5))); end; procedure OpenTellerWindow; { voor schermteller een window initialiseren } procedure SetUpCountDownWindow; var myRect: Rect; fontNum: INTEGER; begin myRect := countDownWindow^.portRect; SetPort(countDownWindow); GetFNum('monaco', fontnum); TextFont(fontNum); TextSize(24); TextFace([bold]); TextMode(srcCopy); { paint over existing graphics } end; begin HideWindow(gWPtr); { Verberg het window waarin wordt geplot } countDownWindow := GetNewWindow(STANDAARD_ID + 1, nil, WindowPtr(-1)); SetUpCountDownWindow; ShowWindow(countDownWindow); Moveto(20, 100); DrawString('Ik tel de berekeningen'); end; procedure Utiliteit_CumBeta (a, b: REAL; n: INTEGER; var arr: UAT); var sA: stringsT; s1, s2, s3: str255; x, horschaal, yLo, yHi: REAL; { parameters betafunctie } i, j, nPunten: INTEGER; cumulatief: BOOLEAN; begin yLo := 0; yHi := 1; cumulatief := TRUE; if gUOverToetsscores then begin { utiliteit over TOETSscores nemen } nPunten := n; horschaal := gHorU / nPunten; { niet delen door nPunten + 1, maar door nPunten } end else begin nPunten := gHorU; horSchaal := 1; { utiliteit over DOMEINscores nemen } end; for i := 1 to 8 do sA[i] := ''; arr[0] := 0; { per definitie heeft score 0 utiliteit 0 } { Voor raden hoeft de utiliteitsfunctie niet te worden aangepast: } { voor een gegeven toetsresultaat is immers niet bekend welk deel } { van de score door raden is verkregen. } { 'Correctie voor raden' is een groepscorrectie! } if (a = 1) and (b = 1) then { lineaire utliteit } (* t.z.t. de mogelijkheid inbouwen de helling en locatie voor lineaire utiliteit op te geven *) for i := 1 to nPunten do arr[i] := 1 / nPunten else if a + b > 100000 then { de bedoeling is dan een drempelfunctie } begin { bij drempelverlies, dus als grote waarden voor gAU en gBU opgegeven, } { en utiliteit over toetsscores, dan wordt drempel .8 bij 10 toetsvragen } { zo geinterpreteerd dat score 8 utiliteit 1 heeft. } for j := 1 to nPunten do arr[j] := 0; i := round(nPunten * a / (a + b)); { wat is de eerste arr [ i ] die utiliteit 1 oplevert: } arr[i] := 1; { PlotThis maakt er nu wel een cumulatieve verdeling van } end else begin for i := 1 to nPunten do arr[i] := 0; x := a / (a + b); { plotten gebeurt in twee delen, telkens vanuit het gemiddelde } i := round((nPunten + 1) * x); {'basis' is gelijk het aantal pixels in de basis } repeat { voor iedere pixel / vraag rechts van het gemiddelde } x := (i + 0.5) / (nPunten + 1); { evalueren op middenpunten histogrammen, normaliseren van nPunten + 1 } { naar 1; dit kan onnauwkeurig zijn bij kleine aantallen toetsvragen, maar } { kleine aantallen toetsvragen leveren zelf ook onnauwkeurigheid op } arr[i] := fBeta(a, b, x); i := i + 1; until (round(arr[i - 1] * gVertU) = 0) or (i = nPunten + 1); { doelmatigheid: bij bereiken extreme waarde verdere berekeningen stoppen } { voor i = nPunten is x = 1, dan geen beta evalueren! } x := a / (a + b); i := round((nPunten + 1) * x); repeat { voor iedere pixel links van het gemiddelde } x := (i + 0.5) / (nPunten + 1); arr[i] := fBeta(a, b, x); i := i - 1; until (round(arr[i + 1] * gVertU) = 0) or (i = 0); { voor i = 0 is arr [ i ] al gelijk 0 gesteld } end; for i := 1 to nPunten do arr[i] := arr[i] + arr[i - 1]; { cumulatieve verdeling } for i := 0 to nPunten do arr[i] := arr[i] / arr[nPunten]; { normaliseer tot oppervlakte 1 } end; { BetaUtiliteit } begin { ExpU } tijdschaal := gHorU / gTijdschaal; { aantal pixels beschikbaar per tijdseenheid } Utiliteit_CumBeta(gAU, gBU, gNItems, arrUf); { haal utiliteitsfunctie arrUf } BepaalVTV(gAPrT, gBPrT, gNItems, VTV); { bepaal bereikte punt op de leercurve gegeven proeftoetsresultaat } { curve p = 1 - .5 ** t --> 1 - p = .5 ** t } { --> ln ( 1 - p ) = t ln .5 --> t = ln ( 1 - p ) / ln .5 } { formule [ 6 ] uit Hoofdstuk 4, leermodel: } { t = ln (( 1 - p ) / m ) / (( 1 - v ) ( 1 - r ))) / ln .5 } { Afleiding uit p = m ( 1 - ( 1 - v ) ( 1 - r ) .5 ** t ): } { noem c = ( 1 - v ) ( 1 - r ) } { p = m ( 1 - c .5 ** t ) } { .5 ** t = ( 1 - p/m ) / c } { t ln .5 = ln (( 1 - p/m ) / c ) } { t = ln (( 1 - p/m ) / c ) / ln .5 } beheersing := gAPrT / (gAPrT + gBPrT); tijdbesteding := (ln(gPlafond - beheersing) - ln(gPlafond) - ln(1 - gVoorkennis) - ln(1 - gRaadkans)) / ln(0.5); { ln ( 1 - beheersing / plafond ) / ((1-voorkennis) * (1-raadkans)) / ln ( 0.5 ); } { bereken per horizontale pixel ( = tijdbesteding ) de leerwinst, en de daarbij horende E ( U )} {plot E ( U ) in de leercurveplot, om te beginnen vanaf de huidige tijdbesteding } { beginnen waar tijdbesteding = i / schaal, dus i = schaal * tijdbesteding: } i := round(tijdschaal * tijdbesteding); for j := 0 to i - 1 do arrEU[j] := -0.001; { blijven onbepaald; neg: teken voor PlotThis } arrEU[i] := VerwachteUtiliteit(gNItems, VTV, arrUf); if tellen then OpenTellerWindow; { open window voor teller voor aantal berekeningen } for i := round(tijdschaal * tijdbesteding) + 1 to gHorU do { voor iedere pixel op de horizontale as de verwachte utiliteit berekenen } begin if tellen then MoveTo(100, 60); if tellen then if i mod 10 = 0 then begin NumToString(i, s1); DrawString(s1); { tellen in TellerWindow } end; { De VTV wordt nu geconditioneerd op de verbeterde beheersing, maar } { niet op een groter aantal proeftoetsvragen: aPr + bPr blijft gelijk, } { aPr / ( aPr + bPr ) wordt gelijk aan nieuwe beheersing } { 'geleerd ( i / schaal )' } constante := gAPrT + gBPrT; at := geleerd(i / tijdschaal) * constante; bt := constante - at; if (gAU = 1) and (gBU = 1) then begin { bij lineaire U geldt E(U) = E(X) } arrEU[i] := at / constante { verw. waarde BeBi (at, bt, n ) gedeeld door n } end else begin BepaalVTV(at, bt, gNItems, VTV); arrEU[i] := VerwachteUtiliteit(gNItems, VTV, arrUf); if round(500 * (1 - arrEU[i])) = 1 then begin for j := i + 1 to gHorU do arrEU[j] := 1.0; goto 123; end; end; end; 123: end; { EvalueerExpU } procedure ExpUvsT (var arrEU: UAT); { plot verwachte utiliteit versus halfwaardetijden } var i: INTEGER; prop, yLo, yHi, tijdschaal: REAL; sA: stringsT; s1, s2, s3: str255; cumulatief: BOOLEAN; begin for i := 1 to 8 do sA[i] := ''; EvalueerExpU(gTijdschaal, arrEU); tijdschaal := gHorU / gTijdschaal; { aantal pixels beschikbaar per tijdseenheid } PenPat(black); PenSize(1, 1); if dialRadio^[PLOT_VERW_UTIL] = 1 then begin sA[1] := 'VERWACHT NUT NA EXTRA LEERTIJD.'; sA[2] := 'horizontaal: tijdseenheden (halfwaardetijden); verticaal: verw. nut'; if gRaadkans <> 0 then begin RealToString(gRaadkans, 2, s1); sA[4] := Concat(sA[4], 'raadkans ', s1, '% '); end; if gVoorkennis <> 0 then begin RealToString(gVoorkennis, 2, s2); sA[4] := Concat(sA[4], ' voorkennis ', s2, ' % '); end; if gPlafond <> 1 then begin RealToString(gPlafond, 2, s3); sA[4] := Concat(sA[4], ' plafond ', s3, '% '); end; sA[5] := 'Proeftoets '; if gAPrT / gBPrT > 0.02 then begin { als aPr heel klein, dan is dat kennelijk zo} { gekozen om een zo volledig mogelijke curven te kunnen plotten ;} { de mededeling over proeftoetsresultaat heeft dan geen betekenis } if gAPrT - Trunc(gAPrT) = 0 then NumToString(round(gAPrT), s1) else RealToString(gAPrT, 1, s1); sA[5] := Concat(sA[5], s1, ' goed uit '); NumToString(gNItems, s2); end; NumToString(round(gAPrT + gBPrT), s2); NumToString(gNItems, s3); sA[5] := Concat(sA[5], s2, ' vragen. Toets ', s3, ' vragen.'); if gAU + gBU > 100000 then begin prop := gAU / (gAU + gBU); NumToString(round(100 * prop), s3); sA[6] := Concat('nut drempel ', s3, ' %.'); end else begin if gAU - Trunc(gAU) = 0 then NumToString(round(gAU), s1) else RealToString(gAU, 1, s1); if gBU - Trunc(gAU) = 0 then NumToString(round(gBU), s2) else RealToString(gBU, 1, s2); sA[6] := Concat('nutsfunctie cum. beta (', s1, '; ', s2, ').'); if (gAU = 1) and (gBU = 1) then sA[6] := Concat(sA[6], ' (lineair)'); end; if gUOverToetsscores then s1 := '' else s1 := ' (over domeinscores)'; sA[6] := Concat(sA[6], s1); yLo := 0; yHi := 1; gHistogram := FALSE; cumulatief := FALSE; PlotThis(tijdschaal, yLo, yHi, cumulatief, arrEU, sA); { nu niet 'overToetsscores = TRUE' doorgeven, maar een FALSE boodschap } { omdat er ondanks utiliteiten over toetsscores nu over pixels wordt geplot } { het laatste is niet fraai, ik moet er iets beters op bedenken, bijv. } { Ut_Over_Toets } end; if tellen then SluitTellerWindow; end; procedure ExpUMinusT (var arrEU: UAT); { Berekent en plot de verwachte utiliteit ( over domein- of toetsscores ) } { min de op vergelijkbare schaal gewaardeerde kosten van de tijdbesteding; } { omdat deze laatste persoonlijk zijn, worden voor 10 verschillende waarden } { van 1t = 0,05 tot 0,5 de curven geplot. } var sA: stringsT; s1, s2, s3: str255; i, j, k, teller: INTEGER; beheersing, tijdbesteding, prop, c: REAL; tijdschaal: REAL; { schaalfactor voor figuren met leertijd: } kosten: REAL; { de persoonlijke inschaling van tijdbestding op de } { utiliteitsschaal voor resultaten. } arrEU_T: UAT; { per pixel te berekenen verschil tussen E ( U ) en gewaarde T } nCurven: INTEGER; { aantal verschillende waarden van T waarvoor geplot wordt } { nodig voor PlotThis: } nPunten: INTEGER; {ofwel aantal pixels, ofwel aantal toetsvragen + 1 } yLo, yHi: real; {standaard 0 (= links); laagste & hoogste verticale schaalwaarde } cumulatief: BOOLEAN; { Moet PlotThis cumuleren voordat de verdeling wordt geplot? } begin nCurven := 5; { haal eerst array met de verwachte utiliteit, in enkele stappen: } EvalueerExpU(gTijdschaal, arrEU); arrEU_T := arrEU; { alleen om te initialiseren } tijdschaal := gHorU / gTijdschaal; { aantal pixels beschikbaar per tijdseenheid } yLo := -1; yHi := 1; for i := 1 to 8 do sA[i] := ''; for j := 0 to nCurven do begin { plot meerdere curven } kosten := (j / 10) / tijdschaal; { Stapsgewijze ophogen met telkens 1/20e als nCurven = 10 } { Door tijdschaal delen, zodat per pixel kan worden gerekend: } { als 40 pixels per t, en t = .1, dan per pixel i/40e t kosten = .0025 } for i := 0 to gHorU do begin { voor iedere pixel extra tijdbesteding de verwachte utiliteit berekenen } if arrEU[i] <= 1 then begin { onbepaalde waarden niet afdrukken } c := arrEU[i] - kosten * i; { E(U) min kosten van i 'pixeltijdseenheden' } if c > 1 then c := 1; if (arrEU[i] < 0) or (c < -1) then c := -1.001; end else c := 1.0001; { als het maar een waarde groter dan yHi is } arrEU_T[i] := c; end; RealToString(j / (2 * nCurven), 2, s1); RealToString(j / (2 * nCurven), 2, s2); RealToString(j / (2 * nCurven), 2, s3); if j = nCurven then begin (*sA[1] := Concat('E(U) - t met resp. 1t = ', s1, ', ', s2, ', ', s3, ' ... ', '0.50.');*) sA[1] := 'VERWACHT NUT INCLUSIEF NUT VAN TIJD'; sA[2] := 'kosten resp. 0 (bovenste curve); 0,1; 0,2; ... ; 0,5 per tijdseenheid'; sA[3] := 'horizontaal: tijdseenheden (halfwaardetijden); verticaal: verw. nut'; if gRaadkans <> 0 then begin RealToString(gRaadkans, 2, s1); sA[4] := Concat(sA[4], 'raadkans ', s1, '% '); end; if gVoorkennis <> 0 then begin RealToString(gVoorkennis, 2, s2); sA[4] := Concat(sA[4], ' voorkennis ', s2, ' % '); end; if gPlafond <> 1 then begin RealToString(gPlafond, 2, s3); sA[4] := Concat(sA[4], ' plafond ', s3, '% '); end; sA[5] := 'Proeftoets '; if gAPrT / gBPrT > 0.02 then begin { als aPr heel klein, dan is dat kennelijk zo} { gekozen om een zo volledig mogelijke curven te kunnen plotten ;} { de mededeling over proeftoetsresultaat heeft dan geen betekenis } if gAPrT - Trunc(gAPrT) = 0 then NumToString(round(gAPrT), s1) else RealToString(gAPrT, 1, s1); sA[5] := Concat(sA[5], s1, ' goed uit '); NumToString(gNItems, s2); end; NumToString(round(gAPrT + gBPrT), s2); NumToString(gNItems, s3); sA[5] := Concat(sA[5], s2, ' vragen. Toets ', s3, ' vragen.'); if gAU + gBU > 100000 then begin prop := gAU / (gAU + gBU); NumToString(round(100 * prop), s3); sA[6] := Concat('nut drempel ', s3, ' %.'); end else begin if gAU - Trunc(gAU) = 0 then NumToString(round(gAU), s1) else RealToString(gAU, 1, s1); if gBU - Trunc(gAU) = 0 then NumToString(round(gBU), s2) else RealToString(gBU, 1, s2); sA[6] := Concat('nutsfunctie cum. beta (', s1, '; ', s2, ').'); if (gAU = 1) and (gBU = 1) then sA[6] := Concat(sA[6], ' (lineair)'); end; if gUOverToetsscores then s1 := '(Toetsscore-)' else s1 := '(Domeinscore-)'; sA[6] := Concat(s1, sA[6]); end; gHistogram := FALSE; cumulatief := FALSE; PlotThis(tijdschaal, yLo, yHi, cumulatief, arrEU_T, sA); { de FALSE boodschap laat PlotThis per pixel een curve printen; anders} { zou een wat dikker getekende curve als histogram over pixels worden geplot } end; { for j:= 0 to nCurven do } end; { Plot_Verschil_Utiliteit_en_Tijd } procedure initialiseren; { parameterwaarden en dergelijke halen } var s: Str255; begin gUOverToetsscores := true; { utiliteitsfunctie over toetsscores f over domeinscores ?} (* Is het interessant om dit echt te implementeren? *) s := dialStr^[HOOGTE_PLOT]; StringToNum(s, gVertU); s := dialStr^[BREEDTE_PLOT]; StringToNum(s, gHorU); { Hoogte en breedte van de grafiek in pixels } s := dialStr^[TIJD_SCHAAL_LENGTE]; StringToReal(s, 2, gTijdschaal); { schaalfactor voor figuren met leertijd: } { bijv. 4.5 laat schaal lopen van 0 tot 4.5 } s := dialStr^[N_TOETS]; StringToNum(s, gNItems); { Aantal items waaruit de te voorspellen toets bestaat } s := dialStr^[A_UTILITEIT]; StringToReal(s, 4, gAu); { parameters voor utiliteitsfunctie als cumulatieve beta } s := dialStr^[B_UTILITEIT]; StringToReal(s, 4, gBu); { parameters voor utiliteitsfunctie als cumulatieve beta } s := dialStr^[SCORE_PRTOETS]; StringToREAL(s, 2, gAPrT); { proeftoetsresultaat veronderstellenderwijs, } s := dialStr^[N_PRTOETS]; StringToNum(s, gNPrItems); {aantal items in de proeftoets } gBPrT := gNPrItems - gAPrT; { andere 'parameter' voor proeftoetsresultaat } s := dialStr^[RAAD_KANS]; StringToReal(s, 2, gRaadkans); { als percentage opgegeven, proportie van maken } gRaadkans := gRaadkans / 100; if gVoorkennis > 1 then gVoorkennis := 0; s := dialStr^[VOOR_KENNIS]; StringToReal(s, 2, gVoorkennis); { als percentage opgegeven, proportie van maken } gVoorkennis := gVoorkennis / 100; if gVoorkennis > 1 then gVoorkennis := 0; s := dialStr^[KENNIS_PLAFOND]; StringToReal(s, 2, gPlafond); { als percentage opgegeven, proportie van maken } gPlafond := gPlafond / 100; if gPlafond > 1 then gPlafond := 0; end; {------------------------- DitProgramma ---------------------------} begin initialiseren; { parameterwaarden en dergelijke halen } gUOverToetsscores := TRUE; if dialRadio^[PLOT_VERW_UTIL] = 1 then ExpUvsT(gUA); if dialRadio^[PLOT_UTIL_TIJD] = 1 then ExpUMinusT(gUA); { verander de parameters waar het om gaat } { Alleen het aantal goed op de proeftoets veranderen levert identieke plots op, } { behalve het aanvangspunt.} { Bij groter aantal toetsvragen worden de optima geprononceerder, tenminste wanneer} { de utiliteitsfunctie niet lineair is } if dialRadio^[PLOT_UTIL_CURVE] = 1 then Cumbeta(gAU, gBU, gNItems, gUA); if dialRadio^[PLOT_LEER_CURVE] = 1 then Leercurve; if dialRadio^[PLOT_VTV] = 1 then Betabinomiaal; if dialRadio^[PLOT_BINO] = 1 then Binomiaal; if dialRadio^[PLOT_BETA] = 1 then Beta; (* PlotLogistic ( gVertU, gHorU, hellingU, locatieU ); na eind van het programma bewaard *) end; { DitProgramma } end. { oktober 1991 } unit Wlbr; { Interface-, numerieke- en andere standaardprocedures voor simulatie-} { en andere programma's voor het toetsmodel-project } interface uses WlbrMenus; {als errormessage Can’t find THPrint, dan in Compiler Options de USES EXTENSIONS option aanzetten} const EDGE_THRESHOLD = 30; WINDOW_HOME_LEFT = 5; WINDOW_HOME_TOP = 45; NEW_WINDOW_OFFSET = 20; SCROLL_BAR_PIXELS = 15; NO_GO_AWAY = FALSE; { or TRUE; nodig bij Toolbox function NewWindow } NIL_REF_CON = 0; { nodig bij Toolbox function NewWindow } ACTIVATING = 1; { nodig voor activateEvts } NORMAL_UPDATES = TRUE; { nodig voor inGrow updateEvts } MIN_SLEEP = 20; SLEEP = 60; { aantal clockticks the application is willing not to perform any } { background processing while waiting for an event. Apple recommends 60 } LEAVE_WHERE_IT_IS = FALSE; WNE_TRAP_NUM = $60; { Voor WaitNextEvent / GetNextEvent routine nodig } UNIMPL_TRAP_NUM = $9F; { idem, zie bv. Mark & Reed p. 116 e.v., 135 e.v. } procedure HandleMouseDown; procedure DrawMyPicture (drawingWindow: WindowPtr; thePicture: picHandle); procedure HaalPICT (var thePicture: picHandle); procedure HandleEvent; procedure DrawAt (x, y: INTEGER; s: Str255); procedure NewFont (fontNumber, pointSize: INTEGER); procedure DrawInteger (int: LONGINT); procedure OpenNuHetWindow (var windowOpen: BOOLEAN); procedure StringToReal (s: str255; dec: INTEGER; var r: REAL); procedure RealToString (r: REAL; dec: INTEGER; var s: str255); procedure DrawReal (r: REAL; dec: INTEGER); procedure WindowInit; implementation procedure HandleMouseDown; { Mark & Reed p. 143-146 } var whichWindow: WindowPtr; thePart, dummy: INTEGER; menuChoice, windSize: LONGINT; oldPort: GrafPtr; begin thePart := FindWindow(gTheEvent.where, whichWindow); { which window was the mouse clicked in? FindWindow returns in the } { parameter whichWindow a WindowPtr to the window containing the point , } { in addition, the integer type thePart describes the part of the window in } { which the point was located } case thePart of { list of predifined part codes: Inside Macintosh I: 287 } inSysWindow: { in a system window, a desk accessory } SystemClick(gTheEvent, whichWindow); { pass the event and the Ptr to the system to handle the event } inMenuBar: begin AdjustMenus; menuChoice := MenuSelect(gTheEvent.where); HandleMenuChoice(menuChoice); end; inDrag: DragWindow(whichWindow, gTheEvent.where, screenBits.bounds); { drag the window anywhere on the screen } inContent: if whichWindow <> FrontWindow then SelectWindow(whichWindow); { activates the clicked-in window } inGrow: begin windSize := GrowWindow(whichWindow, gTheEvent.where, gSizeRect); { the number of pixels the window will grow or shrink in either direction } if windSize <> 0 then begin GetPort(oldPort); SetPort(whichWindow); EraseRect(whichWindow^.portRect); SizeWindow(whichWindow, LoWord(windSize), HiWord(windSize), NORMAL_UPDATES); { the LONGINT windSize dissected in LoWord and HiWord pixel numbers } { NORMAL_UPDATE tells the window manager to update if window has changed } InvalRect(whichWindow^.portRect); { add the entire contents of the window to the window's updateRgn } { alternatief voor het hele window updaten is de Region-update } SetPort(oldPort); end; end; inGoAway: gDone := TRUE; { we're done, quit the program } { netjes is anders: t.z.t. alleen window sluiten, niet de apllicatie } inZoomIn, inZoomOut: if TrackBox(whichWindow, gTheEvent.where, thePart) then begin GetPort(oldPort); SetPort(whichWindow); EraseRect(whichWindow^.portRect); ZoomWindow(whichWindow, thePart, LEAVE_WHERE_IT_IS); InvalRect(whichWindow^.portRect); SetPort(oldPort); end; otherwise begin { al was het alleen maar om window clicks tijdens het ontwikkelen op te vangen } end; end; end; procedure DrawMyPicture (drawingWindow: WindowPtr; thePicture: picHandle); { Mark & Reed p. 146 } { Hoe laat je een bestaand picture tekenen in een gegeven window? } { draws the picture handled by thePicture in the window pointed to by drawingWindow, } { clipping the drawing so that the scroll bar and grow areas aren't overwritten } var drawingClipRect, myRect: Rect; oldPort: GrafPtr; { to be saved pointer to the current GrafPort } tempRgn: RgnHandle; { memory for a region } lengte, offset: longint; myPicPtr: PicPtr; { ik wil verdorie de plot in het clipboard kunnen zetten } procedure CenterPict (thePicture: PicHandle; var myRect: Rect); { constructs a new Rect the size of thePicture, centering it in the original myRect } var wR, pictR: Rect; begin wR := myRect; if thePicture <> nil then pictR := thePicture^^.picFrame; myRect.top := (wR.bottom - wR.top - (pictR.bottom - pictR.top)) div 2 + wR.top; myRect.bottom := myRect.top + (pictR.bottom - pictR.top); myRect.left := (wR.right - wR.left - (pictR.right - pictR.left)) div 2 + wR.left; myRect.right := myRect.left + (pictR.right - pictR.left); end; begin GetPort(oldPort); { save a pointer to the current GrafPort } SetPort(drawingWindow); { make drawingWindow the current GrafPort } tempRgn := NewRgn; { allocate memory for a region to save a copy of the current clipregion into tempRgn } GetClip(tempRgn); { copy the current clip region into tempRgn } { GetClip resizes the region to accommodate the current clip region. IM I: 141, 166 } EraseRect(drawingWindow^.portRect); { erase the whole window } drawGrowIcon(drawingWindow); { redraw the GrowIcon } drawingClipRect := drawingWindow^.portRect; { set up clipping Rect } drawingClipRect.right := drawingClipRect.right - SCROLL_BAR_PIXELS; { exclude right scroll bar area } myRect := drawingWindow^.portRect; { sets up a Rect the size of pictureWindow } { to fit the picture in, to pass as parameter to procedures } CenterPict(thePicture, myRect); ClipRect(drawingClipRect); { set the clipping region to the now defined rectangle } DrawPicture(thePicture, myRect); { Toolbox, zie Chernicoff I: 249 } { thePicture: PicHandle is het te tekenen picture, myRect is de rechthoek waarin } { en er wordt natuurlijk in de current GrafPort getekend. } { Het picture wordt geschaald naar de maten van myRect, althans volgens Chernicoff, } { maar in werkelijkheid wordt van een te omvangrijk picture het nodige weggesneden. } { Zie voor schaalmethoden Chernicoff I: 169 } { De figuur nu copiëren naar de desk scrap, om zo in een Word file te kunnen zetten} { Zie Mark & Reed p. 306-308 } myPicPtr := gMyPicture^; { gMyPicture is een handle, gMyPicture^ een masterpointer } (*lengte := ZeroScrap;*) {altijd eerst deze functie aanroepen, creeert de scrap } (*lengte := PutScrap(gMyPicture^^.picSize, 'PICT', myPicPtr);*) SetClip(tempRgn); { reset the ClipRect to the setting saved in tempRgn } DisposeRgn(tempRgn); { release the memory allocated to tempRgn } SetPort(oldPort); { set the current GrafPort back to the original setting } end; procedure HaalPICT (var thePicture: picHandle); begin thePicture := GetPicture(PICT_ID); { geef de handle de waarde die GetPicture oplevert: } { loads thePicture with a call to GetPicture, die zoekt de resource PICT } { De function GetPicture allocates the memory for the picture itself, as well as for } { the pointer to the picture } { voor algemeen gebruik van de procedure thePicture procedure-parameter maken } if thePicture = nil then FoutBoodschap(WAAR_IS_HET_WINDOW); end; procedure HandleEvent; { retrieves an event from the eventqueue and processes the event } var theChar: CHAR; oldPort: GrafPtr; gotOne: BOOLEAN; begin if gWNEImplemented then { wat alleen voor oude Macs niet zo is } gotOne := WaitNextEvent(everyEvent, gTheEvent, SLEEP, nil) { 'everyEvent' op aanraden van Apple: signaleert alle mogelijke events } { gTheEvent is van type EventRecord = RECORD what: INTEGER; message: LONGINT; } { where: POINT; modifiers: INTEGER end; Mark & Reed p. 117 } else begin SystemTask; gotOne := GetNextEvent(everyEvent, gTheEvent); end; if gotOne then case gTheEvent.what of { gaat alle mogelijke events na die '.what' kan bevatten } nullEvent: ; mouseDown: HandleMouseDown; mouseUp: ; keyUp: ; keyDown, autoKey: begin theChar := CHR(BitAnd(gTheEvent.message, charCodeMask)); if (BitAnd(gTheEvent.modifiers, cmdKey) <> 0) then begin AdjustMenus; HandleMenuChoice(MenuKey(theChar)); end; end; updateEvt: if not IsDAWindow(WindowPtr(gTheEvent.message)) then begin {updateEvts voor windows: bij meerdere windows uitzoeken welk window; } { gTheEvent.message bevat de pointer naar het window dat volgens de } { window manager een update moet hebben } GetPort(oldPort); SetPort(WindowPtr(gTheEvent.message)); BeginUpdate(WindowPtr(gTheEvent.message)); { tells the window manager that something will be done about it } { Zie Mark & Reed p 138; Inside Macintosh I ch. 9 over updaten } DrawMyPicture(WindowPtr(gTheEvent.message), gMyPicture); { in this case by redrawing thePicture in gPictWindow } EndUpdate(WindowPtr(gTheEvent.message)); { let the window manager know the condition hasbeen taken care of, else the } { will keep sending messages for updateEvts } SetPort(oldPort); end; diskEvt: ; activateEvt: if (WindowPtr(gTheEvent.message) = gWPtr) then begin { activateEvts voor windows: bij meerdere windows uitzoeken welk window; } { gTheEvent.message bevat de pointer naar het window dat volgens de } { window manager een activateEvt heeft geboekt } DrawGrowIcon(WindowPtr(gTheEvent.message)); { redraw the GrowBox and the empty scroll bar areas } if (BitAnd(gTheEvent.modifiers, activeFlag) = ACTIVATING) then ; { check in the modifiers field: is the event an activate or a deactivate event } end; networkEvt, driverEvt: ; app1Evt, app2Evt, app3Evt: ; { gebruik van deze mogelijkheden wordt door Apple ontraden } app4Evt: ; { to get resume and suspend events there must be a SIZE rsrc in the apllication } end; end; procedure DrawAt (x, y: INTEGER; s: Str255); { Draw string at this coordinate, similar to an x, y location } { on a conventional computer terminal. } { Het zijn locale coordinaten! } const Xoffset = 5; { Blank pixels in left border } Yoffset = 12; { Blank pixels in top border } begin Moveto(Xoffset + x * gTextwidth, Yoffset + y * gTextHeight); DrawString(s); end; { DrawAt } procedure NewFont (fontNumber, pointSize: INTEGER); { Select new font and adjust variables for DrawAt } begin TextFont(fontNumber); { Use new font } TextSize(pointSize); { Use new size } gTextHeight := pointSize + 2; { Calc new character height } gTextWidth := CharWidth('M'); { Calc width of widest character } end; { NewFont } procedure DrawInteger (int: LONGINT); var s: Str255; begin NumToString(int, s); { convert number to string } DrawString(s); { display it } end; procedure OpenNuHetWindow (var windowOpen: BOOLEAN); { open window and attach quickDraw picture, } { procedure ontleend aan Swan, 1987, p. 212 } { t.z.t. het openen van het window scheiden van het attachen van het picture } { De procedure moet worden opgevolgd met SluitNuHet Picture } { Ik moet dit tzt natuurlijk op kunnen vangen in de normale procedure voor windows en pictures } var myRect: Rect; { het window waarin wordt geplot } { gMyPicture is het picture handle dat de window manager gebruikt om teken- } { opdrachten te bewaren in relocatable memory op de hoop } begin ShowWindow(gPlotWindow); if not windowOpen then begin gWPtr := GetNewWindow(WINDOW_Id, @gWrec, POINTER(-1)); { open de resource template en het window } if gWPtr = nil then FoutBoodschap(WAAR_IS_HET_WINDOW); { Een alternatief is: gWPtr := } { NewWindow(nil, screenBits.bound,'Titel',WindowPtr(-1), } { NO_GO_AWAY,NIL_REF_CON); The field screenBits.bounds is declared } { as a Rect and contains a rectangle that encloses the main Mac screen. } windowOpen := gWPtr <> nil; if windowOpen then begin SetPort(gWPtr); { set the current grafport to this window } { zie Chernikoff I:122. GetPort geeft de pointer to the currentPort, } { SetPort bepaalt de curentPort (mits deze al bestaat, met OpenPort bv.) } ClipRect(gWPtr^.portRect); { Not optional. Reduce the clipping region } myRect := gWPtr^.portRect; { Maak rechthoek gelijk aan het window } gMyPicture := OpenPicture(myRect); { Create a picture } { OpenPicture starts a new picture on the heap and passes back a handle } { to the associated memory block } { QuickDraw commando's gaan nu niet naar het scherm, maar naar de hoop } NewFont(helvetica, 14); { voor te printen tekst } { tzt menu-gestuurd, maar eenvoudig weghalen levert nog niet als resultaat} {dat de menu-keuze voor font en style wordt gevolgd ???? } end end end; procedure StringToReal (s: str255; dec: INTEGER; var r: REAL); var s1, s2, s0: str255; i, r1, r2, d, p: LONGINT; begin p := pos(',', s); { op welke positie staat de comma? } if p = 0 then { kijk of decimale punt is gebruikt } p := pos('.', s); { op welke positie staat de punt? } if p = 0 then begin { geheel getal opgegeven } StringToNum(s, r1); r := r1; end else begin s1 := copy(s, 1, p - 1); { gehele gedeelte } StringToNum(s1, r1); s2 := copy(s, p + 1, length(s)); { decimale gedeelte } StringToNum(s2, r2); if length(s2) < dec then dec := length(s2) else s2 := copy(s2, 1, dec); { de constraint van aantal te gebruiken decimalen is niet in alle toepassingen } { zinvol of nodig, maar in andere wel, dus handhaven } r := r1 + r2 / exp(ln(10) * (dec)); { r2 delen door 10 tot de macht dec } end; end; procedure RealToString (r: REAL; dec: INTEGER; var s: str255); var s1, s2, s0: str255; i, r1, r2, d: longint; begin r1 := trunc(r); r2 := abs(round(exp(dec * ln(10)) * (r - r1))); NumToString(r1, s1); { convert number to string } NumToString(r2, s2); d := dec - Length(s2); { leading zeros van het decimale deel tellen } if d <> 0 then for i := 1 to d do s2 := Concat('0', s2); if (r < 0) and (r > -1) then begin s1 := Concat('-', s1); end; s := Concat(s1, '.', s2); if dec = 0 then NumToString(round(r), s); end; procedure DrawReal (r: REAL; dec: INTEGER); var s: str255; begin RealToString(r, dec, s); { convert number to string } DrawString(s); { and display it } end; procedure WindowInit; { Loads WIND ID-number from the rsrc-file, stores a pointer to it in gDrawWindow } begin gPlotWindow := GetNewWindow(STANDAARD_ID, nil, WindowPtr(-1)); gTellerWindow := GetNewWindow(STANDAARD_ID + 1, nil, WindowPtr(-1)); end; end. { of unit } { oktober 1991 } unit WlbrMenus; { Interface-menu-procedures voor simulatie-} { en andere programma's voor het toetsmodel-project } interface uses PrintTraps; const STANDAARD_ID = 128; { de volgende 3 const corresponderen met ID's van 'STR ' resources } APPLE_MENU_ID = STANDAARD_ID; { MENU-ID } FILE_MENU_ID = STANDAARD_ID + 1; { MENU-ID } EDIT_MENU_ID = STANDAARD_ID + 2; { MENU_ID } ERROR_ALERT_ID = STANDAARD_ID + 1; { ALRT-ID } ABOUT_ITEM = 1; { apple-menu } ABOUT_ALERT = STANDAARD_ID; KIES_PARAM = 1; {Cmd K} EXTRA_ITEM = 2; {Cmd E} START_ITEM = 3; {Cmd G} CLOSE_ITEM = 4; {---------------------} SAVE_AS_ITEM = 6; PRINT_ITEM = 7; {Cmd P} {---------------------} QUIT_ITEM = 9; {Cmd Q} { items in the interactive dialog: } OK_BUTTON = 1; ANNULEER_BUTTON = 2; N_PRTOETS = 3; { aantal vragen waaruit de proeftoets bestond } SCORE_PRTOETS = 4; { op proeftoets behaalde score } A_PRTOETS = 5; { BetaBinoVerdeling over proeftoetsscores } B_PRTOETS = 6; N_TOETS = 7; { aantal vragen dat de te voorspellen toets bevat } A_UTILITEIT = 8; { cumulatieve betaverdeling voor de utiliteitsfunctie } B_UTILITEIT = 9; CESUUR_TOETS = 10; { minimale score die nog 'succes' oplevert } RAAD_KANS = 11; { raadkans; default is 5 %, er is altijd wel raadkans } KENNIS_PLAFOND = 12; { maximale beheersing; default 95 % als realistisch } VOOR_KENNIS = 13; { voorkennis waarmee het leren is gestart, default 5 % } BREEDTE_PLOT = 14; { parameter voor dimensie van de plot } HOOGTE_PLOT = 15; { parameter voor dimensie van de plot } TIJD_SCHAAL_LENGTE = 16; { aantal tijdseenheden waarover wordt geplot } MAX_STR_FIELDS = 16; { the number of stringfields above, plus OK & ANNULEER } RADIO_SCORE = 17; { uitgangspunt is score op proefoets } RADIO_VERDELING = 18; { uitgangspunt is verdeling van proeftoetsscores } RADIO_OGIEF = 19; { een ogief als utiliteitsfunctie } RADIO_DREMPEL = 20; { drempel-utiliteit } PLOT_LEER_CURVE = 21; { leercurve plotten? } PLOT_UTIL_CURVE = 22; { utiliteitscurve plotten? Bij keuze voor ogief nuttig } PLOT_VERW_UTIL = 23; { dit is de normale output van het programma } PLOT_UTIL_TIJD = 24; { dit is een alternatieve manier om de output te plotten } RASTER_OF_NIET = 25; { plotten in een gerasterde figuur? } SCHAAL_WAARDEN = 26; { schaalwaarden aangeven? } PLOT_VTV = 40; {plot bebi voorspellende toetsscoreverdeling} PLOT_BINO = 41; { plot binomiaal alsof proeftoetsresultaat ware beheersing is } SIMULEER = 42; { De te plotten verdeling baseren op simulatie? } PLOT_BETA = 43; { plot betaverdeling voor ware beheersing } {BEREKENEN_OF_SIMULEREN Een toekomstige uitbreiding? } {AANTAL_TOETSTREKKINGEN idem } ON = 1; { for radio- and check buttons in dialog } OFF = 0; KEY_RETURN = 36; {return key code, keyboard scan code, not the same as ASCII values! } KEY_ENTER = 76; { enter key code } UNDO_ITEM = 1; CUT_ITEM = 3; COPY_ITEM = 4; PASTE_ITEM = 5; CLEAR_ITEM = 6; FONT_MENU_ID = 100; STYLE_MENU_ID = 101; NOT_A_NORMAL_MENU = -1; { niet direct op de menubalk te plaatsen } ADD_CHECK_MARK = TRUE; { voor checks in font en style menu's } REMOVE_CHECK_MARK = FALSE; PLAIN = []; PLAIN_ITEM = 1; BOLD_ITEM = 2; ITALIC_ITEM = 3; UNDERLINE_ITEM = 4; OUTLINE_ITEM = 5; SHADOW_ITEM = 6; WINDOW_ID = STANDAARD_ID; { WIND-ID } PICT_ID = STANDAARD_ID; { PICT-ID } { Foutmeldingen: } WAAR_IS_HET_WINDOW = 1; { correspondeert met "STR ' ID STANDAARD_ID + 1 } WAAR_IS_HET_MENU = 2; { correspondeert met "STR ' ID STANDAARD_ID + 2 } WAAR_IS_HET_PICT = 3; { correspondeert met "STR ' ID STANDAARD_ID + 3 } WAAR_IS_HET_MBAR = 4; { correspondeert met "STR ' ID STANDAARD_ID + 4 } HEADER_SIZE = 512; { used for removing the header at the top of PICT files } type ParameterStringArray = array[3..MAX_STR_FIELDS] of Str255; (***** Let op, ik denk dat het string-array overbodig is, ik heb al het stringHandle array! ******) ParameterOnOffArray = array[MAX_STR_FIELDS..100] of INTEGER; ParameterStringArrayPtr = ^ParameterStringArray; ParameterOnOffArrayPtr = ^ParameterOnOffArray; (* Het ParameterOnOffArray kan makkelijk worden uitgebouwd tot een array of REAL *) (* waarin de numerieke parametergegevens worden opgeslagen, voor eventueel meerdere *) (* series, en waarom zou ik niet de mogelijkheid tot 10 series geven? *) var { Hier ook alle globale variabelen die nodig zijn } gDone: BOOLEAN; { while gDone=FALSE do HandleEvent } gIfNew: BOOLEAN; { als TRUE then nieuwe simulatie runnen } gWNEImplemented: BOOLEAN; { TRUE if WaitNextEvent is implemented in de current version of the system } gTheEvent: EventRecord; { voor het signaleren en afhandelen van events } gMyPicture: PicHandle; { het picture waarnaar QuickDraw wegschrijft } gPlotWindow, gTellerWindow: windowPtr; { beide benodigde windows } gWRec: windowrecord; { Program's window data record; InsMac I: 276 } gWPtr: windowptr; { Pointer to above wRec } gWindowOpen: BOOLEAN; { TRUE only if window is open } gSizeRect: Rect; { controls the size of the window } gTextWidth: INTEGER; { Maximum width of a character } gTextHeight: INTEGER; { Height of a character } gCurrentStyle: Style; { voor style choice } gLastFont: INTEGER; gSD: DialogPtr; { gSettingsDialog, pointer naar setings in de dialoog } gPrintRecordH: THPrint; { handle to the print record you'll create } gReply: SFReply; { will hold the data returned by the cal to SFGetFile } { default string Handles to 'STR '.rsrc dialog items: } (* gDefNPrToetsH, gDefScorePrToetsH, gDefAPrToetsH, gDefBPrToetsH: StringHandle;*) (* gDefNToetsH, gDefAUtiliteitH, gDefBUtiliteitH, gDefCesuurToetsH: StringHandle;*) (* gDefHoogtePlotStrH, gDefBreedtePlotStrH, gDefTijdSchaalLengteH: StringHandle;*) (* gDefRaadKansStrH, gDefKennisPlafondStrH, gDefVoorKennisStrH: StringHandle;*) { The following array holds handles to each edit text item in the dialog entry form. } { Copying the handles into a global array helps cut down the number of calls to the } { Dialog manager. De first string has number 3 (1 en 2: OK en Cancel) Swan, p. 303 } ItemHandles: array[3..MAX_STR_FIELDS] of Handle; DialStr: ParameterStringArrayPtr; DialRadio: ParameterOnOffArrayPtr; gLinks, gOnder, gBoven, yScale: INTEGER; { Afbeeldingsparameters voor plot } i, j: INTEGER; {tellers} { ik denk dat procedures die niet van buiten de unit worden aangeroepen, hier ook } { niet hoeven te worden gedeclareerd } function IsDAWindow (whichWindow: WindowPtr): BOOLEAN; procedure SaveSettings; procedure RestoreSettings; procedure HandleDialog; procedure CheckStyles; { update the check marks on the Style menu } procedure HandleStyleChoice (theItem: INTEGER); { IM I: 171 } procedure HandleEditChoice (theItem: INTEger); procedure HandleFontChoice (theItem: INTEGER); procedure HandleFileChoice (theItem: INTEGER); procedure HandleAppleChoice (theItem: INTEGER); procedure AdjustMenus; procedure HandleMenuChoice (menuChoice: LONGINT); procedure FoutBoodschap (stringNum: INTEGER); procedure MenuBarInit; procedure DialogInit (var d: DialogPtr); procedure ArrayInit; { array met strings voor de parameterwaarden } implementation procedure PrintPlot; { Mark & Reed Hoofdstuk 7 p. 337} { zie Inside Macintosh IV, Ch. 1, for a detailed discussion of the File Manager } { Hier aangepast om de plot in het window te printen } const CANT_OPEN_FILE = STANDAARD_ID + 6; GET_EOF_ERROR = STANDAARD_ID + 7; HEADER_TOO_SMALL = STANDAARD_ID + 8; OUT_OF_MEMORY = STANDAARD_ID + 9; CANT_READ_HEADER = STANDAARD_ID + 10; CANT_READ_PICT = STANDAARD_ID + 11; NIL_STRING = ''; IGNORED_STRING = NIL_STRING; procedure PrintPICT (thePict: picHandle); { Mark & Reed Hoofdstuk 7 p. 337 } var printPort: TPPrPort; printStatus: TPrStatus; begin { PrOpenDoc returns a pointer printPort to the printing grafPort: } printPort := PrOpenDoc(gPrintRecordH, nil, nil); prOpenPage(printPort, nil); { open a new page } DrawPicture(thePict, thePict^^.picFrame); { and draw the picture } PrClosePage(printPort); { then close the page } PrCloseDoc(printPort); { and the printing grafPort } PrPicFile(gPrintRecordH, nil, nil, nil, printStatus); { This prints the file! } HUnlock(Handle(thePict)); { opruimen } end; function DoDialogs: BOOLEAN; { do the Page Setup and Print Job dialogs } (* dat kan worden verbeterd door die dialogen op te roepen via het menu, met *) (* Page Setup en Print Job, je raadt het al *) var keepGoing: BOOLEAN; begin keepGoing := PrStlDialog(gPrintRecordH); if keepGoing then DoDialogs := PrJobDialog(gPrintRecordH) else DoDialogs := FALSE; end; procedure PrintInit; begin gPrintRecordH := THPrint(NewHandle(sizeof(TPrint))); { NewHandle allocates a block of memory to the size of a print record and makes } { gPrintRecordH a handle to that memory } PROpen; { start up the Printing Manager } PrintDeFault(gPrintRecordH); { ensures that any changes made to the Page Setup } { and Print Job dialogs will be implemented when you print } end; begin PrintInit; if DoDialogs then PrintPict(gMyPicture); end; procedure SaveSettings; { fills the savedSettings data structure with the values currently set in the settings dialog } var T, itemNo: INTEGER; { hier slechts een filleritem } R: Rect; { hier slechts een filleritem } H: Handle; begin { eerst voor de string-items: } for itemNo := 3 to MAX_STR_FIELDS do begin { voor alle EditText items } GetDItem(gSD, itemNo, T, H, R); { itemHandle H ophalen } if H = nil then ExitToShell; { a field is missing. Foutmelding maken } itemHandles[itemNo] := H; { itemHandle H opslaan } GetIText(H, dialStr^[itemNo]); end; { en dan voor de check en radio items: } for itemNo := MAX_STR_FIELDS + 1 to 100 do begin { voor alle EditText items } GetDItem(gSD, itemNo, T, H, R); { itemHandle H ophalen } if H <> nil then begin dialRadio^[itemNo] := GetCtlValue(ControlHandle(H)); end; end; end; procedure RestoreSettings; { restore the settings dialog items saved in the savedSettings data structure } var itemNo, T: INTEGER; R: Rect; H: Handle; begin { eerst voor de string-items: } for itemNo := 3 to MAX_STR_FIELDS do begin { voor alle EditText items } GetDItem(gSD, itemNo, T, H, R); { itemHandle H ophalen } if H = nil then ExitToShell; { a field is missing. Foutmelding maken } itemHandles[itemNo] := H; { itemHandle H opslaan } SetIText(H, dialStr^[itemNo]); { InsMac I: 422; Zet tekst in dialoogvenster } { ik neem aan dat de Dialog en Resource Managers zorgen voor verwerking in de } { itemlijst en de kopie van de itemlijst, zie InsMac I: 414 } end; { en dan voor de check en radio items: } for itemNo := MAX_STR_FIELDS + 1 to 100 do begin { voor alle EditText items } GetDItem(gSD, itemNo, T, H, R); { itemHandle H ophalen } if H <> nil then begin SetCtlValue(ControlHandle(H), dialRadio^[itemNo]); end; end; end; procedure HandleDialog; { Ontleend aan Mark & Reed, p. 265. Zie ook Swan, p. 310 } var dialogDone, flag: BOOLEAN; iHit, T, dummy: INTEGER; R: Rect; H: Handle; s: Str255; procedure afmaken; begin if GetCtlValue(ControlHandle(H)) = ON then SetCtlValue(ControlHandle(H), OFF) else SetCtlValue(ControlHandle(H), ON); end; begin { Dan moet ik het probleem oplossen waarom klikken in 3 wel resultaat heeft in 4 en 5, } {en het omgekeerde niet het geval is. Dat heeft te maken met het enabled zijn van de items! } { Het window opent met de tekststrings uit de DITL, niet uit 'STR ', geïnstalleerd } ShowWindow(gSD); { Er moet wel een open dialoog-window te zien zijn } { Bewaar huidige setting, voor geval van 'Cancel' } dialogDone := FALSE; while dialogDone = FALSE do begin { rustig alle items afwerken } ModalDialog(nil, iHit); case iHit of RADIO_SCORE: begin { af wanneer aan, aan wanneer af 17 } GetDItem(gSD, RADIO_SCORE, T, H, R); afmaken end; RADIO_VERDELING: begin { af wanneer aan, aan wanneer af { 18 } GetDItem(gSD, RADIO_VERDELING, T, H, R); afmaken end; RADIO_OGIEF: begin { af wanneer aan, aan wanneer af { 19 } GetDItem(gSD, RADIO_OGIEF, T, H, R); afmaken end; RADIO_DREMPEL: begin { af wanneer aan, aan wanneer af { 20 } GetDItem(gSD, RADIO_DREMPEL, T, H, R); afmaken end; PLOT_LEER_CURVE: begin GetDItem(gSD, PLOT_LEER_CURVE, T, H, R); { 21 } afmaken end; PLOT_UTIL_CURVE: begin { 22 } GetDItem(gSD, PLOT_UTIL_CURVE, T, H, R); afmaken end; PLOT_VTV: begin { 22 } GetDItem(gSD, PLOT_VTV, T, H, R); afmaken end; PLOT_BINO: begin { 22 } GetDItem(gSD, PLOT_BINO, T, H, R); afmaken end; PLOT_VERW_UTIL: begin { 23 } GetDItem(gSD, PLOT_VERW_UTIL, T, H, R); afmaken end; PLOT_UTIL_TIJD: begin { 24 } GetDItem(gSD, PLOT_UTIL_TIJD, T, H, R); afmaken end; PLOT_BETA: begin { 24 } GetDItem(gSD, PLOT_BETA, T, H, R); afmaken end; SIMULEER: begin { 22 } GetDItem(gSD, SIMULEER, T, H, R); afmaken end; RASTER_OF_NIET: begin { 25 } GetDItem(gSD, RASTER_OF_NIET, T, H, R); afmaken end; SCHAAL_WAARDEN: begin { 26 } GetDItem(gSD, SCHAAL_WAARDEN, T, H, R); afmaken end; OK_BUTTON: begin { save de instellingen 1 } HideWindow(gSD); { Close dialoog-window, settings are done } SaveSettings; { Bewaar nieuwe instellingen } dialogDone := TRUE; gIfNew := TRUE; { vlag voor uitvoeren van berekeningen en voor plotten } (* als meerdere series tegelijk worden opgegeven, dan OK en RUN weer loskoppelen! *) end; ANNULEER_BUTTON: begin { 2 } HideWindow(gSD); { Close window first, then restore settings } RestoreSettings; dialogDone := TRUE; end; end; end; end; procedure HandleEditChoice (theItem: INTEger); { HandleEditChoice calls SystemEdit. If the active window belongs to a desk accessory, } { SystemEdit passes the appropriate command to the accessory. Otherwise, it returns } { FALSE, and your application should then handle the edit command. } var dummy: BOOLEAN; begin dummy := SystemEdit(theItem - 1); end; procedure HandleFileChoice (theItem: INTEGER); (* t.z.t. uitbreiden met SAVE mogelijkheid *) (* t.z.t. uitbreiden met de mogelijkheid om bewaarde PICT files te bekijken *) var whichWindow: WindowPtr; nieuw: BOOLEAN; begin case theItem of KIES_PARAM: begin HandleDialog; end; EXTRA_ITEM: begin { de dialoog nu gebruiken om parameters voor extra plot in te lezen } { Maximum stellen aan het aantal extra plots: bijvoorbeeld 10 } { enabled maken wanneer een eerdere set parameters is ingevoerd } HandleDialog; end; START_ITEM: {Create New Window} begin gIfNew := TRUE; { vlag voor uitvoeren van berekeningen en voor plotten } end; PRINT_ITEM: PrintPlot; CLOSE_ITEM: begin whichWindow := FrontWindow; if whichWindow <> nil then DisposeWindow(whichWindow); end; QUIT_ITEM: begin whichWindow := FrontWindow; if whichWindow <> nil then DisposeWindow(whichWindow); gDone := TRUE; end; end; end; procedure HandleAppleChoice (theItem: INTEGER); { handles all the apple-menu selections } var accName: Str255; accNumber, itemNumber, dummy: INTEGER; appleMenu: MenuHandle; begin case theItem of ABOUT_ITEM: dummy := NoteAlert(ABOUT_ALERT, nil); otherwise begin appleMenu := GetMHandle(APPLE_MENU_ID); GetItem(appleMenu, theItem, accName); accNumber := OpenDeskAcc(accName); end; end; end; procedure HandleMenuChoice (menuChoice: LONGINT); var theMenu, theItem, dummy: INTEGER; begin if menuChoice <> 0 then begin theMenu := HiWord(menuChoice); { the first two bytes contain the menu selected, } theItem := LoWord(menuChoice); { the last two the item selected from that menu } case theMenu of APPLE_MENU_ID: HandleAppleChoice(theItem); FILE_MENU_ID: HandleFileChoice(theItem); EDIT_MENU_ID: HandleFileChoice(theItem); (* ******** dit klopt toch niet? ****** *) FONT_MENU_ID: HandleFontChoice(theItem); STYLE_MENU_ID: HandleStyleChoice(theItem); otherwise dummy := 0; end; HiLiteMenu(0); { uninvert the selected menu } end; end; function IsDAWindow (whichWindow: WindowPtr): BOOLEAN; begin if whichWindow = nil then IsDAWindow := FALSE else IsDAWindow := (WindowPeek(whichWindow)^.windowKind < 0); { the field windowKind is positive if the window is an application window, } { negative if it is a desk accessory window } end; procedure AdjustMenus; { enables and disables the items in the Edit menu, depending on whether the current } { window is a desk accessory window or not. T.z.t. veralgemenen dus } { Mark & Reed, p. 300. Works together with procedure IsDAWindow } var aMenu: MenuHandle; begin aMenu := GetMHandle(FILE_MENU_ID); if FrontWindow = nil then DisableItem(aMenu, CLOSE_ITEM) else EnableItem(aMenu, CLOSE_ITEM); aMenu := GetMHandle(EDIT_MENU_ID); if IsDAWindow(FrontWindow) then begin EnableItem(aMenu, UNDO_ITEM); EnableItem(aMenu, CUT_ITEM); EnableItem(aMenu, COPY_ITEM); EnableItem(aMenu, PASTE_ITEM); EnableItem(aMenu, CLEAR_ITEM); end else begin DisableItem(aMenu, UNDO_ITEM); DisableItem(aMenu, CUT_ITEM); DisableItem(aMenu, COPY_ITEM); DisableItem(aMenu, PASTE_ITEM); DisableItem(aMenu, CLEAR_ITEM); end; end; procedure CheckStyles; { update the check marks on the Style menu } var styleMenu: MenuHandle; begin styleMenu := GetMHandle(STYLE_MENU_ID); CheckItem(styleMenu, PLAIN_ITEM, (gCurrentStyle = PLAIN)); CheckItem(styleMenu, BOLD_ITEM, (bold in gCurrentStyle)); CheckItem(styleMenu, ITALIC_ITEM, (italic in gCurrentStyle)); CheckItem(styleMenu, UNDERLINE_ITEM, (underline in gCurrentStyle)); CheckItem(styleMenu, OUTLINE_ITEM, (outline in gCurrentStyle)); CheckItem(styleMenu, SHADOW_ITEM, (shadow in gCurrentStyle)); end; procedure HandleStyleChoice (theItem: INTEGER); { IM I: 171 } begin case theItem of PLAIN_ITEM: gCurrentStyle := []; { als PLAIN ipv [], dan out of range melding ?????? } BOLD_ITEM: if bold in gCurrentStyle then gCurrentStyle := gCurrentStyle - [bold] else gCurrentStyle := gCurrentStyle + [bold]; ITALIC_ITEM: if bold in gCurrentStyle then gCurrentStyle := gCurrentStyle - [italic] else gCurrentStyle := gCurrentStyle + [italic]; UNDERLINE_ITEM: if bold in gCurrentStyle then gCurrentStyle := gCurrentStyle - [underline] else gCurrentStyle := gCurrentStyle + [underline]; OUTLINE_ITEM: if bold in gCurrentStyle then gCurrentStyle := gCurrentStyle - [outline] else gCurrentStyle := gCurrentStyle + [outline]; SHADOW_ITEM: if bold in gCurrentStyle then gCurrentStyle := gCurrentStyle - [shadow] else gCurrentStyle := gCurrentStyle + [shadow]; end; CheckStyles; { update the check marks on the Style menu } TextFace(gCurrentStyle); { implement the styles in gCurrentStyle } end; procedure HandleFontChoice (theItem: INTEGER); { is called when the FONT item in the SPECIAL menu is selected } var fontNumber: INTEGER; fontName: Str255; fontMenu: MenuHandle; begin fontMenu := GetMHandle(FONT_MENU_ID); { get the font's menu handle } CheckItem(fontMenu, gLastFont, REMOVE_CHECK_MARK); { removes check mark } CheckItem(fontMenu, theItem, ADD_CHECK_MARK); { check mark newly selected font } gLastFont := theItem; { set to the selected item number } GetItem(fontMenu, theItem, fontName); { returns the fontName for the menu selection } getFNum(fontName, fontNumber); { provides the fontNumber given the fontName} TextFont(fontNumber); { changes textfont to the new font ID number } end; procedure FoutBoodschap (stringNum: INTEGER); { input is een fout ID; laadtr de 'STR ' resource met dat ID, } { gebruikt StopAlert voor de mededeling op het scherm } const NIL_STRING = ''; var errorStringH: StringHandle; dummy: INTEGER; begin errorStringH := GetString(STANDAARD_ID + stringNum); if errorStringH = nil then { de benodigde 'STR ' resource is niet gevonden } ParamText('Er is een fout, de beschrijving van de fout is helaas zoek', NIL_STRING, NIL_STRING, NIL_STRING) else ParamText(errorStringH^^, NIL_STRING, NIL_STRING, NIL_STRING); { ParamText, zie Macintosh Revealed II: 366, definieert tot 4 strings voor } { static text items, voor de plaatsvervangers ^0, ^1, ^2, en ^3 resp. } { Deze strings worden pas gewijzigd bij een volgende aanroep van ParamText } SysBeep(1); { waarschuwingssignaal 1 seconde } dummy := StopAlert(ERROR_ALERT_ID, nil); { Voor StopAlert, zie Inside Macintosh II: 358 } { NB: Als de resource met ResEdit geopend staat, dan werkt StopAlert niet } ExitToShell; { returns control of the Macintosh to the Finder } end; procedure MenuBarInit; var myMenuBar: Handle; aMenu: MenuHandle; begin myMenuBar := GetNewMBar(STANDAARD_ID); if myMenuBar = nil then FoutBoodschap(WAAR_IS_HET_MBAR); SetMenuBar(myMenuBar); DisPosHandle(myMenuBar); { Mark & Reed p. 183 gebruiken dan DisposHandle(myMenuBar), maar doen dat niet } { in het programma WindowMaker. Ik weet niet of die DisposHandle gewenst is } aMenu := GetMHandle(APPLE_MENU_ID); if aMenu = nil then FoutBoodschap(WAAR_IS_HET_MENU); AddResMenu(aMenu, 'DRVR'); { adds desk accessories to apple menu } aMenu := GetMenu(FONT_MENU_ID); if aMenu = nil then FoutBoodschap(WAAR_IS_HET_MENU); InsertMenu(aMenu, NOT_A_NORMAL_MENU); { voor hierarchisch menu } AddResMenu(aMenu, 'FONT'); { adds the names of all resources of type FONT to the menu } aMenu := GetMenu(STYLE_MENU_ID); if aMenu = nil then FoutBoodschap(WAAR_IS_HET_MENU); InsertMenu(aMenu, NOT_A_NORMAL_MENU); { voor hierarchisch menu } CheckItem(aMenu, PLAIN_ITEM, TRUE); { place a checkmark next to 'plain' } DrawMenuBar; gLastFont := 1; gCurrentStyle := []; HandleFontChoice(gLastFont); { sets the current font to the first font in the font menu } end; procedure ArrayInit; { arrays met strings en ON/OFF voor de parameterwaarden } { Creates the array of strings for parameters as a non-relocatable object on the heap. } { That is OK, becaue it is not a small array, and the the array is always in memory while } { the program runs. It is placed as low on the heap as possible, to avoid fragmenting the } { heap and to let the program use simpler pointer adressing. Swan, p . 312 , 323. } begin dialStr := ParameterStringArrayPtr(NewPtr(SizeOf(ParameterStringArray))); dialRadio := ParameterOnOffArrayPtr(NewPtr(SizeOf(ParameterOnOffArray))); { reserves heap space for the array, by calling NewPtr, requesting the SizeOf the } { DataArray type in bytes and casting the resulting plain pointer to a DataArrayPtr } { type, assigning this value to theData, the pointer to the array } if MemError <> NoErr then begin { checks that this works; if not, there wasn't enough memory available for the array } (*DisplayError(1, 'Not enough memory', 'Set MaxRec to lower value', StopError);*) (* t.z.t. hier een behoorlijke foutmelding van maken *) ExitToShell end; end; procedure DialogInit (var d: DialogPtr); { Voor een voorbeeld, zie Swan, p. 312 } var itemNo, i, T: INTEGER; H: Handle; R: Rect; begin d := GetNewDialog(STANDAARD_ID, nil, WindowPtr(-1)); { resource ID; create template in heap; make frontmost window } (* if d=nil then ExitToShell; Foutmelding installeren *) { settings dialog is loaded from the resource file. Zie bv. Swan p. 275. } { dialog window is set up as invisible; ShowWindow makes it visible } { NB: In het DLOG rscr wordt verwezen naar een DITL ID, zorg ervoor dat die DITL er } { inderdaad is, en het juiste ID heeft! } { Copy handles of each edit text item in the dialog. This makes transferring strings } { to and from the dialog easier and avoids calling GetDItem too often } if d <> nil then begin { De window Manager heeft de teksten uit de DITL gehaald, en in een itemlist gezet, } { maar het is handig de handles voor die teksten in een array beschikbaar te hebben. } { Omdat de teksten in de bij de DLOG behorende DITL zijn gegeven, zijn deze als } { defaultwaarden onmiddellijk in het dialoogvenster ingevuld. } for itemNo := 3 to MAX_STR_FIELDS do begin { voor alle EditText items } GetDItem(d, itemNo, T, H, R); { itemHandle H ophalen } if H = nil then ExitToShell; { a field is missing. t.z.t. foutmelding maken. Fout kan onstaan } { wanneer bij de RUN OPTIONS de WlbrMenus-RSRC-file niet is aangekoppeld } itemHandles[itemNo] := H; { itemHandle H opslaan } end; for itemNo := RADIO_SCORE to SCHAAL_WAARDEN do { for itemNo:=17 to 26 do } begin GetDItem(d, itemNo, T, H, R); if itemNo = RADIO_VERDELING then SetCtlValue(ControlHandle(H), OFF) else if itemNo = RADIO_DREMPEL then SetCtlValue(ControlHandle(H), OFF) else if itemNo = PLOT_LEER_CURVE then SetCtlValue(ControlHandle(H), OFF) else if itemNo = PLOT_UTIL_CURVE then SetCtlValue(ControlHandle(H), OFF) else if itemNo = PLOT_VERW_UTIL then SetCtlValue(ControlHandle(H), OFF) else if itemNo = PLOT_UTIL_TIJD then SetCtlValue(ControlHandle(H), OFF) else if itemNo = PLOT_BETA then SetCtlValue(ControlHandle(H), ON) else if itemNo = RASTER_OF_NIET then SetCtlValue(ControlHandle(H), ON) else SetCtlValue(ControlHandle(H), ON); { de procedure SetCtlValue bij enabled items heeft direct effect in window } end; GetDItem(d, PLOT_VTV, T, H, R); SetCtlValue(ControlHandle(H), OFF); GetDItem(d, PLOT_BINO, T, H, R); SetCtlValue(ControlHandle(H), OFF); GetDItem(d, SIMULEER, T, H, R); SetCtlValue(ControlHandle(H), OFF); SaveSettings; { zodat ook bij niet openen van het dialoogvenster de defaultwaarden beschikbaar zijn } end; { if d <> nil then } end; end. { of unit } procedure PrintPlot; { Mark & Reed Hoofdstuk 7 p. 337} { zie Inside Macintosh IV, Ch. 1, for a detailed discussion of the File Manager } const CANT_OPEN_FILE = STANDAARD_ID + 6; GET_EOF_ERROR = STANDAARD_ID + 7; HEADER_TOO_SMALL = STANDAARD_ID + 8; OUT_OF_MEMORY = STANDAARD_ID + 9; CANT_READ_HEADER = STANDAARD_ID + 10; CANT_READ_PICT = STANDAARD_ID + 11; NIL_STRING = ''; IGNORED_STRING = NIL_STRING; procedure PrintPICTFile (reply: SFReply); { Mark & Reed Hoofdstuk 7 p. 337 } var srcFile: INTEGER; printPort: TPPrPort; printStatus: TPrStatus; thePict: PicHandle; pictHeader: packed array[0..HEADER_SIZE] of CHAR; pictSize, headerSize: LONGINT; dummy: OSErr; begin { start off with a call to FSOpen to get the access path of the file selected } if (FSOpen(reply.fName, reply.vRefNum, srcFile) <> noErr) then { FSOpen opens the specified file for eading and / or writing , depending on } { the file's open permission. vRefNum specifies the file's volume and directory } FoutBoodschap(CANT_OPEN_FILE); { GetEOF returns the size of the file in pictSize: } if (GetEOF(srcFile, pictSize) <> noErr) then FoutBoodschap(GET_EOF_ERROR); headerSize := HEADER_SIZE; { FSReader attempts to read 512-byte header that describes the rest of the file, } { the actual number of bytes read is returned in the parameter headerSize } if (FSRead(srcFile, headerSize, @pictHeader) <> noErr) then FoutBoodschap(CANT_READ_HEADER); pictSize := pictSize - HEADER_SIZE; { printPict won't need the 512-byte header } if pictSize <= 0 then FoutBoodschap(HEADER_TOO_SMALL); thePict := PicHandle(NewHandle(pictSize)); if thePict = nil then FoutBoodschap(OUT_OF_MEMORY); HLock(Handle(thePict)); { FSRead requires a pointer to the read buffer, } { and because you allocated a handle thePict, you have to HLock the handle } { before you pass its pointer thePict^ to FSRead. } { read in the PICT: } if (FSRead(srcFile, pictSize, Ptr(thePict^)) <> noErr) then FoutBoodschap(CANT_READ_PICT); dummy := FSClose(srcFile); { the PICT is in memory, so close the PICT file } { PrOpenDoc returns a pointer printPort to the printing grafPort: } printPort := PrOpenDoc(gPrintRecordH, nil, nil); prOpenPage(printPort, nil); { open a new page } DrawPicture(thePict, thePict^^.picFrame); { and draw the picture } PrClosePage(printPort); { then close the page } PrCloseDoc(printPort); { and the printing grafPort } PrPicFile(gPrintRecordH, nil, nil, nil, printStatus); { This prints the file! } HUnlock(Handle(thePict)); { opruimen } end; function DoDialogs: BOOLEAN; { do the Page Setup and Print Job dialogs } (* dat kan worden verbeterd door die dialogen op te roepen via het menu, met *) (* Page Setup en Print Job, je raadt het al *) var keepGoing: BOOLEAN; begin keepGoing := PrStlDialog(gPrintRecordH); if keepGoing then DoDialogs := PrJobDialog(gPrintRecordH) else DoDialogs := FALSE; end; procedure GetFileName (var replyPtr: SFReply); { sets up the arguments and calls SFGetFile } var myPoint: Point; typelist: SFTypelist; numTypes: INTEGER; begin myPoint.h := 100; myPoint.v := 100; typeList[0] := 'PICT'; numTypes := 1; { set to 1, so you need to set up a single entry in the typeList array } SFGetFile(myPoint, IGNORED_STRING, nil, numTypes, typelist, nil, replyPtr); { SFGetFile displays the standard open dialog on the screen at the point myPoint } { Mark & Reed, p. 321. } end; procedure PrintInit; begin gPrintRecordH := THPrint(NewHandle(sizeof(TPrint))); { NewHandle allocates a block of memory to the size of a print record and makes } { gPrintRecordH a handle to that memory } PROpen; { start up the Printing Manager } PrintDeFault(gPrintRecordH); { ensures that any changes made to the Page Setup } { and Print Job dialogs will be implemented when you print } end; begin PrintInit; GetFileName(gReply); if gReply.good then begin if DoDialogs then PrintPictFile(gReply) end; end; { This file has been processed by The THINK Pascal Source Converter, v1.1. } {} {Created: Tuesday, August 2, 1988 at 9:06 AM} { PrintTraps.p} { Pascal Interface to the Macintosh Libraries} {} { Copyright Apple Computer, Inc. 1985-1988} { All rights reserved} {} unit PrintTraps; interface const bDraftLoop = 0; bSpoolLoop = 1; iPFMaxPgs = 128; {Max number of pages in a print file.} iPrPgFract = 120; iPrPgFst = 1; {Page range constants} iPrPgMax = 9999; iPrRelease = 2; {Current version number of the code.} iPrSavPFil = -1; iIOAbort = -27; pPrGlobals = $00000944; bUser1Loop = 2; bUser2Loop = 3; fNewRunBit = 2; {Bit 2 (3rd bit) in bDocLoop is new JobRun indicator.} fHiResOK = 3; {Bit 3 (4th bit) in bDocLoop is hi res indicator for paint.} fWeOpenedRF = 4; {Bit 4 (5th bit) in bDocLoop is set if driver opened the pr res file.} iPrAbort = 128; iPrDevCtl = 7; {The PrDevCtl Proc's ctl number} lPrReset = $00010000; {The PrDevCtl Proc's CParam for reset} lPrLineFeed = $00030000; lPrLFStd = $0003FFFF; {The PrDevCtl Proc's CParam for std paper advance} lPrLFSixth = $0003FFFF; lPrPageEnd = $00020000; {The PrDevCtl Proc's CParam for end page} lPrDocOpen = $00010000; {note: same as lPrReset low order byte indicates number of copies to print} lPrPageOpen = $00040000; lPrPageClose = $00020000; {note: same as lPrPageEnd} lPrDocClose = $00050000; iFMgrCtl = 8; {The FMgr's Tail-hook Proc's ctl number} iMscCtl = 9; {Msc Text state / Drvr State ctl number} iPvtCtl = 10; {Private ctls start here} iMemFullErr = -108; {} {Driver constants} iPrBitsCtl = 4; {The Bitmap Print Proc's ctl number} lScreenBits = 0; {The Bitmap Print Proc's Screen Bitmap param} lPaintBits = 1; {The Bitmap Print Proc's Paint [sq pix] param} lHiScreenBits = $00000002; {The Bitmap Print Proc's Screen Bitmap param} lHiPaintBits = $00000003; {The Bitmap Print Proc's Paint [sq pix] param} iPrIOCtl = 5; {The Raw Byte IO Proc's ctl number} iPrEvtCtl = 6; {The PrEvent Proc's ctl number} lPrEvtAll = $0002FFFD; {The PrEvent Proc's CParam for the entire screen} lPrEvtTop = $0001FFFD; {The PrEvent Proc's CParam for the top folder} iPrDrvrRef = -3; getRslDataOp = 4; {PrGeneral Cs} setRslOp = 5; {PrGeneral Cs} draftBitsOp = 6; {PrGeneral Cs} noDraftBitsOp = 7; {PrGeneral Cs} getRotnOp = 8; {PrGeneral Cs} NoSuchRsl = 1; {PrGeneral Cs} RgType1 = 1; {PrGeneral Cs} type TFeed = (feedCut, feedFanfold, feedMechCut, feedOther); TScan = (scanTB, scanBT, scanLR, scanRL); TPPrPort = ^TPrPort; TPrPort = record gPort: GrafPort; {The Printer's graf port.} gProcs: QDProcs; {..and its procs} lGParam1: LONGINT; {16 bytes for private parameter storage.} lGParam2: LONGINT; lGParam3: LONGINT; lGParam4: LONGINT; fOurPtr: BOOLEAN; {Whether the PrPort allocation was done by us.} fOurBits: BOOLEAN; {Whether the BitMap allocation was done by us.} end; { Printing Graf Port. All printer imaging, whether spooling, banding, etc, happens "thru" a GrafPort.} {This is the "PrPeek" record.} TPPrInfo = ^TPrInfo; TPrInfo = record iDev: INTEGER; {Font mgr/QuickDraw device code} iVRes: INTEGER; {Resolution of device, in device coordinates} iHRes: INTEGER; {..note: V before H => compatable with Point.} rPage: Rect; {The page (printable) rectangle in device coordinates.} end; { Print Info Record: The parameters needed for page composition. } TPPrStl = ^TPrStl; TPrStl = record wDev: INTEGER; {The device (driver) number. Hi byte=RefNum, Lo byte=variant. f0 = fHiRes f1 = fPortrait, f2 = fSqPix, f3 = f2xZoom, f4 = fScroll.} iPageV: INTEGER; {paper size in units of 1/iPrPgFract} iPageH: INTEGER; { ..note: V before H => compatable with Point.} bPort: SignedByte; {The IO port number. Refnum?} feed: TFeed; {paper feeder type.} end; { Printer Style: The printer configuration and usage information. } TPPrXInfo = ^TPrXInfo; TPrXInfo = record iRowBytes: INTEGER; iBandV: INTEGER; iBandH: INTEGER; iDevBytes: INTEGER; iBands: INTEGER; bPatScale: SignedByte; bUlThick: SignedByte; bUlOffset: SignedByte; bUlShadow: SignedByte; scan: TScan; bXInfoX: SignedByte; end; TPPrJob = ^TPrJob; TPrJob = record iFstPage: INTEGER; iLstPage: INTEGER; iCopies: INTEGER; bJDocLoop: SignedByte; fFromUsr: BOOLEAN; pIdleProc: ProcPtr; pFileName: StringPtr; iFileVol: INTEGER; bFileVers: SignedByte; bJobX: SignedByte; end; TPrFlag1 = packed record f15: BOOLEAN; f14: BOOLEAN; f13: BOOLEAN; f12: BOOLEAN; f11: BOOLEAN; f10: BOOLEAN; f9: BOOLEAN; f8: BOOLEAN; f7: BOOLEAN; f6: BOOLEAN; f5: BOOLEAN; f4: BOOLEAN; f3: BOOLEAN; f2: BOOLEAN; fLstPgFst: BOOLEAN; fUserScale: BOOLEAN; end; TPPrint = ^TPrint; THPrint = ^TPPrint; TPrint = record iPrVersion: INTEGER; prInfo: TPrInfo; rPaper: Rect; prStl: TPrStl; prInfoPT: TPrInfo; prXInfo: TPrXInfo; prJob: TPrJob; case INTEGER of 0: ( printX: array[1..19] of INTEGER ); 1: ( prFlag1: TPrFlag1; {a word of flags} iZoomMin: INTEGER; iZoomMax: INTEGER; hDocName: StringHandle ); {current doc's name, nil = front window} end; TPPrStatus = ^TPrStatus; TPrStatus = record iTotPages: INTEGER; {Total pages in Print File.} iCurPage: INTEGER; {Current page number} iTotCopies: INTEGER; {Total copies requested} iCurCopy: INTEGER; {Current copy number} iTotBands: INTEGER; {Total bands per page.} iCurBand: INTEGER; {Current band number} fPgDirty: BOOLEAN; {True if current page has been written to.} fImaging: BOOLEAN; {Set while in band's DrawPic call.} hPrint: THPrint; {Handle to the active Printer record} pPrPort: TPPrPort; {Ptr to the active PrPort} hPic: PicHandle; {Handle to the active Picture} end; { Print Status: Print information during printing. } TPPfPgDir = ^TPfPgDir; THPfPgDir = ^TPPfPgDir; TPfPgDir = record iPages: INTEGER; iPgPos: array[0..128] of LONGINT; {ARRAY [0..iPfMaxPgs] OF LONGINT} end; { PicFile = a TPfHeader followed by n QuickDraw Pics (whose PicSize is invalid!) } TPPrDlg = ^TPrDlg; TPrDlg = record Dlg: DialogRecord; {The Dialog window} pFltrProc: ProcPtr; {The Filter Proc.} pItemProc: ProcPtr; {The Item evaluating proc.} hPrintUsr: THPrint; {The user's print record.} fDoIt: BOOLEAN; fDone: BOOLEAN; lUser1: LONGINT; {Four longs for user's to hang global data.} lUser2: LONGINT; {...Plus more stuff needed by the particular printing dialog.} lUser3: LONGINT; lUser4: LONGINT; end; TGnlData = record iOpCode: INTEGER; iError: INTEGER; lReserved: LONGINT; {more fields here depending on call} end; TRslRg = record iMin: INTEGER; iMax: INTEGER; end; TRslRec = record iXRsl: INTEGER; iYRsl: INTEGER; end; TGetRslBlk = record iOpCode: INTEGER; iError: INTEGER; lReserved: LONGINT; iRgType: INTEGER; xRslRg: TRslRg; yRslRg: TRslRg; iRslRecCnt: INTEGER; rgRslRec: array[1..27] of TRslRec; end; TSetRslBlk = record iOpCode: INTEGER; iError: INTEGER; lReserved: LONGINT; hPrint: THPrint; iXRsl: INTEGER; iYRsl: INTEGER; end; TDftBitsBlk = record iOpCode: INTEGER; iError: INTEGER; lReserved: LONGINT; hPrint: THPrint; end; TGetRotnBlk = record iOpCode: INTEGER; iError: INTEGER; lReserved: LONGINT; hPrint: THPrint; fLandscape: BOOLEAN; bXtra: SignedByte; end; TPRect = ^Rect; { A Rect Ptr } TPBitMap = ^BitMap; { A BitMap Ptr } TN = 0..15; { a Nibble } TPWord = ^TWord; THWord = ^TPWord; TWord = packed record case INTEGER of 0: ( c1, c0: CHAR ); 1: ( b1, b0: SignedByte ); 2: ( usb1, usb0: Byte ); 3: ( n3, n2, n1, n0: TN ); 4: ( f15, f14, f13, f12, f11, f10, f9, f8, f7, f6, f5, f4, f3, f2, f1, f0: BOOLEAN ); 5: ( i0: INTEGER ); end; TPLong = ^TLong; THLong = ^TPLong; TLong = record case INTEGER of 0: ( w1, w0: TWord ); 1: ( b1, b0: LONGINT ); 2: ( p0: Ptr ); 3: ( h0: Handle ); 4: ( pt: Point ); end; procedure PrPurge; inline $2F3C, $A800, $0000, $A8FD; procedure PrNoPurge; inline $2F3C, $B000, $0000, $A8FD; function PrDrvrDCE: Handle; inline $2F3C, $9400, $0000, $A8FD; function PrDrvrVers: INTEGER; inline $2F3C, $9A00, $0000, $A8FD; procedure PrOpen; inline $2F3C, $C800, $0000, $A8FD; procedure PrClose; inline $2F3C, $D000, $0000, $A8FD; procedure PrintDefault (hPrint: THPrint); inline $2F3C, $2004, $0480, $A8FD; function PrValidate (hPrint: THPrint): BOOLEAN; inline $2F3C, $5204, $0498, $A8FD; function PrStlDialog (hPrint: THPrint): BOOLEAN; inline $2F3C, $2A04, $0484, $A8FD; function PrJobDialog (hPrint: THPrint): BOOLEAN; inline $2F3C, $3204, $0488, $A8FD; procedure PrJobMerge (hPrintSrc: THPrint; hPrintDst: THPrint); inline $2F3C, $5804, $089C, $A8FD; function PrOpenDoc (hPrint: THPrint; pPrPort: TPPrPort; pIOBuf: Ptr): TPPrPort; inline $2F3C, $0400, $0C00, $A8FD; procedure PrCloseDoc (pPrPort: TPPrPort); inline $2F3C, $0800, $0484, $A8FD; procedure PrOpenPage (pPrPort: TPPrPort; pPageFrame: TPRect); inline $2F3C, $1000, $0808, $A8FD; procedure PrClosePage (pPrPort: TPPrPort); inline $2F3C, $1800, $040C, $A8FD; procedure PrPicFile (hPrint: THPrint; pPrPort: TPPrPort; pIOBuf: Ptr; pDevBuf: Ptr; var prStatus: TPrStatus); inline $2F3C, $6005, $1480, $A8FD; function PrError: INTEGER; inline $2F3C, $BA00, $0000, $A8FD; procedure PrSetError (iErr: INTEGER); inline $2F3C, $C000, $0200, $A8FD; procedure PrGeneral (pData: Ptr); inline $2F3C, $7007, $0480, $A8FD; procedure PrDrvrOpen; inline $2F3C, $8000, $0000, $A8FD; function PrDlgMain (hPrint: THPrint; pDlgInit: ProcPtr): BOOLEAN; inline $2F3C, $4A04, $0894, $A8FD; procedure PrDrvrClose; inline $2F3C, $8800, $0000, $A8FD; function PrJobInit (hPrint: THPrint): TPPrDlg; inline $2F3C, $4404, $0410, $A8FD; procedure PrCtlCall (iWhichCtl: INTEGER; lParam1: LONGINT; lParam2: LONGINT; lParam3: LONGINT); inline $2F3C, $A000, $0E00, $A8FD; function PrStlInit (hPrint: THPrint): TPPrDlg; inline $2F3C, $3C04, $040C, $A8FD; { UsingPrintTraps } implementation end. unit Press; interface const mbig = 1000000000; { Ran3 } mz = 0; { Ran3 } fac = 1.0e-9; { Ran3 } np = 4; type RealArrayNP = array[1..np] of REAL; { Mnewt } RealArrayNPbyNP = array[1..np, 1..np] of REAL; { Mnewt } IntegerArrayNP = array[1..np] of INTEGER; { Mnewt } glnarray = RealArrayNP; glnbyn = RealArrayNPbyNP; glindx = IntegerArrayNP; glnpbynp = glnbyn; var glinext, glinextp, idum: LONGINT; { Ran3 } glma: array[1..55] of LONGINT; { Ran3 } cof: array[1..6] of EXTENDED; { GammLn; Press c.s. gebruiken 'double' } glntop: INTEGER; { GammLn } gla: array[0..400] of REAL; { GammLn } function FactLn (n: INTEGER): REAL; procedure FactLnInit; function Ran3 (var idum: LONGINT): REAL; procedure Ran3Init (idum: LONGINT); { initialiseren;} { vervangt de door Press c.s. gebruikte conditie op de } { geinitialiseerde waarde voor idem = -1 } function GammLn (xx: REAL): REAL; procedure GammLnInit; function GamDev (var ia: INTEGER; var idum: LONGINT): REAL; function BetaDev (a, b: INTEGER; var idum: LONGINT): REAL; function BnlDev (pp: REAL; n: INTEGER; var idum: LONGINT): REAL; procedure Ludcmp (var a: RealArrayNPbyNP; n: INTEGER; var indx: IntegerArrayNP; var d: REAL); procedure Lubksb (var a: RealArrayNPbyNP; n: INTEGER; var indx: IntegerArrayNP; var b: RealArrayNP); procedure usrfun (x: glnarray; n: INTEGER; var alpha: glnbyn; var beta: glnarray); procedure Mnewt (ntrial: INTEGER; var x: RealArrayNP; n: INTEGER; tolx, tolf: REAL); implementation function Factln; { Berekent ln n!; maakt gebruik van de relatie n! = gamma ( n+1 ). } { W.H. Press, B.P. Flannery, S.A. Teukolsky, W.T. Vetterling, } { Numerical recipes; the art of scientific computing. } { London: Cambridge University Press, 1986, p.159, 705. } begin if n < 0 then begin writeln('pause: in PROCEDURE FactLn negatieve input: ', n : 1); readln; end; if (n <= 199) then {voor grotere waarden wordt geen tabel aangehouden } begin if gla[n + 1] = -1 then {als de betreffende waarde nog niet is berekend } gla[n + 1] := GammLn(n + 1); Factln := gla[n + 1] end else Factln := GammLn(n + 1); end; procedure FactLnInit; { Initialiseert ook GammLn } var i: INTEGER; begin for i := 1 to 400 do gla[i] := -1; cof[1] := 76.18009173; cof[2] := -86.50532033; cof[3] := 24.01409822; cof[4] := -1.231739516; cof[5] := 0.120858003e-2; cof[6] := -0.536382e-5; end; function GammLn; { Returns the value LN [gamma (xx)] for xx > 0. Full accuracy is obtained for xx > 1. } { W.H. Press, B.P. Flannery, S.A. Teukolsky, W.T. Vetterling, } { Numerical recipes; the art of scientific computing. } { London: Cambridge University Press, 1986, p. 156-158, 704 e.v. } { In afwijking van Press e.a. gebeurt initialiseren van het array cof in een } { afzonderlijke PROCEDURE, omdat GammLn heel vaak wordt aangeroepen scheelt dat } { tijd.} const stp = 2.50662827465; fpf = 5.5; var x, tmp, ser: EXTENDED; j: INTEGER; begin if cof[4] <> -1.231739516 then GammlnInit; x := xx - 1; tmp := x + fpf; tmp := (x + 0.5) * ln(tmp) - tmp; ser := 1; for j := 1 to 6 do begin x := x + 1; ser := ser + cof[j] / x end; GammLn := tmp + ln(stp * ser) end; procedure GammlnInit; { Initialiseert ook FactLn. } var i: INTEGER; begin cof[1] := 76.18009173; cof[2] := -86.50532033; cof[3] := 24.01409822; cof[4] := -1.231739516; cof[5] := 0.120858003e-2; cof[6] := -0.536382e-5; for i := 1 to 400 do gla[i] := -1 end; function Ran3; { Press en anderen, blz. 199, 715-716 } { hier is gekozen voor gebruik van 4-byte integers, dus LONGINT, ipv reals. } var mj: LONGINT; begin glinext := glinext + 1; if glinext = 56 then glinext := 1; glinextp := glinextp + 1; if glinextp = 56 then glinextp := 1; mj := glma[glinext] - glma[glinextp]; if mj < 0 then mj := mj + mBig; glma[glinext] := mj; Ran3 := mj * fac; end; procedure Ran3Init; { initialiseren; } { vervangt de door Press c.s. gebruikte conditie op de } { geinitialiseerde waarde voor idem = -1 } const mseed = 161803398; var i, ii: 1..55; mj, mk, k: LONGINT; begin if idum <= 0 then idum := -1; { anders heeft idum de waarde TickCount gekregen in het hoofdprogramma } mj := mseed + idum; mj := mj mod mbig; glma[55] := mj; mk := 1; for i := 1 to 54 do begin ii := 21 * i mod 55; glma[ii] := mk; mk := mj - mk; if mk < 0 then mk := mk + mbig; mj := glma[ii] end; for k := 1 to 4 do begin for i := 1 to 55 do begin { glma [ i ] := glma [ i ] - glma [ 1 + ( ( i + 30 ) mod 55 ) ] ; dit werkt in Turbo niet goed } if i < 25 then glma[i] := glma[i] - glma[1 + i + 30] else glma[i] := glma[i] - glma[1 + i + 30 - 55]; if glma[i] < mz then glma[i] := glma[i] + mBig end; end; glinext := 0; glinextp := 31; idum := 1; { ***** Ik denk toch dat dit allen op coinditie IF idum = -1 THEN moet zijn !!!} end; function GamDev; { Returns a deviate distributed as a gamma distribution of INTEGER order IA, } { i.e. a waiting time to the IAth event in a Poisson process of unit mean, } { using Ran3 as the source of uniform deviates. } { Press, Flannery, Teukolsky, Vetterling, 1986, blz 716. } { Maakt gebruik van de 'rejection method', beschreven op blz. 203 e.v. } { "The rejection method is a powerful, general technique for generating random } { deviates whose distribution function p(x)dx (probability of a value occurring } { between x and x + dx) is known and computable. The rejection method does not } { require that the cumulative distribution function [indefinite integral of p(x)] be } { readily computable, much less the inverse of that function - which was requitred } { for the transformation method " in section 7.2. Zie de duidelijke figuur 7.3.1 op } { blz. 205. Function GamDev berust op een algoritme ontleend aan Ahrens, zoals } { beschreven in Knuth. Ik weet niet of ia REEEL ook OK is, houd het dus voorlopig } { even bij INTEGER. } var am, e, s, v1, v2, x, y: REAL; {' Extended' is niet nodig, dan zouden Press cs 'double' hebben gebruikt } j: INTEGER; begin if ia < 1 then begin writeln('pause in GamDev: de input is kleiner dan 1'); readln; end; if ia < 6 then { Using direct method, adding waiting times;} { for small values of ia it is best to add up, ia exponentially distributed waiting } {times, i.e. logarithms of uniform deviates. Since the sum of the logarithsms is } {the logarithm of the product, one really has only to generate the product } { of ia uniform deviates, then take the log. } begin x := 1; for j := 1 to ia do x := x * Ran3(idum); { idum is de seed voor Ran3 } x := -ln(x) end else { Use rejection method } begin repeat repeat repeat { "A useful comparison function (...) is derives from the } { Lorentzian distribution p ( y ) dy = ( 1 / pi ) ( 1 / ( 1 + y* y ) dy, } { whise inverse indefinite integral is just the tangent function." } { These lines generate the tangent of a random angle, i.e. are equivalent to } { y = TAN ( 3.14159265 * Randomx ( r ) / ( scalb ( 31, 1 ) - 1 ) } v1 := 2 * Ran3(idum) - 1; v2 := 2 * Ran3(idum) - 1; until sqr(v1) + sqr(v2) <= 1; y := v2 / v1; am := ia - 1; s := sqrt(2 * am + 1); x := s * y + am; until x > 0; { reject in region of zero probability } e := (1 + sqr(y)) * exp(am * ln(x / am) - s * y); { ratio of probability fn. to comparison fn. } until Ran3(idum) <= e { reject on basis of a second uniform deviate } end; GamDev := x end; function BetaDev; { Random deviate uit Betaverdeling met integer parameters a en b. } { Procedure ontleend aan S.J. Yakowitz, Computational probability and simulation, } { Amsterdam: Addison-Wesley Publishing Company, 1977, p. 63. } { Generate independent gamma (a, alpha ) and ( b, alpha ) Variates X1 and X2 } { and use the fact ( Wilks, 1962, pp. 170-174 ) that under these circumstances, } { X1 / ( X1+X2 ) is beta ( a, b ). } { In deze procedure wordt alpha=1 gekozen, zodat deze parameter wegvalt. } var x1, x2: REAL; begin if (a <= 1) or (b <= 1) then writeln('parameters: a = ', a : 2, ' en b = ', b : 2); x1 := GamDev(a, idum); x2 := GamDev(b, idum); BetaDev := x1 / (x1 + x2); end; function BnlDev; { Returns as a REAL number an INTEGER value that is a random deviate drawn } { from a binomial distribution of N trials each of probability pp. } { Press, Flannery, Teukolsky, Vetterling, 1986, blz 717, 208; } { De procedure is gebaseerd op de rejection method, p. 203 e.v. } label 1; const pi = 3.141592654; var am, em, en, g, angle: REAL; oldg, p, pc, bnl: REAL; pclog, plog, pold, sq, t, y: REAL; a, b, j, nold: INTEGER; begin nold := -1; pold := -1; pc := -1; if pp <= 0.5 then { de analyse is symmetrisch onder verwisseling van pp en 1 - pp, } { mits ook het antwoord wordt veranderd tot N min het antwoord; onthouden ! } p := pp else p := 1 - pp; am := n * p; { this is the mean of the binomila deviate to be produced } if n < 25 then { use the direct method when N is not too large } begin bnl := 0; for j := 1 to n do { Voor alle trials n } if Ran3(idum) < p then { Uniform deviate } bnl := bnl + 1 end else if am < 1 then { IF fewer than one event is expected out of 25 or more trials, } { THEN the distribution is quite accurately Poisson. Use direct Poisson method } begin g := exp(-am); t := 1; for j := 0 to n do begin t := t * Ran3(idum); if t < g then goto 1 end; j := n; 1: bnl := j; end else { Use the rejection method } begin if n <> nOld then { IF n has changed, THEN compute useful quantities } begin en := n; oldg := Gammln(en + 1); nOld := n end; if p <> pOld then { IF p has changed, then compute useful quantities } begin pc := 1 - p; pLog := ln(p); pcLog := ln(pc); pOld := p; end; sq := sqrt(2 * am * pc); { rejection method with a Lorentzian comparison function} { vergelijk function GamDev. } repeat repeat angle := pi * Ran3(idum); y := sin(angle) / cos(angle); em := sq * y + am; until ((em >= 0) and (em < en + 1)); em := trunc(em); { trick for INTEGER-valued distribution } t := 1.2 * sq * (1 + sqr(y)) * exp(oldg - Gammln(em + 1) - Gammln(en - em + 1) + em * pLog + (en - em) * pcLog); until Ran3(idum) <= t; { reject; this happens about 0.5 times per deviate, on average } bnl := em; end; if p <> pp then { onthouden ! } bnl := n - bnl; BnlDev := bnl; end; procedure Ludcmp; { Given an n x n matrix a [ 1..n, 1..n ], this routine replaces it by the LU decomposition of a } { rowwise prmutation of itself. a and n are input, arranged as in equation (2.3.14) above; } { indx [ 1..n ] is an output vector which records the row permutation effected by the partial} { pivoting; d is output as + or -1 depending on whether the number of row interchanges was} { even or odd, respectively. This routine is used in combination with lubskb to solve } { linear equations or invert a matrix. } { } { W.H. Press, B.P. Flannery, S.A. Teukolsky, W.T. Vetterling,} { Numerical recipes; the art of scientific computing.} { London: Cambridge University Press, 1989. p. 42.} { Zie ook zelfde auteurs (1986) Numerical recipes: example book p. 8} { } const tiny = 1.0e-20; var k, j, imax, i: INTEGER; sum, dum, big: REAL; vv: ^RealArrayNP; begin { ludcmp } new(vv); d := 0; for i := 1 to n do begin big := 0; for j := 1 to n do if abs(a[i, j]) > big then big := abs(a[i, j]); if big = 0 then begin { no nonzero largest element } writeln('pause in LUDCMP - singular matrix'); readln; end; vv^[i] := 1 / big; end; for j := 1 to n do begin { This is the loop over columns of Crout's method } for i := 1 to j - 1 do begin { This is equation (2.3.12) except for i = j } sum := a[i, j]; for k := 1 to i - 1 do sum := sum - a[i, k] * a[k, j]; a[i, j] := sum; end; big := 0; { initialize for the search for largest pivot element } for i := j to n do begin { This is i = j of equation (2.3.12) } { and i = j + 1 ... N of equation (2.3.13). } sum := a[i, j]; for k := 1 to j - 1 do sum := sum - a[i, k] * a[k, j]; a[i, j] := sum; dum := vv^[i] * abs(sum); { Is the figure of merit for the pivot better then the best so far? } if dum >= big then begin big := dum; imax := i; end; end; if j <> imax then begin { Do we need to interchange rows? } for k := 1 to n do begin { Yes, do so ... } dum := a[imax, k]; a[imax, k] := a[j, k]; a[j, k] := dum; end; d := -d; { ... and change the parity of d. } vv^[imax] := vv^[j]; { Also interchange the scale factor } end; indx[j] := imax; if a[j, j] = 0 then a[j, j] := tiny; { If the pivot element is zero the matrix is singular (at least to the precision of the algorithm).} { For some applications on singular matrices, it is desirable to substitute tiny for zero. } if j <> n then begin dum := 1 / a[j, j]; { Now, finally, divide by the pivot element } for i := j + 1 to n do a[i, j] := a[i, j] * dum; end; end; { Go back for the next column in the reduction. } dispose(vv); end; {ludcmp } procedure Lubksb; { Solves the set of n linear equations A • X = B. Here a [ 1..n, 1..n ] is input, not as the matrix A} { but rather as its LU decomposition, determined by the routine ludcmp. indx [ 1..n ] is input} { as the permutation vector returned by ludcmp. b [ 1.. ] is input as the right-hand side vector B,} { and returns with the solution vector X. a, n, and indx are not modified by this routine and can be} { left in place for successive calls with different right-hand sides b. This routine takes into } { account the possibility that b will begin with many zero elements, so it is efficient for use } { in matrix inversion.} { } { W.H. Press, B.P. Flannery, S.A. Teukolsky, W.T. Vetterling,} { Numerical recipes; the art of scientific computing.} { London: Cambridge University Press, 1989. p. 44.} { Zie ook zelfde auteurs (1986) Numerical recipes: example book p. 10} { } var j, ip, ii, i: INTEGER; sum: REAL; begin ii := 0; { When ii is set to a positive value, it will become the index of the first } { nonvanishing element of b. We now do the forward substitution, equation (2.3.6).} { The only new wrinkle is to unscramble the permutation. } for i := 1 to n do begin ip := indx[i]; sum := b[ip]; b[ip] := b[i]; if ii <> 0 then for j := ii to i - 1 do sum := sum - a[i, j] * b[j] else if sum <> 0 then { A nonzero element was encountered, so from } { now on we will have to do the sums in the loop above. } ii := i; b[i] := sum; end; for i := n downto 1 do begin { Now we do the substitution, equation (2.3.7). } sum := b[i]; for j := i + 1 to n do sum := sum - a[i, j] * b[j]; b[i] := sum / a[i, i]; { store a component of the solution vector X } end; end; procedure usrfun; begin alpha[1, 1] := -2 * x[1]; alpha[1, 2] := -2 * x[2]; alpha[1, 3] := -2 * x[3]; alpha[1, 4] := 1; alpha[2, 1] := 2 * x[1]; alpha[2, 2] := 2 * x[2]; alpha[2, 3] := 2 * x[3]; alpha[2, 4] := 2 * x[4]; alpha[3, 1] := 1; alpha[3, 2] := -1; alpha[3, 3] := 0; alpha[3, 4] := 0; alpha[4, 1] := 0; alpha[4, 2] := 1; alpha[4, 3] := -1; alpha[4, 4] := 0; { vier gelijktijdig op te lossen vergelijkingen : } beta[1] := sqr(x[1]) + sqr(x[2]) + sqr(x[3]) - x[4]; beta[2] := -sqr(x[1]) - sqr(x[2]) - sqr(x[3]) - sqr(x[4]) + 1; beta[3] := -x[1] + x[2]; beta[4] := -x[2] + x[3]; end; procedure Mnewt; { Given an initial gues x [ 1 .. n ] for a root in n dimensions, take ntrial Newton-Raphson steps} { to improve the root. Stop if the root converges in either summed absolute variable } { increments tolx or summed absolute function values tolf. } { } { W.H. Press, B.P. Flannery, S.A. Teukolsky, W.T. Vetterling,} { Numerical recipes; the art of scientific computing.} { London: Cambridge University Press, 1989. p. 307.} { Zie ook zelfde auteurs (1986) Numerical recipes: example book p. 125 } { } label 99; var k, i: INTEGER; errx, errf, d: REAL; beta: ^RealArrayNP; alpha: ^RealArrayNPbyNP; indx: ^IntegerArrayNP; begin new(beta); new(alpha); new(indx); for k := 1 to ntrial do begin usrfun(x, n, alpha^, beta^); errf := 0; for i := 1 to n do errf := errf + abs(beta^[i]); if errf <= tolf then goto 99; ludcmp(alpha^, n, indx^, d); lubksb(alpha^, n, indx^, beta^); errx := 0; for i := 1 to n do begin errx := errx + abs(beta^[i]); x[i] := x[i] + beta^[i]; end; if errx <= tolx then goto 99 end; 99: dispose(indx); dispose(alpha); dispose(beta); end; end.
http://www.benwilbrink.nl/publicaties/94AlgemeenToetsmodelCito.htm