Het Marangoni-effect1 is het fenomeen waarbij er massaoverdracht plaatsvindt langs het grensvlak tussen twee vloeistoffen als gevolg van een verschil in oppervlaktespanning2. In 2017 bedacht Kohta Suzuno van de Meiji University in Japan een manier om met dit effect de kortste weg naar de uitgang van een doolhof te vinden: vul het doolhof met melk (water kan ook maar de witte kleur van melk geeft een beter contrast), plaats een zuur hydrogelblok bij de uitgang, kleur de melk aan de ingang met inkt en maak de gekleurde melk basisch door er met een penseel kortstondig vloeibare zeep in aan te brengen. Als gevolg van het pH-verschil verandert de oppervlaktespanning, waardoor de gekleurde melk naar het hydrogelblok gedreven wordt.

video stub
Rode inkt vindt het kortste pad naar de uitgang van het doolhof.

In 2018 toonden Fernando Temprano-Coleto en zijn collega's aan dat het zelfs helemaal niet nodig is om de uitgang te markeren met een zuur hydrogelblok, zolang het oppervlak aan de in- en uitgang van het doolhof maar een voldoende groot gebied beslaat. Dit wordt geïllustreerd in bovenstaande video.

Opgave

In deze opgave simuleren we stroming doorheen een rechthoekig doolhof. Voor de eenvoud wordt de stroming niet veroorzaakt door een verschil in oppervlaktespanning, maar laten we een vloeistof door het doolhof stromen. Het doolhof heeft juist één ingang, waar we op hoogte 1.0 een kraan plaatsen waarlangs een vloeistof in het doolhof stroomt met debiet $$\delta$$. Het doolhof heeft ook juist één uitgang, waar we op hoogte 0.0 (corresponderende met de onderkant van het doolhof) een kraan plaatsen waarlangs alle vloeistof zonder beperking uit het doolhof kan stromen.

