import jax
import jax.numpy as jnp
from memo import memo
from memo import domain as product
from enum import IntEnum
False belief
forgery
A simple illustration of the key idea.
class Deception(IntEnum):
= 0
NO = 1
YES
class Action(IntEnum):
= 0
TRANSMIT = 1
FORGE
class Message(IntEnum):
= 0
BAD = 1
GOOD
@memo
def message_simple[_msgTrue: Message, _msgBelieved: Message](deception=0):
kartik: knows(_msgBelieved)in Message, wpp=1)]
kartik: thinks[josh: chooses(message is _msgTrue ### kartik knows the true contents of josh's message
kartik: observes[josh.message]
kartik: chooses(in Message,
message_transmitted =(
wpp== josh.message
message_transmitted if deception == 0
else message_transmitted != josh.message
),
)
kartik: thinks[
dae: knows(_msgBelieved),in Message, wpp=1)],
dae: thinks[josh: chooses(message ### what kartik thinks dae's belief about the message is (false belief when `message_transmitted != josh.message`):
is message_transmitted,
dae: observes [josh.message]
]return E[kartik[dae[josh.message == _msgBelieved]]]
Without deception, no false belief
= message_simple(0, print_table=True) _
+-------------------+-----------------------+-----------------+
| _msgTrue: Message | _msgBelieved: Message | message_simple |
+-------------------+-----------------------+-----------------+
| BAD | BAD | 1.0 |
| BAD | GOOD | 0.0 |
| GOOD | BAD | 0.0 |
| GOOD | GOOD | 1.0 |
+-------------------+-----------------------+-----------------+
With deception and false belief
= message_simple(1, print_table=True) _
+-------------------+-----------------------+-----------------+
| _msgTrue: Message | _msgBelieved: Message | message_simple |
+-------------------+-----------------------+-----------------+
| BAD | BAD | 0.0 |
| BAD | GOOD | 1.0 |
| GOOD | BAD | 1.0 |
| GOOD | GOOD | 0.0 |
+-------------------+-----------------------+-----------------+
A more elaborated version of the same
class Action(IntEnum):
= 0
TRANSMIT = 1
FORGE
class Message(IntEnum):
= 0
BAD = 1
GOOD
class Disposition(IntEnum):
= 0
MEAN = 1
NICE
@memo
def advisor_message[_msgTrue: Message, _disposition: Disposition]():
kartik: knows(_disposition)
kartik: thinks[in Message, wpp=1)
josh: chooses(message
]is _msgTrue
kartik: observes[josh.message] in Action, wpp=(
kartik: chooses(action
(0.9 if action == {Action.FORGE} else 0.1
if josh.message == {Message.GOOD}
) else (
0.8 if action == {Action.TRANSMIT} else 0.2
)
))
kartik: chooses(in Message,
message_transmitted =(
wpp== josh.message
message_transmitted if action == {Action.TRANSMIT}
else message_transmitted != josh.message
),
)
kartik: thinks[
dae: knows(_disposition),
dae: thinks[in Disposition, wpp=(
josh: given(disposition 0.9 if disposition == {Disposition.NICE} else 0.1
)),in Message, wpp=(
josh: chooses(message
(0.8 if message == {Message.BAD} else 0.2
if disposition == {Disposition.MEAN}
) else (
0.3 if message == {Message.BAD} else 0.7
)
))
],### what kartik thinks dae's belief about the message is (false belief when `action == {Action.FORGE}`):
is message_transmitted,
dae: observes [josh.message]
]return E[kartik[dae[E[josh.disposition == _disposition]]]]
= advisor_message(print_table=True) _
+-------------------+---------------------------+----------------------+
| _msgTrue: Message | _disposition: Disposition | advisor_message |
+-------------------+---------------------------+----------------------+
| BAD | MEAN | 0.18901099264621735 |
| BAD | NICE | 0.8109890222549438 |
| GOOD | MEAN | 0.20879121124744415 |
| GOOD | NICE | 0.7912086844444275 |
+-------------------+---------------------------+----------------------+
Try commenting out the line dae: observes [josh.message] is message_transmitted,
. How do the probabilities change? Without the observes statement, Kartik thinks that Dae thinks that that Josh’s disposition is the prior (0.9 NICE, 0.1 MEAN). What about if you replace it with dae: observes [josh.message] is _msgTrue,
(you’ll need to add kartik: knows(_msgTrue)
)?
goodman array
This implementation sticks close to the structure of Goodman et al. (2006).
We assume asymmetric-beta priors on \epsilon and \gamma. In the example simulations described below (Figures 2 and 3) the hyper-parameters were set to { \beta(1, 10) } for \epsilon, indicating that Sally probably wants her toy, and { \beta(1, 5) } for \gamma, indicating that she is unlikely to change her belief (lacking access).
Our PT model becomes deterministic if we introduce an External Information (or ‘alternate access’) variable, { \operatorname{E}_\gamma } (with prior probability \gamma), to explicitly represent events which cause changes in belief (in the absence of access).
The variable Desire has prior probability 1 - \epsilon, which will be large for desirable objects (such as a toy).
import jax
import jax.numpy as jnp
from memo import memo
from enum import IntEnum
from matplotlib import pyplot as plt
from jax.scipy.stats.beta import pdf as betapdf
= jax.jit(betapdf)
BetaPrior
class World(IntEnum):
= 0
ORIGINAL = 1
NEW
class PerceptualAccess(IntEnum):
= 0
NO = 1
YES
class Action(IntEnum):
= 0
ORIGINAL = 1
NEW
class Belief(IntEnum):
= 0
ORIGINAL = 1
NEW
class Desire(IntEnum):
= 0
OTHER = 1
TOY
@jax.jit
def action_pmf(a, b, d):
return jnp.array([
[0.5, 0.5], ### b=0, d=0
[0.5, 0.5], ### b=1, d=0
[
],
[1, 0], ### b=0, d=1
[0, 1], ### b=1, d=1
[
],
])[d, b, a]
@jax.jit
def belief_pt_pmf(b, w, v, gamma):
return jnp.array([
[1-gamma, gamma], ### w=0, v=0
[1-gamma, gamma], ### w=1, v=0
[
],
[1, 0], ### w=0, v=1
[0, 1], ### w=1, v=1
[
],
])[v, w, b]
= jnp.linspace(0, 1, 20)
Epsilon = jnp.linspace(0, 1, 20)
Gamma
@memo
def goodman_array[_w: World, _v: PerceptualAccess, _a: Action, _b: Belief, _d: Desire]():
child: knows(_v, _w, _a, _b, _d)
child: thinks[
sally: knows(_v, _w, _a),in Epsilon, wpp=BetaPrior(epsilon, 1, 10)),
sally: given(epsilon in Gamma, wpp=BetaPrior(gamma, 1, 5)),
sally: given(gamma in Desire, wpp=1 - epsilon if d == 1 else epsilon),
sally: chooses(d in Belief, wpp=belief_pt_pmf(b, _w, _v, gamma)), ### what child thinks that sally's belief is (false belief when `b != _w`)
sally: chooses(b in Action, wpp=action_pmf(a, b, d)),
sally: chooses(a
]is _a ### child observes sally look in a location
child: observes [ sally.a ] return child[ Pr[sally.b == _b, sally.d == _d] ]
= f(print_table=True, return_pandas=True, return_aux=True, return_xarray=True) res
+----------------------+-----------------------+
| _p: P | f |
+----------------------+-----------------------+
| 0.0 | 0.009999999776482582 |
| 0.010101010091602802 | 0.009999999776482582 |
| 0.020202020183205605 | 0.009999999776482582 |
| 0.03030303120613098 | 0.009999999776482582 |
| 0.04040404036641121 | 0.009999999776482582 |
| 0.05050504952669144 | 0.009999999776482582 |
| 0.06060606241226196 | 0.009999999776482582 |
| 0.07070706784725189 | 0.009999999776482582 |
| 0.08080808073282242 | 0.009999999776482582 |
| 0.09090909361839294 | 0.009999999776482582 |
| 0.10101009905338287 | 0.009999999776482582 |
| 0.1111111119389534 | 0.009999999776482582 |
| 0.12121212482452393 | 0.009999999776482582 |
| 0.13131313025951385 | 0.009999999776482582 |
| 0.14141413569450378 | 0.009999999776482582 |
| 0.1515151560306549 | 0.009999999776482582 |
| 0.16161616146564484 | 0.009999999776482582 |
| 0.17171716690063477 | 0.009999999776482582 |
| 0.1818181872367859 | 0.009999999776482582 |
| 0.19191919267177582 | 0.009999999776482582 |
| 0.20202019810676575 | 0.009999999776482582 |
| 0.21212121844291687 | 0.009999999776482582 |
| 0.2222222238779068 | 0.009999999776482582 |
| 0.23232322931289673 | 0.009999999776482582 |
| 0.24242424964904785 | 0.009999999776482582 |
| 0.2525252401828766 | 0.009999999776482582 |
| 0.2626262605190277 | 0.009999999776482582 |
| 0.27272728085517883 | 0.009999999776482582 |
| 0.28282827138900757 | 0.009999999776482582 |
| 0.2929292917251587 | 0.009999999776482582 |
| 0.3030303120613098 | 0.009999999776482582 |
| 0.31313130259513855 | 0.009999999776482582 |
| 0.3232323229312897 | 0.009999999776482582 |
| 0.3333333432674408 | 0.009999999776482582 |
| 0.34343433380126953 | 0.009999999776482582 |
| 0.35353535413742065 | 0.009999999776482582 |
| 0.3636363744735718 | 0.009999999776482582 |
| 0.3737373650074005 | 0.009999999776482582 |
| 0.38383838534355164 | 0.009999999776482582 |
| 0.39393940567970276 | 0.009999999776482582 |
| 0.4040403962135315 | 0.009999999776482582 |
| 0.4141414165496826 | 0.009999999776482582 |
| 0.42424243688583374 | 0.009999999776482582 |
| 0.4343434274196625 | 0.009999999776482582 |
| 0.4444444477558136 | 0.009999999776482582 |
| 0.4545454680919647 | 0.009999999776482582 |
| 0.46464645862579346 | 0.009999999776482582 |
| 0.4747474789619446 | 0.009999999776482582 |
| 0.4848484992980957 | 0.009999999776482582 |
| 0.49494948983192444 | 0.009999999776482582 |
| 0.5050504803657532 | 0.009999999776482582 |
| 0.5151515007019043 | 0.009999999776482582 |
| 0.5252525210380554 | 0.009999999776482582 |
| 0.5353535413742065 | 0.009999999776482582 |
| 0.5454545617103577 | 0.009999999776482582 |
| 0.5555555820465088 | 0.009999999776482582 |
| 0.5656565427780151 | 0.009999999776482582 |
| 0.5757575631141663 | 0.009999999776482582 |
| 0.5858585834503174 | 0.009999999776482582 |
| 0.5959596037864685 | 0.009999999776482582 |
| 0.6060606241226196 | 0.009999999776482582 |
| 0.6161616444587708 | 0.009999999776482582 |
| 0.6262626051902771 | 0.009999999776482582 |
| 0.6363636255264282 | 0.009999999776482582 |
| 0.6464646458625793 | 0.009999999776482582 |
| 0.6565656661987305 | 0.009999999776482582 |
| 0.6666666865348816 | 0.009999999776482582 |
| 0.6767676472663879 | 0.009999999776482582 |
| 0.6868686676025391 | 0.009999999776482582 |
| 0.6969696879386902 | 0.009999999776482582 |
| 0.7070707082748413 | 0.009999999776482582 |
| 0.7171717286109924 | 0.009999999776482582 |
| 0.7272727489471436 | 0.009999999776482582 |
| 0.7373737096786499 | 0.009999999776482582 |
| 0.747474730014801 | 0.009999999776482582 |
| 0.7575757503509521 | 0.009999999776482582 |
| 0.7676767706871033 | 0.009999999776482582 |
| 0.7777777910232544 | 0.009999999776482582 |
| 0.7878788113594055 | 0.009999999776482582 |
| 0.7979797720909119 | 0.009999999776482582 |
| 0.808080792427063 | 0.009999999776482582 |
| 0.8181818127632141 | 0.009999999776482582 |
| 0.8282828330993652 | 0.009999999776482582 |
| 0.8383838534355164 | 0.009999999776482582 |
| 0.8484848737716675 | 0.009999999776482582 |
| 0.8585858345031738 | 0.009999999776482582 |
| 0.868686854839325 | 0.009999999776482582 |
| 0.8787878751754761 | 0.009999999776482582 |
| 0.8888888955116272 | 0.009999999776482582 |
| 0.8989899158477783 | 0.009999999776482582 |
| 0.9090909361839294 | 0.009999999776482582 |
| 0.9191918969154358 | 0.009999999776482582 |
| 0.9292929172515869 | 0.009999999776482582 |
| 0.939393937587738 | 0.009999999776482582 |
| 0.9494949579238892 | 0.009999999776482582 |
| 0.9595959782600403 | 0.009999999776482582 |
| 0.9696969985961914 | 0.009999999776482582 |
| 0.9797979593276978 | 0.009999999776482582 |
| 0.9898989796638489 | 0.009999999776482582 |
| 1.0 | 0.009999999776482582 |
+----------------------+-----------------------+
goodman conditionals
This implementation replaces the array above with conditionals. Still the same model as Goodman et al. (2006).
import jax
import jax.numpy as jnp
from memo import memo
from enum import IntEnum
from matplotlib import pyplot as plt
from jax.scipy.stats.beta import pdf as betapdf
= jax.jit(betapdf)
BetaPrior
class World(IntEnum):
= 0
ORIGINAL = 1
NEW
class PerceptualAccess(IntEnum):
= 0
NO = 1
YES
class Action(IntEnum):
= 0
ORIGINAL = 1
NEW
class Belief(IntEnum):
= 0
ORIGINAL = 1
NEW
class Desire(IntEnum):
= 0
OTHER = 1
TOY
= jnp.linspace(0, 1, 20)
Epsilon = jnp.linspace(0, 1, 20)
Gamma
@memo
def goodman_conditionals[_w: World, _v: PerceptualAccess, _a: Action, _b: Belief, _d: Desire]():
child: knows(_w, _v, _a, _b, _d)
child: thinks[
sally: knows(_w, _v),
### epsilon - tends to be < 0.5, indicating that Sally probably wants her toy, but can want something other than her toy with probability epsilon
in Epsilon, wpp=BetaPrior(epsilon, 1, 10)), ### epsilon ~ Beta(1, 10)
sally: given(epsilon in Desire, wpp=(
sally: chooses(d 1 - epsilon if d == {Desire.TOY} ### P(Desire=TOY) = 1 - epsilon
else epsilon ### P(Desire=OTHER) = epsilon
)),
### gamma - tends to be < 0.5, indicating that she is unlikely to change her belief (lacking perceptual access)
in Gamma, wpp=BetaPrior(gamma, 1, 5)), ### epsilon ~ Beta(1, 5)
sally: given(gamma
### what child thinks that sally's belief is (false belief when `b != _w`) ###
in Belief, wpp=(
sally: chooses(b ### if she has perceptual access, her belief about the world is the same as the child's knowledge
== b if _v == {PerceptualAccess.YES}
_w else (
### if she doesn't have perceptual access, Sally can spontaneously change her belief about the location of the toy with probability gamma, but gamma is small, so her original belief (b == 0) tends to persist
1 - gamma if b == {Belief.ORIGINAL} ### P(Belief=ORIGINAL) = 1 - gamma
else gamma ### P(Belief=NEW) = gamma
)
)),
### if Sally wants the toy, she deterministically choose the action that matches her belief.
### if she doesn't want the toy, choose with no preference
in Action, wpp=a == b if d == {Desire.TOY} else 0.5),
sally: chooses(a
]is _a
child: observes [ sally.a ] return child[ Pr[sally.b == _b, sally.d == _d] ]
= goodman_conditionals(print_table=True, return_aux=True, return_pandas=True, return_xarray=True) res
+-----------+----------------------+------------+------------+------------+-----------------------+
| _w: World | _v: PerceptualAccess | _a: Action | _b: Belief | _d: Desire | goodman_conditionals |
+-----------+----------------------+------------+------------+------------+-----------------------+
| ORIGINAL | NO | ORIGINAL | ORIGINAL | OTHER | 0.035531189292669296 |
| ORIGINAL | NO | ORIGINAL | ORIGINAL | TOY | 0.9584101438522339 |
| ORIGINAL | NO | ORIGINAL | NEW | OTHER | 0.006058559753000736 |
| ORIGINAL | NO | ORIGINAL | NEW | TOY | 0.0 |
| ORIGINAL | NO | NEW | ORIGINAL | OTHER | 0.17331279814243317 |
| ORIGINAL | NO | NEW | ORIGINAL | TOY | 0.0 |
| ORIGINAL | NO | NEW | NEW | OTHER | 0.02955223061144352 |
| ORIGINAL | NO | NEW | NEW | TOY | 0.7971349954605103 |
| ORIGINAL | YES | ORIGINAL | ORIGINAL | OTHER | 0.03574776649475098 |
| ORIGINAL | YES | ORIGINAL | ORIGINAL | TOY | 0.9642521142959595 |
| ORIGINAL | YES | ORIGINAL | NEW | OTHER | 0.0 |
| ORIGINAL | YES | ORIGINAL | NEW | TOY | 0.0 |
| ORIGINAL | YES | NEW | ORIGINAL | OTHER | 1.0 |
| ORIGINAL | YES | NEW | ORIGINAL | TOY | 0.0 |
| ORIGINAL | YES | NEW | NEW | OTHER | 0.0 |
| ORIGINAL | YES | NEW | NEW | TOY | 0.0 |
| NEW | NO | ORIGINAL | ORIGINAL | OTHER | 0.035531189292669296 |
| NEW | NO | ORIGINAL | ORIGINAL | TOY | 0.9584101438522339 |
| NEW | NO | ORIGINAL | NEW | OTHER | 0.006058559753000736 |
| NEW | NO | ORIGINAL | NEW | TOY | 0.0 |
| NEW | NO | NEW | ORIGINAL | OTHER | 0.17331279814243317 |
| NEW | NO | NEW | ORIGINAL | TOY | 0.0 |
| NEW | NO | NEW | NEW | OTHER | 0.02955223061144352 |
| NEW | NO | NEW | NEW | TOY | 0.7971349954605103 |
| NEW | YES | ORIGINAL | ORIGINAL | OTHER | 0.0 |
| NEW | YES | ORIGINAL | ORIGINAL | TOY | 0.0 |
| NEW | YES | ORIGINAL | NEW | OTHER | 1.0 |
| NEW | YES | ORIGINAL | NEW | TOY | 0.0 |
| NEW | YES | NEW | ORIGINAL | OTHER | 0.0 |
| NEW | YES | NEW | ORIGINAL | TOY | 0.0 |
| NEW | YES | NEW | NEW | OTHER | 0.03574776649475098 |
| NEW | YES | NEW | NEW | TOY | 0.9642521142959595 |
+-----------+----------------------+------------+------------+------------+-----------------------+
marble
from memo import memo
import jax.numpy as np
import jax
from enum import IntEnum
class Loc(IntEnum):
= 0
BOX = 1
BASKET
class Action(IntEnum):
= 0
ACT_STAY = 1
ACT_MOVE
@jax.jit
def do(l, a):
return np.array([
0, 1],
[1, 0]
[
])[a, l]
class Obs(IntEnum):
= -1
OBS_NONE = 0
OBS_STAY = 1
OBS_MOVE
@memo
def marble[marble_pos_t0: Loc, obs: Obs, where_look: Loc]():
child: knows(marble_pos_t0, obs, where_look)
child: thinks[
sally: knows(marble_pos_t0, where_look),
sally: thinks[
anne: knows(marble_pos_t0),in Action, wpp=0.01 if a=={Action.ACT_MOVE} else 0.99),
anne: chooses(a in Loc, wpp=do(marble_pos_t0, a)==marble_pos_t1),
anne: chooses(marble_pos_t1 in Obs, wpp=1 if o=={Obs.OBS_NONE} or o==a else 0),
anne: chooses(o
],is obs
sally: observes [anne.o]
]return child[ sally[Pr[ anne.marble_pos_t1 == where_look ]] ]
marble()
Array([[[0.99, 0.01],
[1. , 0. ],
[0. , 1. ]],
[[0.01, 0.99],
[0. , 1. ],
[1. , 0. ]]], dtype=float32)
%reset -f
import sys
import platform
import importlib.metadata
print("Python:", sys.version)
print("Platform:", platform.system(), platform.release())
print("Processor:", platform.processor())
print("Machine:", platform.machine())
print("\nPackages:")
for name, version in sorted(
"Name"], dist.version) for dist in importlib.metadata.distributions()),
((dist.metadata[=lambda x: x[0].lower() # Sort case-insensitively
key
):print(f"{name}=={version}")
Python: 3.13.2 (main, Feb 5 2025, 18:58:04) [Clang 19.1.6 ]
Platform: Darwin 23.6.0
Processor: arm
Machine: arm64
Packages:
annotated-types==0.7.0
anyio==4.8.0
appnope==0.1.4
argon2-cffi==23.1.0
argon2-cffi-bindings==21.2.0
arrow==1.3.0
asttokens==3.0.0
async-lru==2.0.4
attrs==25.1.0
babel==2.17.0
beautifulsoup4==4.13.3
bleach==6.2.0
certifi==2025.1.31
cffi==1.17.1
cfgv==3.4.0
charset-normalizer==3.4.1
click==8.1.8
comm==0.2.2
contourpy==1.3.1
cycler==0.12.1
debugpy==1.8.13
decorator==5.2.1
defusedxml==0.7.1
distlib==0.3.9
executing==2.2.0
fastjsonschema==2.21.1
filelock==3.17.0
fonttools==4.56.0
fqdn==1.5.1
h11==0.14.0
httpcore==1.0.7
httpx==0.28.1
identify==2.6.8
idna==3.10
importlib_metadata==8.6.1
ipykernel==6.29.5
ipython==9.0.1
ipython_pygments_lexers==1.1.1
ipywidgets==8.1.5
isoduration==20.11.0
jax==0.5.2
jaxlib==0.5.1
jedi==0.19.2
Jinja2==3.1.6
joblib==1.4.2
json5==0.10.0
jsonpointer==3.0.0
jsonschema==4.23.0
jsonschema-specifications==2024.10.1
jupyter-cache==1.0.1
jupyter-events==0.12.0
jupyter-lsp==2.2.5
jupyter_client==8.6.3
jupyter_core==5.7.2
jupyter_server==2.15.0
jupyter_server_terminals==0.5.3
jupyterlab==4.3.5
jupyterlab_pygments==0.3.0
jupyterlab_server==2.27.3
jupyterlab_widgets==3.0.13
kiwisolver==1.4.8
MarkupSafe==3.0.2
matplotlib==3.10.1
matplotlib-inline==0.1.7
memo-lang==1.1.0
mistune==3.1.2
ml_dtypes==0.5.1
nbclient==0.10.2
nbconvert==7.16.6
nbformat==5.10.4
nest-asyncio==1.6.0
networkx==3.4.2
nodeenv==1.9.1
notebook_shim==0.2.4
numpy==2.2.3
opt_einsum==3.4.0
optype==0.9.1
overrides==7.7.0
packaging==24.2
pandas==2.2.3
pandas-stubs==2.2.3.241126
pandocfilters==1.5.1
parso==0.8.4
pexpect==4.9.0
pillow==11.1.0
platformdirs==4.3.6
plotly==5.24.1
pre_commit==4.1.0
prometheus_client==0.21.1
prompt_toolkit==3.0.50
psutil==7.0.0
ptyprocess==0.7.0
pure_eval==0.2.3
pycparser==2.22
pydantic==2.10.6
pydantic_core==2.27.2
Pygments==2.19.1
pygraphviz==1.14
pyparsing==3.2.1
python-dateutil==2.9.0.post0
python-dotenv==1.0.1
python-json-logger==3.3.0
pytz==2025.1
PyYAML==6.0.2
pyzmq==26.2.1
referencing==0.36.2
requests==2.32.3
rfc3339-validator==0.1.4
rfc3986-validator==0.1.1
rpds-py==0.23.1
ruff==0.9.10
scikit-learn==1.6.1
scipy==1.15.2
scipy-stubs==1.15.2.0
seaborn==0.13.2
Send2Trash==1.8.3
setuptools==75.8.2
six==1.17.0
sniffio==1.3.1
soupsieve==2.6
SQLAlchemy==2.0.38
stack-data==0.6.3
tabulate==0.9.0
tenacity==9.0.0
terminado==0.18.1
threadpoolctl==3.5.0
tinycss2==1.4.0
toml==0.10.2
tornado==6.4.2
tqdm==4.67.1
traitlets==5.14.3
types-python-dateutil==2.9.0.20241206
types-pytz==2025.1.0.20250204
typing_extensions==4.12.2
tzdata==2025.1
uri-template==1.3.0
urllib3==2.3.0
virtualenv==20.29.3
wcwidth==0.2.13
webcolors==24.11.1
webencodings==0.5.1
websocket-client==1.8.0
widgetsnbextension==4.0.13
xarray==2025.1.2
zipp==3.21.0