This is a static, read-only snapshot — sliders and other controls will not respond.
Back to notebook list
·
Run it yourself
%23%20%2F%2F%2F%20script%0A%23%20%5Btool.marimo.opengraph%5D%0A%23%20title%20%3D%20%22Sudoku%20Solving%22%0A%23%20description%20%3D%20%22Feasibility%20Problem%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-sudoku.svg%22%0A%23%20%2F%2F%2F%0A%0Aimport%20marimo%0A%0A__generated_with%20%3D%20%220.24.0%22%0Aapp%20%3D%20marimo.App()%0A%0A%0A%40app.cell%0Adef%20_()%3A%0A%20%20%20%20import%20marimo%20as%20mo%0A%20%20%20%20import%20xpress%20as%20xp%0A%20%20%20%20import%20numpy%20as%20np%0A%20%20%20%20import%20matplotlib.pyplot%20as%20plt%0A%20%20%20%20import%20matplotlib.ticker%20as%20ticker%0A%20%20%20%20import%20math%0A%0A%20%20%20%20return%20math%2C%20mo%2C%20np%2C%20plt%2C%20ticker%2C%20xp%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20def%20license_error_callout(error)%3A%0A%20%20%20%20%20%20%20%20%23%20Render%20a%20clear%2C%20copyable%20error%20message%20as%20normal%20cell%20output%20instead%0A%20%20%20%20%20%20%20%20%23%20of%20letting%20the%20SolverError%20surface%20as%20marimo's%20generic%20%22see%20console%22%0A%20%20%20%20%20%20%20%20%23%20error%20popup%2C%20which%20is%20unhelpful%20(and%20there%20may%20be%20no%20visible%20console%0A%20%20%20%20%20%20%20%20%23%20at%20all%20when%20running%20locally%20with%20%60marimo%20run%60).%0A%20%20%20%20%20%20%20%20return%20mo.callout(mo.md(f%22%22%22%0A%20%20%20%20%20%20%20%20**This%20instance%20could%20not%20be%20solved%3A**%0A%0A%20%20%20%20%20%20%20%20%60%60%60%0A%20%20%20%20%20%20%20%20%7Berror%7D%0A%20%20%20%20%20%20%20%20%60%60%60%0A%0A%20%20%20%20%20%20%20%20This%20is%20due%20to%20the%20Community%20license's%20combined%20rows-plus-columns%20limit%20of%205000%20being%20exceeded%20-%20the%2016x16%20grid%20alone%20requires%205120.%20Try%20the%204x4%20or%209x9%20grid%20size%20instead%2C%20or%20use%20a%20full%20Xpress%20license%20to%20solve%20the%2016x16%20variant.%0A%20%20%20%20%20%20%20%20%22%22%22)%2C%20kind%3D%22danger%22)%0A%0A%20%20%20%20return%20(license_error_callout%2C)%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%20**Solving%20a%20Sudoku%20problem**%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20Sudoku%3A%20place%20numbers%20from%201%20to%20%24n%24%20into%20a%20%24n%20%5Ctimes%20n%24%20grid%20such%20that%20no%20number%20repeats%20in%20any%20row%2C%20column%2C%20or%20%24q%20%5Ctimes%20q%24%20sub-grid.%0A%0A%20%20%20%20%26copy%3B%20Copyright%202025-2026%20Fair%20Isaac%20Corporation.%20The%20use%20of%20this%20example%20is%20subject%20to%20%5Blegal%20and%20license%20requirements%5D(https%3A%2F%2Fgithub.com%2Ffico-xpress%2Fpython-notebooks%23legal-and-license-requirements).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Problem%20description%20and%20formulation%0A%0A%20%20%20%20In%20classic%20sudoku%2C%20the%20objective%20is%20to%20fill%20a%20%24n%20%5Ctimes%20n%24%20grid%20with%20digits%20so%20that%20each%20column%2C%20each%20row%2C%20and%20each%20of%20the%20%24q%20%5Ctimes%20q%24%20subgrids%20that%20compose%20the%20grid%20(also%20called%20%22boxes%22%2C%20%22blocks%22%2C%20or%20%22regions%22)%20contains%20all%20of%20the%20digits%20from%201%20to%20%24n%24.%20The%20puzzle%20setter%20provides%20a%20partially%20completed%20grid%2C%20which%20for%20a%20well-posed%20puzzle%20has%20a%20single%20solution.%0A%0A%20%20%20%20Several%20formulations%20exist%20for%20this%20problem%2C%20where%20choosing%20the%20right%20variables%20is%20a%20fundamental%20step.%20Although%20all%20cells%20must%20contain%20integer%20numbers%2C%20using%20integer%20decision%20variables%20would%20make%20it%20hard%20to%20guarantee%20that%20they%20are%20different%20within%20a%20given%20block%20(row%2Fcolumn)%20with%20a%20mathematical%20programming%20formulation.%0A%0A%20%20%20%20In%20this%20example%2C%20we%20use%20binary%20variables%20%24assign_%7Bi%2Cj%2Ck%7D%24%20that%20indicate%20whether%20a%20value%20%24k%20%5Cin%20%5C%7B1%2C..%2Cn%5C%7D%24%20is%20assigned%20to%20a%20given%20cell%20%24i%2Cj%20%5Cin%20%5Cmathcal%7BN%7D%24%20of%20the%20grid%20(%3D1)%20or%20not%20(%3D0).%20Also%2C%20no%20objective%20function%20is%20needed%3A%20this%20is%20a%20**feasibility**%20problem%20not%20an%20**optimization**%20problem%2C%20subject%20to%20the%20following%20constraints%3A%0A%0A%20%20%20%20*%20Each%20cell%20can%20only%20have%20one%20value%3A%0A%20%20%20%20%24%24%5Csum_%7Bk%20%5Cin%20%5Cmathcal%7BN%7D%7D%20assign_%7Bi%2Cj%2Ck%7D%20%3D%201%2C%20%5Cqquad%20%5Cforall%20i%2Cj%20%5Cin%20%5Cmathcal%7BN%7D%24%24%0A%0A%20%20%20%20*%20Assign%20values%20already%20in%20grid%20(%24g_%7Bi%2Cj%7D%24%20has%20a%20positive%20value)%3A%0A%20%20%20%20%24%24assign_%7Bi%2Cj%2Ck%7D%20%3D%201%2C%20%5Cqquad%20%5Cforall%20i%2Cj%20%5Cin%20%5Cmathcal%7BN%7D%2C%20k%20%3D%20g_%7Bi%2Cj%7D%2C%20g_%7Bi%2Cj%7D%20%3E%200%24%24%0A%0A%20%20%20%20*%20Every%20number%20must%20appear%20once%20on%20every%20row%3A%0A%20%20%20%20%24%24%5Csum_%7Bj%20%5Cin%20%5Cmathcal%7BN%7D%7D%20assign_%7Bi%2Cj%2Ck%7D%20%3D%201%2C%20%5Cqquad%20%5Cforall%20i%2Ck%20%5Cin%20%5Cmathcal%7BN%7D%24%24%0A%0A%20%20%20%20*%20Every%20number%20must%20appear%20once%20on%20every%20column%3A%0A%20%20%20%20%24%24%5Csum_%7Bi%20%5Cin%20%5Cmathcal%7BN%7D%7D%20assign_%7Bi%2Cj%2Ck%7D%20%3D%201%2C%20%5Cqquad%20%5Cforall%20j%2Ck%20%5Cin%20%5Cmathcal%7BN%7D%24%24%0A%0A%20%20%20%20*%20Every%20number%20must%20appear%20once%20in%20every%20%24q%20%5Ctimes%20q%24%20block%3A%0A%20%20%20%20%24%24%5Csum_%7Bi%2Cj%20%5Cin%20%5Cmathcal%7BQ%7D%3A%20n%20%3D%20i%2Bq.h%2C%20m%20%3D%20j%2Bq.l%7D%20assign_%7Bn%2Cm%2Ck%7D%20%3D%201%2C%20%5Cqquad%20%5Cforall%20h%2Cl%20%5Cin%20%5Cmathcal%7BQ%7D%2C%20%5Cforall%20k%20%5Cin%20%5Cmathcal%7BN%7D%24%24%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Data%20preparation%0A%0A%20%20%20%20The%20input%20is%20a%20starting%20grid%20where%20the%20unknown%20numbers%20are%20replaced%20by%20zero.%20You%20can%20adjust%20the%20parameter%20below%20to%20switch%20between%20the%20%244%20%5Ctimes%204%24%2C%20%249%20%5Ctimes%209%24%20(classic)%2C%20and%20%2416%20%5Ctimes%2016%24%20variants.%20The%20%2416%20%5Ctimes%2016%24%20variant%20creates%20a%20much%20larger%20model%20and%20**requires%20a%20full%20Xpress%20license**%20-%20it%20will%20not%20solve%20under%20a%20Community%20license.%20The%20starting%20grid%20is%20shown%20below.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo)%3A%0A%20%20%20%20grid_selector%20%3D%20mo.ui.radio(%0A%20%20%20%20%20%20%20%20options%3D%7B%224x4%20(small)%22%3A%20%224x4%22%2C%20%229x9%20(classic)%22%3A%20%229x9%22%2C%20%2216x16%20(variant%2C%20requires%20full%20Xpress%20license)%22%3A%20%2216x16%22%7D%2C%0A%20%20%20%20%20%20%20%20value%3D%229x9%20(classic)%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Grid%20size%22%0A%20%20%20%20)%0A%20%20%20%20grid_selector%0A%20%20%20%20return%20(grid_selector%2C)%0A%0A%0A%40app.cell%0Adef%20_(grid_selector%2C%20mo)%3A%0A%20%20%20%20grid2x2%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5B1%2C%200%2C%200%2C%204%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%204%2C%201%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%201%2C%204%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B4%2C%200%2C%200%2C%201%5D%0A%20%20%20%20%5D%0A%20%20%20%20grid3x3%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5B8%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%203%2C%206%2C%200%2C%200%2C%200%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%207%2C%200%2C%200%2C%209%2C%200%2C%202%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%205%2C%200%2C%200%2C%200%2C%207%2C%200%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%200%2C%200%2C%204%2C%205%2C%207%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%200%2C%201%2C%200%2C%200%2C%200%2C%203%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%201%2C%200%2C%200%2C%200%2C%200%2C%206%2C%208%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%208%2C%205%2C%200%2C%200%2C%200%2C%201%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%209%2C%200%2C%200%2C%200%2C%200%2C%204%2C%200%2C%200%5D%0A%20%20%20%20%5D%0A%20%20%20%20grid4x4%20%3D%20%5B%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%2012%2C%200%2C%200%2C%202%2C%200%2C%200%2C%200%2C%207%2C%203%2C%200%2C%2013%2C%2015%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B15%2C%200%2C%200%2C%200%2C%200%2C%203%2C%200%2C%200%2C%209%2C%200%2C%200%2C%200%2C%2012%2C%200%2C%200%2C%2010%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%200%2C%200%2C%209%2C%200%2C%206%2C%200%2C%200%2C%200%2C%2012%2C%200%2C%200%2C%200%2C%202%2C%205%5D%2C%0A%20%20%20%20%20%20%20%20%5B6%2C%2011%2C%201%2C%200%2C%200%2C%2010%2C%205%2C%200%2C%200%2C%202%2C%200%2C%2015%2C%200%2C%200%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B4%2C%206%2C%203%2C%200%2C%200%2C%200%2C%2013%2C%2014%2C%200%2C%200%2C%200%2C%200%2C%200%2C%207%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%2015%2C%2011%2C%200%2C%207%2C%200%2C%209%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%2C%201%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%201%2C%200%2C%2010%2C%2015%2C%200%2C%200%2C%200%2C%2011%2C%203%2C%2014%2C%200%2C%206%2C%200%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B13%2C%200%2C%208%2C%207%2C%200%2C%205%2C%200%2C%200%2C%200%2C%201%2C%209%2C%2012%2C%200%2C%200%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%200%2C%206%2C%203%2C%207%2C%2015%2C%204%2C%200%2C%200%2C%200%2C%200%2C%200%2C%2014%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%208%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%2C%2011%2C%207%2C%200%2C%204%2C%200%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%2C%200%2C%2013%2C%200%2C%200%2C%206%2C%209%2C%200%2C%203%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%200%2C%200%2C%202%2C%208%2C%2014%2C%200%2C%203%2C%200%2C%200%2C%2010%2C%200%2C%200%2C%2013%2C%207%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%200%2C%200%2C%208%2C%200%2C%200%2C%200%2C%207%2C%2010%2C%200%2C%200%2C%200%2C%200%2C%200%2C%205%2C%201%5D%2C%0A%20%20%20%20%20%20%20%20%5B0%2C%204%2C%2010%2C%201%2C%206%2C%200%2C%200%2C%200%2C%200%2C%2012%2C%200%2C%2014%2C%207%2C%203%2C%209%2C%2015%5D%2C%0A%20%20%20%20%20%20%20%20%5B3%2C%200%2C%2015%2C%200%2C%200%2C%200%2C%200%2C%208%2C%200%2C%200%2C%201%2C%200%2C%2014%2C%2012%2C%200%2C%200%5D%2C%0A%20%20%20%20%20%20%20%20%5B2%2C%200%2C%200%2C%209%2C%2012%2C%200%2C%200%2C%201%2C%200%2C%200%2C%200%2C%200%2C%200%2C%206%2C%208%2C%200%5D%0A%20%20%20%20%5D%0A%20%20%20%20if%20grid_selector.value%20%3D%3D%20%224x4%22%3A%0A%20%20%20%20%20%20%20%20grid%20%3D%20grid2x2%0A%20%20%20%20%20%20%20%20q%20%3D%202%0A%20%20%20%20elif%20grid_selector.value%20%3D%3D%20%229x9%22%3A%0A%20%20%20%20%20%20%20%20grid%20%3D%20grid3x3%0A%20%20%20%20%20%20%20%20q%20%3D%203%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20grid%20%3D%20grid4x4%0A%20%20%20%20%20%20%20%20q%20%3D%204%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%20grid%2C%20q%0A%0A%0A%40app.cell%0Adef%20_(grid%2C%20np%2C%20plt%2C%20q%2C%20ticker)%3A%0A%20%20%20%20encode_start%20%3D%20%7B1%3A%20'1'%2C%202%3A%20'2'%2C%203%3A%20'3'%2C%204%3A%20'4'%2C%205%3A%20'5'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%206%3A%20'6'%2C%207%3A%20'7'%2C%208%3A%20'8'%2C%209%3A%20'9'%2C%2010%3A%20'A'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2011%3A%20'B'%2C%2012%3A%20'C'%2C%2013%3A%20'D'%2C%2014%3A%20'E'%2C%2015%3A%20'F'%2C%2016%3A%20'G'%7D%0A%20%20%20%20n_start%20%3D%20q**2%0A%20%20%20%20fig_start%2C%20ax_start%20%3D%20plt.subplots(figsize%3D(max(6%2C%20n_start%20%2F%2F%202)%2C%20max(6%2C%20n_start%20%2F%2F%202)))%0A%20%20%20%20min_val_start%2C%20max_val_start%2C%20diff_start%20%3D%200%2C%20n_start%2C%201%0A%20%20%20%20for%20i_start%20in%20range(n_start)%3A%0A%20%20%20%20%20%20%20%20for%20j_start%20in%20range(n_start)%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20value%20%3D%20grid%5Bi_start%5D%5Bj_start%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20value%20%3E%200%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax_start.text(j_start%2C%20n_start%20-%201%20-%20i_start%2C%20encode_start%5Bvalue%5D%2C%20va%3D'center'%2C%20ha%3D'center')%0A%20%20%20%20ax_start.set_aspect('equal'%2C%20'box')%0A%20%20%20%20ax_start.set_xlim(min_val_start%20-%20diff_start%20%2F%202%2C%20max_val_start%20-%20diff_start%20%2F%202)%0A%20%20%20%20ax_start.set_ylim(min_val_start%20-%20diff_start%20%2F%202%2C%20max_val_start%20-%20diff_start%20%2F%202)%0A%20%20%20%20ax_start.set_xticklabels(%5B%5D)%0A%20%20%20%20ax_start.set_yticklabels(%5B%5D)%0A%20%20%20%20ax_start.yaxis.set_minor_locator(ticker.FixedLocator(np.arange(-0.5%2C%20q%20*%20q%2C%201)))%0A%20%20%20%20ax_start.xaxis.set_minor_locator(ticker.FixedLocator(np.arange(-0.5%2C%20q%20*%20q%2C%201)))%0A%20%20%20%20ax_start.xaxis.set_major_locator(ticker.FixedLocator(np.arange(-0.5%2C%20q%20*%20q%2C%20q)))%0A%20%20%20%20ax_start.yaxis.set_major_locator(ticker.FixedLocator(np.arange(-0.5%2C%20q%20*%20q%2C%20q)))%0A%20%20%20%20ax_start.grid(which%3D'minor')%0A%20%20%20%20ax_start.grid(which%3D'major'%2C%20color%3D'black')%0A%20%20%20%20fig_start%0A%20%20%20%20return%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Model%20implementation%20and%20results%0A%0A%20%20%20%20When%20passing%20sets%2C%20lists%2C%20or%20range%20objects%20to%20%5Bprob.addVariables()%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2Fproblem.addVariables.html)%2C%20the%20result%20is%20a%20Python%20dictionary%20of%20variables%2C%20whose%20keys%20are%20tuples%20of%20indices.%20Variables%20%24assign%24%20are%20created%20this%20way.%0A%0A%20%20%20%20The%20constraints%20are%20then%20created%20and%20added%20to%20the%20problem%20directly%20by%20passing%20the%20corresponding%20expression%20as%20a%20list%20comprehension.%20Note%20that%20no%20objective%20is%20set%20as%20this%20is%20a%20feasibility%20problem.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(grid%2C%20license_error_callout%2C%20mo%2C%20q%2C%20xp)%3A%0A%20%20%20%20%23%20Main%20dimensions%20of%20the%20problem%3A%20q%20is%20the%20size%20of%20the%20qxq%20block%20(3x3%20in%20the%20classic%20Sudoku%20game).%0A%0A%20%20%20%20n%20%3D%20q**2%20%20%20%20%20%20%20%23%20the%20size%20must%20be%20the%20square%20of%20the%20size%20of%20the%20subgrids%0A%20%20%20%20N%20%3D%20range(n)%20%20%20%23%20set%20of%20numbers%20from%200%20to%20n-1%0A%20%20%20%20Q%20%3D%20range(q)%20%20%20%23%20set%20of%20numbers%20from%200%20to%20q-1%0A%0A%20%20%20%20%23%20Create%20a%20model%0A%20%20%20%20prob%20%3D%20xp.problem()%0A%0A%20%20%20%20assign%20%3D%20prob.addVariables(N%2C%20N%2C%20N%2C%20vartype%3Dxp.binary)%0A%0A%20%20%20%20%23%20Constraint%201%3A%20each%20cell%20can%20only%20have%20one%20value%0A%20%20%20%20prob.addConstraint(xp.Sum(assign%5Bi%2Cj%2Ck%5D%20for%20k%20in%20N)%20%3D%3D%201%20for%20i%20in%20N%20for%20j%20in%20N)%0A%0A%20%20%20%20%23%20Constraint%202%3A%20fix%20the%20cells%20in%20the%20starting%20grid%0A%20%20%20%20prob.addConstraint(assign%5Bi%2Cj%2Cgrid%5Bi%5D%5Bj%5D%20-%201%5D%20%3D%3D%201%20for%20i%20in%20N%20for%20j%20in%20N%20if%20grid%5Bi%5D%5Bj%5D%20%3E%200)%0A%0A%20%20%20%20%23%20Constraint%203a%3A%20Every%20number%20must%20appear%20once%20on%20every%20row%0A%20%20%20%20prob.addConstraint(xp.Sum(assign%5Bi%2Cj%2Ck%5D%20for%20j%20in%20N)%20%3D%3D%201%20for%20i%20in%20N%20for%20k%20in%20N)%0A%0A%20%20%20%20%23%20Constraint%203b%3A%20...%20and%20on%20every%20column%0A%20%20%20%20prob.addConstraint(xp.Sum(assign%5Bi%2Cj%2Ck%5D%20for%20i%20in%20N)%20%3D%3D%201%20for%20j%20in%20N%20for%20k%20in%20N)%0A%0A%20%20%20%20%23%20Constraint%203c%3A%20Every%20number%20must%20appear%20once%20in%20every%20qxq%20block%0A%20%20%20%20prob.addConstraint(xp.Sum(assign%5Bi%2Bq*h%2Cj%2Bq*l%2Ck%5D%20for%20i%20in%20Q%20for%20j%20in%20Q)%20%3D%3D%201%20for%20h%20in%20Q%20for%20l%20in%20Q%20for%20k%20in%20N)%0A%0A%20%20%20%20prob.controls.outputlog%20%3D%200%20%20%23%20Suppress%20solver%20logging%20for%20cleaner%20output%0A%0A%20%20%20%20%23%20Catches%20a%20Community-license%20size%20limit%20so%20it%20renders%20as%20a%20clear%20message%0A%20%20%20%20%23%20below%2C%20instead%20of%20marimo's%20generic%20%22see%20console%22%20error%20popup.%20Only%20the%0A%20%20%20%20%23%2016x16%20grid%20can%20hit%20this.%0A%20%20%20%20try%3A%0A%20%20%20%20%20%20%20%20prob.optimize()%0A%20%20%20%20%20%20%20%20solve_error%20%3D%20None%0A%20%20%20%20except%20xp.SolverError%20as%20e%3A%0A%20%20%20%20%20%20%20%20solve_error%20%3D%20e%0A%0A%20%20%20%20mo.show_code(license_error_callout(solve_error)%20if%20solve_error%20else%20None%2C%20position%3D%22above%22)%0A%20%20%20%20return%20N%2C%20Q%2C%20assign%2C%20n%2C%20prob%2C%20solve_error%0A%0A%0A%40app.cell(hide_code%3DTrue)%0Adef%20_(mo)%3A%0A%20%20%20%20mo.md(r%22%22%22%0A%20%20%20%20%23%23%20Visualization%0A%0A%20%20%20%20Now%20we%20use%20*matplotlib*%20to%20visualize%20the%20solution%20in%20a%20%24n%5Ctimes%20n%24%20grid.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(N%2C%20Q%2C%20assign%2C%20math%2C%20n%2C%20np%2C%20plt%2C%20prob%2C%20q%2C%20solve_error%2C%20ticker)%3A%0A%20%20%20%20if%20solve_error%20is%20not%20None%3A%0A%20%20%20%20%20%20%20%20fig%20%3D%20None%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20Visualize%20solution%0A%20%20%20%20%20%20%20%20fig%2C%20ax%20%3D%20plt.subplots(figsize%3D(max(6%2C%20n%2F%2F2)%2C%20max(6%2C%20n%2F%2F2)))%0A%20%20%20%20%20%20%20%20min_val%2C%20max_val%2C%20diff%20%3D%200%2C%20n%2C%201%0A%0A%20%20%20%20%20%20%20%20%23%20This%20is%20used%20to%20visualize%20the%20Sudoku%20solution%20with%20the%2016x16%20grid%20too%0A%20%20%20%20%20%20%20%20encode%20%3D%20%7B1%3A%20'1'%2C%202%3A%20'2'%2C%203%3A%20'3'%2C%204%3A%20'4'%2C%205%3A%20'5'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%206%3A%20'6'%2C%207%3A%20'7'%2C%208%3A%20'8'%2C%209%3A%20'9'%2C%2010%3A%20'A'%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%2011%3A%20'B'%2C%2012%3A%20'C'%2C%2013%3A%20'D'%2C%2014%3A%20'E'%2C%2015%3A%20'F'%2C%2016%3A%20'G'%7D%0A%0A%20%20%20%20%20%20%20%20xsol%20%3D%20prob.getSolution(assign)%0A%0A%20%20%20%20%20%20%20%20for%20i1%20in%20Q%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20for%20i2%20in%20Q%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20j1%20in%20Q%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20for%20j2%20in%20Q%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20c%20%3D%20encode%5Bmath.floor(1%20%2B%20sum(xsol%5Bi1*q%20%2B%20i2%2C%20j1*q%20%2B%20j2%2C%20k%5D*k%20for%20k%20in%20N)%20%2B%200.5)%5D%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax.text(j1*q%20%2B%20j2%2C%20n%20-%201%20-%20(i1*q%20%2B%20i2)%2C%20c%2C%20va%3D'center'%2C%20ha%3D'center')%0A%0A%20%20%20%20%20%20%20%20%23%20Set%20up%20the%20plot%20dimensions%0A%20%20%20%20%20%20%20%20ax.set_aspect('equal'%2C%20'box')%0A%20%20%20%20%20%20%20%20ax.set_xlim(min_val-diff%2F2%2C%20max_val-diff%2F2)%0A%20%20%20%20%20%20%20%20ax.set_ylim(min_val-diff%2F2%2C%20max_val-diff%2F2)%0A%0A%20%20%20%20%20%20%20%20%23%20Hide%20the%20axis%20labels%0A%20%20%20%20%20%20%20%20ax.set_xticklabels(%5B%5D)%0A%20%20%20%20%20%20%20%20ax.set_yticklabels(%5B%5D)%0A%0A%20%20%20%20%20%20%20%20%23%20Draw%20the%20sudoku%20grid%0A%20%20%20%20%20%20%20%20ax.yaxis.set_minor_locator(ticker.FixedLocator(np.arange(-0.5%2C%20q*q%2C%201)))%0A%20%20%20%20%20%20%20%20ax.xaxis.set_minor_locator(ticker.FixedLocator(np.arange(-0.5%2C%20q*q%2C%201)))%0A%20%20%20%20%20%20%20%20ax.xaxis.set_major_locator(ticker.FixedLocator(np.arange(-0.5%2C%20q*q%2C%20q)))%0A%20%20%20%20%20%20%20%20ax.yaxis.set_major_locator(ticker.FixedLocator(np.arange(-0.5%2C%20q*q%2C%20q)))%0A%20%20%20%20%20%20%20%20ax.grid(which%3D'minor')%0A%20%20%20%20%20%20%20%20ax.grid(which%3D'major'%2C%20color%3D'black')%0A%0A%20%20%20%20fig%0A%20%20%20%20return%0A%0A%0Aif%20__name__%20%3D%3D%20%22__main__%22%3A%0A%20%20%20%20app.run()%0A
2a96f8018dec0c99b5b1ad1abb0a25ef