Hieronder tonen we aan de linkerkant hoe een doolhof in een tekstbestand voorgesteld wordt als een rechthoekig rooster. De muren van het doolhof worden aangeduid met hekjes (#) en de gangen worden aangeduid met spaties. Een doolhof is altijd langs de buitenste rand omgeven door muren. De ingang van het doolhof wordt voorgesteld door een plusteken (+) en de uitgang door een minteken (-). De ingang en de uitgang moeten niet noodzakelijk in de buitenste rand van het doolhof liggen. Aan de rechterkant zie je een grafische voorstelling van hetzelfde doolhof.

###########
+         #
# ### ### #
#   #   # #
# ####### #
#   #     #
### # #####
#   # #   #
# ### # # #
#   #   # -
###########
doolhof
Grafische weergave van een doolhof, met aan de ingang een kraan waarlangs vloeistof in het doolhof stroomt en aan de uitgang een kraan waarlangs de vloeistof zonder beperking uit het doolhof kan wegstromen.

We simuleren het vloeistofniveau in de cellen van het doolhof in discrete tijdsstappen. Voor de kraan aan de ingang van het doolhof wordt opengedraaid (stap 0 van de simulatie), is het vloeistofniveau 1.0 aan de ingang en 0.0 in de gangen en aan de uitgang van het doolhof.

doolhof (na 0 stappen)
Vloeistofniveau in het doolhof vooraleer de kraan bij de ingang wordt opengezet (na 0 stappen van de simulatie).
doolhof (na 1 stap)
Vloeistofniveau in het doolhof nadat 1 stap van de simulatie werd uitgevoerd.
doolhof (na 2 stappen)
Vloeistofniveau in het doolhof nadat 2 stappen van de simulatie werden uitgevoerd.
doolhof (na 3 stappen)
Vloeistofniveau in het doolhof nadat 3 stappen van de simulatie werden uitgevoerd.
doolhof (na 4 stappen)
Vloeistofniveau in het doolhof nadat 4 stappen van de simulatie werden uitgevoerd.
doolhof (na 5 stappen)
Vloeistofniveau in het doolhof nadat 5 stappen van de simulatie werden uitgevoerd.
doolhof (na 6 stappen)
Vloeistofniveau in het doolhof nadat 6 stappen van de simulatie werden uitgevoerd.
doolhof (na 7 stappen)
Vloeistofniveau in het doolhof nadat 7 stappen van de simulatie werden uitgevoerd.
doolhof (na 8 stappen)
Vloeistofniveau in het doolhof nadat 8 stappen van de simulatie werden uitgevoerd.
doolhof (na 9 stappen)
Vloeistofniveau in het doolhof nadat 9 stappen van de simulatie werden uitgevoerd.
doolhof (na 10 stappen)
Vloeistofniveau in het doolhof nadat 10 stappen van de simulatie werden uitgevoerd.

In elke tijdsstap wordt het vloeistofniveau in elke cel van het rooster herberekend op basis van het vloeistofniveau in de vorige tijdsstap. Daarbij blijft het vloeistofniveau aan de ingang en de uitgang constant op 1.0 en 0.0 staan. Uiteraard wordt er geen vloeistofniveau berekend in de cellen van het rooster die corresponderen met muren, en stellen we het vloeistofniveau daar per definitie in op -1.0.

simulatie van vloeistofniveau
Bijwerken van het vloeistofniveau tijdens een discrete simulatiestap.

Het nieuwe vloeistofniveau $$x_n$$ van een cel die correspondeert met een gang (niet de in- of uitgang) wordt berekend op basis van het vorige vloeistofniveau van die cel ($$x$$), het vorige vloeistofniveau van de naburige cellen boven ($$a$$), rechts ($$b$$), onder ($$c$$) en links ($$d$$) die corresponderen met gangen (inclusief in- en uitgang) en het debiet $$\delta$$ ($$0 \leq \delta \leq 1$$) waarmee de vloeistof aan de ingang in het doolhof stroomt. Deze berekening gebeurt aan de hand van de formule \[ x_n = x + \delta\left((a - x) + (b - x) + (c - x) + (d - x)\right) \] waarbij in de laatste som alle termen $$(m - x)$$ worden weggelaten waarvoor buurcel $$m$$ correspondeert met een muur van het doolhof. Op die manier behoudt het vloeistofniveau altijd een waarde uit het interval [0.0, 1.0]. Als je klikt op bovenstaande figuur met het vloeistofniveau bij tijdsstap 0, dan kan je doorklikken naar de simulaties van de 10 volgende tijdsstappen. Hieronder zie je de weergave van het vloeistofniveau na een simulatie over 1000 tijdsstappen.

doolhof (na 1000 stappen)
Vloeistofniveau in het doolhof nadat 1000 stappen van de simulatie werden uitgevoerd. De gele lijn geeft de route aan die een drijvend voorwerp zal volgen als het wordt loslaten bij de ingang van het doolhof.

Als we nu een drijvend voorwerp loslaten bij de ingang van het doolhof, dan zal het telkens naar de naburige cel stromen waarvan het vloeistofniveau het kleinste is van alle naburige cellen (boven, rechts, onder, links). Daar is de stroming tussen de twee naburige cellen immers het grootst. Dit proces stopt als er geen naburige cellen zijn waarvan het vloeistofniveau strikt kleiner is dan dat van de huidige cel. In bovenstaande afbeelding (vloeistofniveau na 1000 simulatiestappen) wordt de route van een drijvend voorwerp aangegeven door een gele lijn, en volgt die inderdaad het kortste pad van de ingang naar de uitgang van het doolhof.

Definieer een klasse Doolhof waarmee doolhoven kunnen voorgesteld worden die men kan gebruiken voor het simuleren in discrete tijdsstappen van het vloeistofniveau en het bepalen van de route waarlangs de stroming het sterkst is. Bij het aanmaken van een doolhof (Doolhof) moet de locatie van een tekstbestand doorgegeven worden. Daarin wordt de configuratie van een rechthoekig doolhof voorgesteld in het formaat zoals hierboven beschreven. Er mag vanuit gegaan worden dat het tekstbestand een geldige voorstelling van een doolhof bevat, zonder dat dit expliciet moet gecontroleerd worden. De klasse Doolhof moet minstens de volgende methoden ondersteunen:

Als er een object van de klasse Doolhof wordt doorgegeven aan de ingebouwde functies repr en str, dan moeten beide functies een stringvoorstelling van het doolhof teruggeven. Het formaat van beide voorstellingen kan afgeleid worden uit onderstaand voorbeeld. De functie str gebruikt voor de stringvoorstelling hetzelfde formaat als bij de voorstelling van het doolhof in het tekstbestand. Het enige verschil is dat de route waarlangs een drijvend voorwerp beweegt, wordt aangeduid met tildes (~) in plaats van spaties. De stringvoorstelling die wordt teruggegeven door de functie repr stelt een rooster voor, waarin alle kolommen bestaan uit vijf karakters en telkens van elkaar worden gescheiden door één enkele spatie. Een muur wordt voorgesteld door vijf hekjes (#####) en het vloeistofniveau in de andere cellen wordt voorgesteld als een reeël getal dat wordt afgerond tot op drie decimale cijfers en waarvan ook steeds drie decimale cijfers worden weergegeven.

Voorbeeld

In onderstaande voorbeeldsessie gaan we ervan uit dat de tekstbestanden doolhof_01.txt3, doolhof_02.txt4 en doolhof_03.txt5 zich in de huidige directory bevinden.

>>> doolhof = Doolhof('doolhof_01.txt6') # na 0 stappen
>>> doolhof
##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
1.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 #####
##### 0.000 ##### ##### ##### 0.000 ##### ##### ##### 0.000 #####
##### 0.000 0.000 0.000 ##### 0.000 0.000 0.000 ##### 0.000 #####
##### 0.000 ##### ##### ##### ##### ##### ##### ##### 0.000 #####
##### 0.000 0.000 0.000 ##### 0.000 0.000 0.000 0.000 0.000 #####
##### ##### ##### 0.000 ##### 0.000 ##### ##### ##### ##### #####
##### 0.000 0.000 0.000 ##### 0.000 ##### 0.000 0.000 0.000 #####
##### 0.000 ##### ##### ##### 0.000 ##### 0.000 ##### 0.000 #####
##### 0.000 0.000 0.000 ##### 0.000 0.000 0.000 ##### 0.000 0.000
##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
>>> doolhof.volgende_niveau((0, 0), 0.2)
-1.0
>>> doolhof.volgende_niveau((1, 0), 0.2)
1.0
>>> doolhof.volgende_niveau((1, 1), 0.2)
0.2
>>> doolhof.volgende_niveau((1, 2), 0.2)
0.0
>>> doolhof.route()
[(1, 0), (1, 1)]
>>> print(doolhof)
###########
+~        #
# ### ### #
#   #   # #
# ####### #
#   #     #
### # #####
#   # #   #
# ### # # #
#   #   # -
###########

>>> doolhof.simuleer_niveau(0.2) # na 1 stap
##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
1.000 0.200 0.000 0.000 0.000 0.000 0.000 0.000 0.000 0.000 #####
##### 0.000 ##### ##### ##### 0.000 ##### ##### ##### 0.000 #####
##### 0.000 0.000 0.000 ##### 0.000 0.000 0.000 ##### 0.000 #####
##### 0.000 ##### ##### ##### ##### ##### ##### ##### 0.000 #####
##### 0.000 0.000 0.000 ##### 0.000 0.000 0.000 0.000 0.000 #####
##### ##### ##### 0.000 ##### 0.000 ##### ##### ##### ##### #####
##### 0.000 0.000 0.000 ##### 0.000 ##### 0.000 0.000 0.000 #####
##### 0.000 ##### ##### ##### 0.000 ##### 0.000 ##### 0.000 #####
##### 0.000 0.000 0.000 ##### 0.000 0.000 0.000 ##### 0.000 0.000
##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
>>> doolhof.volgende_niveau((0, 0), 0.2)
-1.0
>>> doolhof.volgende_niveau((1, 0), 0.2)
1.0
>>> doolhof.volgende_niveau((1, 1), 0.2)
0.28
>>> doolhof.volgende_niveau((1, 2), 0.2)
0.04000000000000001
>>> doolhof.route()
[(1, 0), (1, 1), (1, 2)]
>>> print(doolhof)
###########
+~~       #
# ### ### #
#   #   # #
# ####### #
#   #     #
### # #####
#   # #   #
# ### # # #
#   #   # -
###########

>>> doolhof.simuleer_niveau(0.2) # na 2 stappen
##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
1.000 0.280 0.040 0.000 0.000 0.000 0.000 0.000 0.000 0.000 #####
##### 0.040 ##### ##### ##### 0.000 ##### ##### ##### 0.000 #####
##### 0.000 0.000 0.000 ##### 0.000 0.000 0.000 ##### 0.000 #####
##### 0.000 ##### ##### ##### ##### ##### ##### ##### 0.000 #####
##### 0.000 0.000 0.000 ##### 0.000 0.000 0.000 0.000 0.000 #####
##### ##### ##### 0.000 ##### 0.000 ##### ##### ##### ##### #####
##### 0.000 0.000 0.000 ##### 0.000 ##### 0.000 0.000 0.000 #####
##### 0.000 ##### ##### ##### 0.000 ##### 0.000 ##### 0.000 #####
##### 0.000 0.000 0.000 ##### 0.000 0.000 0.000 ##### 0.000 0.000
##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
>>> doolhof.volgende_niveau((0, 0), 0.2)
-1.0
>>> doolhof.volgende_niveau((1, 0), 0.2)
1.0
>>> doolhof.volgende_niveau((1, 1), 0.2)
0.328
>>> doolhof.volgende_niveau((1, 2), 0.2)
0.08000000000000002
>>> doolhof.route()
[(1, 0), (1, 1), (1, 2), (1, 3)]
>>> print(doolhof)
###########
+~~~      #
# ### ### #
#   #   # #
# ####### #
#   #     #
### # #####
#   # #   #
# ### # # #
#   #   # -
###########

>>> doolhof.simuleer_niveau(0.2, 8) # na 10 stappen
##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
1.000 0.481 0.256 0.113 0.040 0.010 0.002 0.000 0.000 0.000 #####
##### 0.249 ##### ##### ##### 0.002 ##### ##### ##### 0.000 #####
##### 0.088 0.033 0.012 ##### 0.000 0.000 0.000 ##### 0.000 #####
##### 0.033 ##### ##### ##### ##### ##### ##### ##### 0.000 #####
##### 0.010 0.002 0.000 ##### 0.000 0.000 0.000 0.000 0.000 #####
##### ##### ##### 0.000 ##### 0.000 ##### ##### ##### ##### #####
##### 0.000 0.000 0.000 ##### 0.000 ##### 0.000 0.000 0.000 #####
##### 0.000 ##### ##### ##### 0.000 ##### 0.000 ##### 0.000 #####
##### 0.000 0.000 0.000 ##### 0.000 0.000 0.000 ##### 0.000 0.000
##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
>>> doolhof.route()
[(1, 0), (1, 1), (2, 1), (3, 1), (4, 1), (5, 1), (5, 2), (5, 3), (6, 3), (7, 3), (7, 2), (7, 1)]
>>> print(doolhof)
###########
+~        #
#~### ### #
#~  #   # #
#~####### #
#~~~#     #
###~# #####
#~~~# #   #
# ### # # #
#   #   # -
###########

>>> doolhof.simuleer_niveau(0.2, 990) # na 1000 stappen
##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
1.000 0.933 0.887 0.841 0.795 0.750 0.709 0.669 0.630 0.592 #####
##### 0.913 ##### ##### ##### 0.747 ##### ##### ##### 0.555 #####
##### 0.893 0.892 0.891 ##### 0.745 0.743 0.742 ##### 0.519 #####
##### 0.875 ##### ##### ##### ##### ##### ##### ##### 0.483 #####
##### 0.858 0.842 0.828 ##### 0.323 0.353 0.384 0.416 0.449 #####
##### ##### ##### 0.814 ##### 0.294 ##### ##### ##### ##### #####
##### 0.783 0.792 0.802 ##### 0.266 ##### 0.114 0.090 0.067 #####
##### 0.775 ##### ##### ##### 0.239 ##### 0.137 ##### 0.045 #####
##### 0.770 0.766 0.764 ##### 0.212 0.187 0.162 ##### 0.022 0.000
##### ##### ##### ##### ##### ##### ##### ##### ##### ##### #####
>>> doolhof.route()
[(1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), (1, 6), (1, 7), (1, 8), (1, 9), (2, 9), (3, 9), (4, 9), (5, 9), (5, 8), (5, 7), (5, 6), (5, 5), (6, 5), (7, 5), (8, 5), (9, 5), (9, 6), (9, 7), (8, 7), (7, 7), (7, 8), (7, 9), (8, 9), (9, 9), (9, 10)]
>>> print(doolhof)
###########
+~~~~~~~~~#
# ### ###~#
#   #   #~#
# #######~#
#   #~~~~~#
### #~#####
#   #~#~~~#
# ###~#~#~#
#   #~~~#~-
###########

>>> print(Doolhof('doolhof_02.txt7').simuleer_niveau(0.2, stappen=1000))
###########
#    #    #
# ####### #
#         #
#####+#####
#    ~~~~~#
# #######~#
#    #~~~~#
#### #~####
#    #~~~~-
###########

>>> print(Doolhof('doolhof_03.txt8').simuleer_niveau(0.2, stappen=500))
#########################
#   #     #   # # #   #~-
# # # ### # ### # # # #~#
# # #   #~~~    #~~~# #~#
# #######~#~#####~#~###~#
#~~~~~~~~~#~    #~#~~~~~#
#~### #####~#####~##### #
#~#   #~~~~~#~~~~~    # #
#~#####~#####~### ### ###
#~    #~~~~~~~# #   # # #
#~### # ### ### ##### # #
#~#   #   #     #       #
#~# ### # ##### ### # ###
+~#   # #   #   #   #   #
#########################

Bronnen