Runs are one at a time now — a second is refused while a job this server started is going,
because two CASINO runs sharing the cores make Total CASINO CPU time, and the efficiency
computed from it, stop meaning what they say. And a restart no longer has to destroy the
answer of the run before it, which matters more now that rerunning in place is the ordinary
way to work.
Behind both, the corrections that go with them: casino_results for a job whose directory had
been rerun was answering with the next run's numbers, and three of the tools that act had no
output schema at all — which is how JobState.binary declared the wrong type for five
releases without anything noticing.
pip install casino-mcpOn PyPI: https://pypi.org/project/casino-mcp/0.6.0/
Added
casino_wait(job_id, timeout)(casino-mcp wait): block until a running calculation
ends, and answer with the state it ended in, pluswaitedandtimed_out. A chain is then
run, waited for and read back without a polling loop -- what a campaign wrote by hand was
while pgrep -x casino; do sleep 5; done, or a status call every few seconds, each one a
round trip through the model. The wait is bounded (600 s by default,wait_timeoutin
casino-mcp config) because a stdio server answers one call at a time, so a wait is the
whole control plane standing still; a caller that wants longer calls again. Waiting is not
stopping: nothing is signalled and the calculation is not touched.- Every tool that takes a job takes a calculation directory instead --
casino_status,
casino_wait,casino_results,casino_stop-- and it means the newest job that ran there.
A campaign holds directories: the scan's own loop variable is the directory it prepared, and
the job id of the run in it is a string the caller never saw, which is why reading results
back meant greppingoutrather than asking for them. The registry answers the question, so
nothing is written into the calculation directory to record it. casino_results(job_id, fields=[...])(casino-mcp results -f vmc.efficiency): answer
with just the paths asked for, as one flat{path: value}. A parsed run is 10-16 kB of JSON
and a scan over 38 directories wanting six numbers from each does not want 600 kB of it --
which is why the campaign that prompted this greppedoutinstead of calling the tool. A
path is written in the run's own keys, with four rules:vmc/opt/dmc_equil/
dmc_statsare the last phase of that kind,opt[3]andvmc[2]the cycle CASINO itself
numbered,phases[-1]andphases[0]a position, and anything else is a key --
keywords.DTVMC,cpu_time,result.energy,vmc.energy.error,vmc.blocks[0].time,
status. A path that lands on a measured value collapses to the number. A path that does not
exist is a mistake in the question and comes back inproblemsnaming what is there instead;
a path that exists but holds a number CASINO never printed comes back as null with its reason
-- the two are different things and the projection keeps them apart. The same selector is what
a scan tool will project its table with, rather than a second one beside it.casino_input(job_id)(casino-mcp input): the keywords and%blocks a calculation was
given, as data.resultsanswers what a run did and this answers what it was told to do, and
the two are not the same reading: thekeywordsin a result are CASINO's own echo, which
holds every default it applied -- 70 entries against the 23 a file typically sets -- and drops
what it does not print.random_seedis one of those, and it is the keyword the question
"can this number be reproduced" turns on. It reads a directory nothing has run in, which is
how a prepared calculation is checked before there is a job to name it by, and for a job that
casino_stop halted it also returnsbefore_halt: theinputthe run was started from, kept
in the registry becausehaltqmc -urewrites the file in place and the rewrite is lossy.casino_list_jobs(limit, workdir)filters to one directory: what a chain of runs did in one
place, and the way to see that a directory has been run twice.- Runs are one at a time, and
casino_run(allow_concurrent=true)is how they are not. A
second run is refused while a job this server started is going -- not because the machine is
busy, but because of what sharing it does to the numbers:Total CASINO CPU timecounts CPU
seconds across the MPI processes, and two jobs that landed on the same core measured 97.2 s
of CPU against 194.41 s of real time, withefficiencywrong by the same factor and nothing
in the output saying so. The reply on the override names what it runs beside, under
concurrent. A job running in the same directory is refused with no override at all --
one directory is one calculation -- and that check is asked before the.runqmc.lockone,
which would otherwise offerunlock=trueand turn the caller loose on a live calculation.
Only jobs this server started are known: apgrep casinoover the machine is deliberately
not done, because "something is computing" with no owner and no job id is a refusal the
caller has nothing to answer with. So readingcpu_timeagainstreal_timeafterwards is
not a backstop but the check that holds, and it is one
casino_results(fields=['cpu_time', 'real_time']). casino_run(restart=true, keep_previous=true)(casino-mcp run --keep-previous) moves
outto the first freeout.1,out.2, ... instead of deleting it; the rest of what
restartdeletes goes as before, and the reply says where it went, underkept. The
numbering ends up being the order the runs happened in. The archives are deliberately not in
DEBRIS, and are not namedout_part.N-- that name is runqmc's own, for the segments of a
--continue, and it is inDEBRIS. Refused withoutrestart: nothing is being deleted
then. What this is really for is thecasino_resultsfix below.
Changed
- The job index records
created_epochbesidecreated. A job id is only chronological to the
second, and two runs of one directory a second apart -- which is what a scan does -- cannot be
told apart by it. A record written before this falls back to its id, which is all it has. casino_resultssays what it returns. Its description named energies and per-block
numbers and nothing else, while the parser had been returningacceptance,
correlation_time,efficiency, the optimizeddtvmc,steps_per_processand the whole
statistical-efficiency section all along — so the way to those numbers was to grepoutfor
them. The docstring now lists what a phase holds, by kind, and a test holds it to the parser.
Fixed
casino_resultsfor a job whose directory has been rerun answered with the wrong run's
numbers. A job record held a directory, not a file, and the output was found in it by the
nameout-- which holds for exactly as long as one run has happened there. After a restart
in place, the earlier job answered with the physics of the run that came after it, under its
own id, with its own status and its own start time, and nothing in the reply saying so. Job
records now name the file,casino_statusreports it asout, andkeep_previousmoves the
record along with the file, so each job goes on answering with what it produced. Records
written before this read asout, which is what every one of them wrote.adviseno longer tells aruntype : vmcinput that CASINO will not read itsopt_dtvmc.
unusedplaced a keyword by its prefix, andopt_dtvmcis named like an optimisation
keyword and is a VMC one — the VMC phase's own time-step optimisation. Which phase a keyword
belongs to isSECTIONSnow; a keyword the table does not know is still placed by its prefix.- The trap that is real is the other way round, and
advisenow warns about it:dtvmcset
whileopt_dtvmcis not 0 means CASINO takes the value as a starting point and optimizes the
step away from it, so a scan overdtvmcmeasures one step in every directory. casino_status,casino_waitandcasino_list_jobsanswer again. All three return the
JobStatefamily, andJobState.binarydeclared astrwhilebinary_stamp()has answered
with a dict —path,exists,size,mtime— since the initial commit, so every one of
them failed at serialization withbinary: Input should be a valid string, after the
calculation had already run. The stamp now has a model of its own,BinaryStamp, which is
also the honest name for it: not which binary, but which build of it, the thing that tells
results from before and after a rebuild apart. Nothing had caught this in five releases
because the field was the one place the model named what the runtime returns and named it
wrongly —extra='allow'waves every unnamed key through untouched — whilecasino_run
declares a plain dict and hands the same stamp back correctly, andcasino_resultsnever
fills the field in. Guarded now by a unit test that puts a realbinary_stamp(), in both the
shapes it has, throughJobState; the tests that stub the runtime out call the tool as a
plain function and never reach the output model at all.- The three tools that act rather than read now have an output schema too.
casino_run,
casino_prepareandcasino_stopwere declared-> dict[str, Any], so what reached the
model was{"type": "object", "additionalProperties": true}: not one field name, not one
unit, and nothing a test could compare against what the runtime actually answers. That is the
holebinarylived in for five releases — a reply nothing describes is a reply nothing can
check. They returnStarted,PreparedandStoppednow, withBinaryStamp,
CorrelationData,GeminalPlan,TerminatedandHaltunder them, so a caller reading a
stop can see thatterminated.scopesays whether the ranks or the whole process group were
signalled and thathalt.updated_inputis what decides whether the run can be continued.
Guarded by a test per tool that puts a real call's answer through the model that declares it,
and by three that hold the module rather than a list: every model in it describes its fields,
every tool answers with one of them, and every one of them is reached by a tool. The list
this replaces named five of the fifteen models, so nothing held the ones added after it was
written — which is the same waybinarysurvived. What is guarded now is the annotation, not
the three tools that happened to have the wrong one. commandmeans one thing again.casino_runanswered with' '.join(command)while
casino_statusanswered with the argv under the same key, so the same name on the same
surface had two types. It is the argv everywhere now, incasino_runand in thehalt
report: the exact arguments, which a joined string cannot be parsed back into.casino_prepareno longer sendswrote: nullalongside a refusal. It was there to say
nothing had been written, which is what the refusal says.parse_outno longer raises on anoutwith no phase in it.split_phasespaired each
phase with the one after it understrict=True, and with no phase there was still a
sentinel to pair against, so a file that never reached VMC — a run that errstopped on a
missinggwfn.data, say — came back asValueError: zip() argument 2 is longer than argument 1out ofcasino_resultsinstead of as a parsed file. Which is the reading that
matters most there: it is the run whose failure has to be looked at.block_boundshad the
same shape for a phase cut off before it finished a block. Both answer with an empty list
now, and the rest of the file —Started, the messages CASINO printed — is read as ever.