PYOSolver.py is a Python wrapper library used for interacting with PioSOLVER. PioSOLVER provides a text interface (UPI) to interact with the solver, and PYOSolver.py lets you drive it from Python scripts — more complex workflows than PioViewer natively supports. Documentation for the UPI commands is here.
Works with PioSOLVER 2.x and 3.x, including 3.10 (see the 3.10 notes below — some behaviors changed in ways that silently break older scripts).
>>> from pyosolver import PYOSolver
>>> solver = PYOSolver("C:\\PioSOLVER", "PioSOLVER3-edge.exe")
>>> solver.load_tree("C:\\PioSOLVER\\saves\\AhKs9d.cfr")
>>> solver.show_node("r:0")
Node(r:0, OOP_DEC, ('Ah', 'Ks', '9d'), (0, 0, 100), 2, ('PIO_ALG',))For a more complex example, see analytics.py. If you have read through the UPI documentation and still need help understanding how the commands work, enable logs in the PioSOLVER settings, do stuff in PioViewer and see which commands it runs.
These were all verified empirically against PioSOLVER-edge 3.10.1; the wrapper accounts for them, but your scripts need to know:
-
set_strategysilently doesn't stick on default trees. Pio 3's default tree algorithm (v4_small) derives strategies from regrets, soset_strategyanswersok!but reads, solves, and saves keep the solver's own strategy. Before anyset_strategy/lock_node/ node-locking workflow, convert the tree:solver.load_tree(path) solver.load_all_nodes() solver.set_algorithm("original_pio") solver.rebuild_tree() # may need a lot of RAM on big trees # now set_strategy / lock_node round-trip exactly
-
eliminate_path+solve_partialcan crash the solver process on unconverted (v4_small) trees. The sameoriginal_pioconversion makes the sequence stable. -
rebuild_forgotten_streetswas removed in 3.10 — userebuild_tree()(requiresload_all_nodes()first). The method is kept for older solvers. -
Saves are compressed by default since 3.10, quantizing strategies (precision 11). If you need exact frequencies preserved (e.g. after node locking), dump with
no_compress:solver._run("dump_tree", f'"{path}"', "no_compress")
-
Trees are fast-loaded: many mutating commands error with
ERROR: ... incomplete treeuntil you callload_all_nodes(). -
set_always_recalc <street> <ms>— the first argument is a street selector (3=flop, 4=turn, 5=river),0disables. The wrapper no longer enables it at startup: with auto-recalc on, reading a node re-solves it, which clobbers strategies you just set.
-
SolverDiedexception — raised instead of hanging forever when the solver process dies mid-command (a dead process otherwise makesreadline()block or spin indefinitely). -
go(steps, units)— runs the solver and captures output until it stops; returns immediately (with the error text) if the solver rejects the command instead of waiting for output that will never come. -
go_nowait()— starts solving and returns right aftergo ok!. Pair withstop()+wait_for_solver()for a bounded, version-independent timed solve:solver.set_info_freq(10_000_000) # quiet progress spam solver.go_nowait() time.sleep(seconds) solver.stop() solver.wait_for_solver()
-
set_algorithm,rebuild_tree,set_info_freq,update_strategy(per-combo strategy edits),calc_results,show_categories, rake helpers, and jesolver output-format support. -
attach_external_io()— optional line-bus integration for apps that relay solver output elsewhere (capture vs stream modes).
Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.
lol