| 213 | |
| 214 | |
| 215 | auto register_table_bindings(py::module_& module) -> void |
| 216 | { |
| 217 | module.def( |
| 218 | "calc_dd_table", |
| 219 | [](const py::dict& table_deal) { |
| 220 | DdTableResults table_results{}; |
| 221 | const DdTableDeal native_deal = dds3_python::dict_to_dd_table_deal(table_deal); |
| 222 | int code = RETURN_NO_FAULT; |
| 223 | { |
| 224 | py::gil_scoped_release release; |
| 225 | code = CalcDDtable(native_deal, &table_results); |
| 226 | } |
| 227 | throw_on_dds_error(code); |
| 228 | return dds3_python::dd_table_results_to_dict(table_results); |
| 229 | }, |
| 230 | py::arg("table_deal"), |
| 231 | "Calculate the double-dummy table for all contracts and strains.\n\n" |
| 232 | "Args:\n" |
| 233 | " table_deal (dict): DD table deal dict with key 'cards' (4x4 nested list).\n\n" |
| 234 | "Returns:\n" |
| 235 | " dict: Double-dummy table with key 'res_table' (5x4 nested list).\n" |
| 236 | " res_table[strain][hand] = tricks available for that strain/hand.\n\n" |
| 237 | "Raises:\n" |
| 238 | " ValueError: If input validation fails (invalid card distribution).\n" |
| 239 | " RuntimeError: If DDS solver returns error code."); |
| 240 | |
| 241 | module.def( |
| 242 | "calc_all_tables_pbn", |
| 243 | [](const py::list& deals_pbn, const int mode, const py::sequence& trump_filter) { |
| 244 | // Validate mode parameter |
| 245 | if (mode < -1 || mode > 3) { |
| 246 | throw py::value_error( |
| 247 | "mode has invalid value " + std::to_string(mode) + |
| 248 | " (expected -1=disabled, 0=none, 1=both, 2=NS, 3=EW)"); |
| 249 | } |
| 250 | |
| 251 | // Validate and convert trump_filter |
| 252 | const auto trump_filter_vec = dds3_python::sequence_to_bounded_int_vector( |
| 253 | trump_filter, |
| 254 | DDS_STRAINS, |
| 255 | 0, |
| 256 | 1, |
| 257 | "trump_filter"); |
| 258 | |
| 259 | const int included_strains = static_cast<int>(std::count( |
| 260 | trump_filter_vec.begin(), |
| 261 | trump_filter_vec.end(), |
| 262 | 0)); |
| 263 | |
| 264 | // Par computation constraints: |
| 265 | // - DDS only populates par results when ALL strains are included (see DDS CalcAllTables) |
| 266 | // - AllParResults::par_results has fixed capacity MAXNOOFTABLES (not MAXNOOFTABLES*DDS_STRAINS) |
| 267 | // - To ensure safe access, we either: |
| 268 | // (a) Reject par computation (mode != -1) unless all strains are included, OR |
| 269 | // (b) Cap max_tables to MAXNOOFTABLES when par is requested with all strains |
| 270 | // This implements approach (a): reject invalid combinations and approach (b): cap appropriately. |
| 271 | |
| 272 | const bool wants_par = mode != -1; |
no test coverage detected