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%22Facility%20Location%22%0A%23%20description%20%3D%20%22Mixed-Integer%20Location%20Model%22%0A%23%20image%20%3D%20%22__marimo__%2Fthumbnail-facility.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%0A%20%20%20%20return%20mo%2C%20np%2C%20plt%2C%20xp%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**Facility%20location%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%20A%20facility%20location%20problem%20to%20select%20the%20location%20of%20parks%20over%20a%20set%20of%20candidate%20sites%20that%20are%20meant%20to%20serve%20public%20schools%20at%20minimum%20(average%20or%20maximum)%20distance.%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%0Adef%20_()%3A%0A%20%20%20%20%23%20Install%20the%20necessary%20packages%0A%20%20%20%20%23%20'%25pip%20install%20-q%20xpress%20numpy%20matplotlib'%20command%20supported%20automatically%20in%20marimo%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%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%20There%20are%20%24n%24%20public%20schools%20in%20a%20region.%20The%20administration%20wants%20to%20create%20parks%20(or%20gyms%2C%20swimming%20pools%2C%20etc.)%20that%20can%20be%20used%20by%20these%20schools%20and%20has%20%24m%24%20currently%20unused%20sites%20that%20could%20be%20converted%20for%20this%20purpose.%20The%20coordinates%20of%20both%20the%20public%20schools%20and%20the%20candidate%20sites%20are%20therefore%20known.%20For%20budget%20reasons%2C%20the%20administration%20can%20only%20open%20%24p%24%20parks.%0A%0A%20%20%20%20We%20formulate%20and%20solve%20the%20problem%20of%20choosing%20the%20%24p%24%20parks%20among%20the%20candidate%20sites%20in%20such%20a%20way%20as%20to%20minimize%20one%20of%20the%20following%20two%20objective%20functions%3A%0A%0A%20%20%20%20*%20the%20average%20(i.e.%20the%20sum%20divided%20by%20the%20number%20of%20schools)%20of%20the%20distances%20between%20each%20school%20and%20the%20closest%20(open)%20park%3B%0A%20%20%20%20*%20the%20maximum%2C%20calculated%20on%20the%20set%20of%20schools%2C%20of%20the%20distance%20between%20the%20school%20and%20the%20closest%20park.%0A%0A%20%20%20%20The%20**binary%20variables%20%24serves_%7Bi%2Cj%7D%24**%20indicate%20if%20school%20%24i%20%5Cin%20SCHOOLS%24%20is%20served%20(%3D1)%20by%20the%20candidate%20site%20%24j%20%5Cin%20SITES%24%20or%20not%20(%3D0).%20The%20**binary%20variables%20%24build_j%24**%20indicate%20if%20a%20certain%20candidate%20site%20%24j%24%20is%20selected%20for%20creating%20a%20park%20(%3D1)%20or%20not%20(%3D0).%0A%0A%20%20%20%20%24%24%5Cmin%20%5Cfrac%7B%5Csum_%7Bi%20%5Cin%20SCHOOLS%7D%20%5Csum_%7Bj%20%5Cin%20SITES%7D%20dist_%7Bi%2Cj%7D%20%5Ccdot%20serves_%7Bi%2Cj%7D%7D%7B%7CSCHOOLS%7C%7D%24%24%0A%0A%20%20%20%20Subject%20to%3A%0A%0A%20%20%20%20*%20Every%20school%20must%20be%20served%20by%20one%20park%3A%0A%20%20%20%20%24%24%5Csum_%7Bj%20%5Cin%20SITES%7D%20serves_%7Bi%2Cj%7D%20%3D%201%2C%20%5Cqquad%20%5Cforall%20i%20%5Cin%20SCHOOLS%24%24%0A%0A%20%20%20%20*%20Exactly%20%24p%24%20parks%20are%20built%3A%0A%20%20%20%20%24%24%5Csum_%7Bj%20%5Cin%20SITES%7D%20build_%7Bj%7D%20%3D%20p%24%24%0A%0A%20%20%20%20*%20Only%20parks%20that%20are%20built%20can%20serve%20schools%3A%0A%20%20%20%20%24%24%5Csum_%7Bi%20%5Cin%20SCHOOLS%7D%20serves_%7Bi%2Cj%7D%20%5Cleq%20%7CSCHOOLS%7C%20%5Ccdot%20build_%7Bj%7D%2C%20%5Cqquad%20%5Cforall%20j%20%5Cin%20SITES%24%24%0A%0A%20%20%20%20To%20instead%20minimize%20the%20**maximum%20distance**%20(as%20opposed%20to%20the%20average)%2C%20an%20auxiliary%20variable%20%24z%24%20is%20introduced%2C%20with%20objective%20%24%5Cmin%20z%24%20and%20the%20additional%20constraint%20%24z%20%5Cgeq%20%5Csum_%7Bj%20%5Cin%20SITES%7D%20dist_%7Bi%2Cj%7D%20%5Ccdot%20serves_%7Bi%2Cj%7D%2C%20%5C%20%5Cforall%20i%20%5Cin%20SCHOOLS%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%20Model%20parameters%0A%0A%20%20%20%20**You%20can%20adjust%20the%20parameters%20below%20to%20change%20the%20problem%20instance%20and%20re-solve%20the%20model%20automatically.**%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%20num_schools_slider%20%3D%20mo.ui.slider(5%2C%2015%2C%20value%3D9%2C%20label%3D%22Number%20of%20schools%22%2C%20show_value%3DTrue)%0A%20%20%20%20num_sites_slider%20%3D%20mo.ui.slider(10%2C%2020%2C%20value%3D11%2C%20label%3D%22Number%20of%20candidate%20sites%22%2C%20show_value%3DTrue)%0A%20%20%20%20num_parks_slider%20%3D%20mo.ui.slider(1%2C%208%2C%20value%3D4%2C%20label%3D%22Number%20of%20parks%20to%20build%22%2C%20show_value%3DTrue)%0A%20%20%20%20seed_slider%20%3D%20mo.ui.slider(0%2C%2099%2C%20value%3D10%2C%20label%3D%22Random%20seed%20(each%20value%20gives%20a%20different%20instance)%22%2C%20show_value%3DTrue)%0A%20%20%20%20objective_selector%20%3D%20mo.ui.radio(%0A%20%20%20%20%20%20%20%20options%3D%7B%22Minimize%20average%20distance%22%3A%20%22avg%22%2C%20%22Minimize%20maximum%20distance%22%3A%20%22max%22%7D%2C%0A%20%20%20%20%20%20%20%20value%3D%22Minimize%20average%20distance%22%2C%0A%20%20%20%20%20%20%20%20label%3D%22Objective%22%0A%20%20%20%20)%0A%20%20%20%20mo.vstack(%5Bnum_schools_slider%2C%20num_sites_slider%2C%20num_parks_slider%2C%20seed_slider%2C%20objective_selector%5D)%0A%20%20%20%20return%20(%0A%20%20%20%20%20%20%20%20num_parks_slider%2C%0A%20%20%20%20%20%20%20%20num_schools_slider%2C%0A%20%20%20%20%20%20%20%20num_sites_slider%2C%0A%20%20%20%20%20%20%20%20objective_selector%2C%0A%20%20%20%20%20%20%20%20seed_slider%2C%0A%20%20%20%20)%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%20and%20analysis%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%20We%20start%20by%20importing%20the%20necessary%20modules%2C%20to%20then%20create%20random%20coordinates%20for%20schools%20and%20candidate%20sites%20and%20calculate%20the%20distances%20between%20them.%20For%20a%20different%20instance%2C%20change%20the%20value%20of%20**Random%20seed**%20above.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(mo%2C%20np%2C%20num_schools_slider%2C%20num_sites_slider%2C%20seed_slider)%3A%0A%20%20%20%20num_schools%20%3D%20num_schools_slider.value%0A%20%20%20%20num_sites%20%3D%20num_sites_slider.value%0A%0A%20%20%20%20rng%20%3D%20np.random.default_rng(seed_slider.value)%0A%0A%20%20%20%20SCHOOLS%20%3D%20range(num_schools)%20%20%23%20Set%20of%20schools%0A%20%20%20%20SITES%20%3D%20range(num_sites)%20%20%20%20%20%20%23%20Set%20of%20candidate%20sites%0A%0A%20%20%20%20coord_schools%20%3D%2010%20*%20rng.random((num_schools%2C%202))%20%20%23%20x-y%20coordinates%20between%200%20and%2010%20(in%20km)%0A%20%20%20%20coord_sites%20%3D%2010%20*%20rng.random((num_sites%2C%202))%0A%0A%20%20%20%20%23%20Dictionary%20with%20the%20distances%20between%20schools%20and%20candidate%20sites%0A%20%20%20%20dist%20%3D%20%7B(i%2C%20j)%3A%20np.linalg.norm(coord_schools%5Bi%5D%20-%20coord_sites%5Bj%5D)%20for%20i%20in%20SCHOOLS%20for%20j%20in%20SITES%7D%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%20SCHOOLS%2C%20SITES%2C%20coord_schools%2C%20coord_sites%2C%20dist%2C%20num_schools%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%20Now%20we%20plot%20the%20points%20for%20schools%20(blue)%20and%20candidate%20sites%20(grey).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(SCHOOLS%2C%20SITES%2C%20coord_schools%2C%20coord_sites%2C%20plt)%3A%0A%20%20%20%20%23%20Plot%20the%20schools%20and%20candidate%20sites%20before%20solving%2C%20with%20no%20assignment%20yet.%0A%20%20%20%20fig_start%2C%20ax_start%20%3D%20plt.subplots(figsize%3D(7%2C%207))%0A%0A%20%20%20%20ax_start.scatter(coord_schools%5B%3A%2C%200%5D%2C%20coord_schools%5B%3A%2C%201%5D%2C%20color%3D%22%235555ff%22%2C%20s%3D80%2C%20zorder%3D3%2C%20label%3D%22Schools%22)%0A%20%20%20%20ax_start.scatter(coord_sites%5B%3A%2C%200%5D%2C%20coord_sites%5B%3A%2C%201%5D%2C%20color%3D%22%23a0a0a0%22%2C%20s%3D80%2C%20zorder%3D3%2C%20label%3D%22Candidate%20sites%22)%0A%0A%20%20%20%20for%20_i%20in%20SCHOOLS%3A%0A%20%20%20%20%20%20%20%20ax_start.annotate(str(_i)%2C%20(coord_schools%5B_i%2C%200%5D%2C%20coord_schools%5B_i%2C%201%5D)%2C%20textcoords%3D%22offset%20points%22%2C%20xytext%3D(4%2C%204))%0A%20%20%20%20for%20_j%20in%20SITES%3A%0A%20%20%20%20%20%20%20%20ax_start.annotate(str(_j)%2C%20(coord_sites%5B_j%2C%200%5D%2C%20coord_sites%5B_j%2C%201%5D)%2C%20textcoords%3D%22offset%20points%22%2C%20xytext%3D(4%2C%204))%0A%0A%20%20%20%20ax_start.set_aspect(%22equal%22%2C%20%22box%22)%0A%20%20%20%20ax_start.set_title(%22Schools%20(blue)%20and%20candidate%20sites%20for%20parks%20(grey)%22)%0A%20%20%20%20ax_start.legend(loc%3D%22upper%20center%22%2C%20bbox_to_anchor%3D(0.5%2C%20-0.05)%2C%20ncol%3D2)%0A%20%20%20%20plt.tight_layout()%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%20The%20plot%20below%20shows%20the%20distance%20range%20and%20a%20histogram%20of%20the%20distances%20between%20schools%20and%20candidate%20sites.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(dist%2C%20mo%2C%20plt)%3A%0A%20%20%20%20%23%20Extract%20all%20distance%20values.%0A%20%20%20%20distance_values%20%3D%20list(dist.values())%0A%0A%20%20%20%20%23%20Plot%20histogram%20of%20distance%20distribution.%0A%20%20%20%20fig_hist%2C%20ax_hist%20%3D%20plt.subplots(figsize%3D(10%2C%205))%0A%20%20%20%20ax_hist.hist(distance_values%2C%20bins%3D10%2C%20color%3D%22skyblue%22%2C%20edgecolor%3D%22black%22)%0A%20%20%20%20ax_hist.set_title(%22Distribution%20of%20distances%20between%20schools%20and%20candidate%20sites%22)%0A%20%20%20%20ax_hist.set_xlabel(%22Distance%20(km)%22)%0A%20%20%20%20ax_hist.set_ylabel(%22Frequency%22)%0A%20%20%20%20ax_hist.grid(True)%0A%20%20%20%20plt.tight_layout()%0A%0A%20%20%20%20%23%20Display%20the%20range.%0A%20%20%20%20mo.vstack(%5B%0A%20%20%20%20%20%20%20%20mo.md(f%22Minimum%20distance%3A%20**%7Bmin(distance_values)%3A.2f%7D%20km**%20%26nbsp%3B%26nbsp%3B%20Maximum%20distance%3A%20**%7Bmax(distance_values)%3A.2f%7D%20km**%22)%2C%0A%20%20%20%20%20%20%20%20fig_hist%2C%0A%20%20%20%20%5D)%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%20solution%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%20When%20passing%20sets%2C%20lists%2C%20or%20range%20objects%20to%20%5Bproblem.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%60serves%60%20and%20%60build%60%20are%20created%20this%20way.%0A%0A%20%20%20%20The%20objective%20and%20constraints%20are%20then%20created%20and%20added%20to%20the%20problem%20directly.%20The%20%60name%60%20argument%20of%20%5Bproblem.addConstraint%5D(https%3A%2F%2Fwww.fico.com%2Ffico-xpress-optimization%2Fdocs%2Flatest%2Fsolver%2Foptimizer%2Fpython%2FHTML%2Fproblem.addConstraint.html)%20attaches%20a%20human-readable%20label%20to%20each%20constraint%2C%20which%20is%20useful%20when%20writing%20the%20LP%20file%20or%20diagnosing%20infeasibility.%0A%0A%20%20%20%20The%20**Objective**%20control%20above%20switches%20between%20minimizing%20the%20average%20distance%20and%20minimizing%20the%20maximum%20distance%20(via%20the%20auxiliary%20variable%20%24z%24%20described%20in%20the%20problem%20description).%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(%0A%20%20%20%20SCHOOLS%2C%0A%20%20%20%20SITES%2C%0A%20%20%20%20dist%2C%0A%20%20%20%20mo%2C%0A%20%20%20%20num_parks_slider%2C%0A%20%20%20%20num_schools%2C%0A%20%20%20%20objective_selector%2C%0A%20%20%20%20xp%2C%0A)%3A%0A%20%20%20%20num_parks%20%3D%20num_parks_slider.value%0A%0A%20%20%20%20%23%20Create%20a%20problem%20instance.%0A%20%20%20%20prob%20%3D%20xp.problem()%0A%0A%20%20%20%20%23%20Decision%20variables%3A%20serves%5Bi%2Cj%5D%20%3D%201%20if%20school%20i%20is%20served%20by%20site%20j%3B%20build%5Bj%5D%20%3D%201%20if%20site%20j%20is%20built.%0A%20%20%20%20serves%20%3D%20prob.addVariables(SCHOOLS%2C%20SITES%2C%20vartype%3Dxp.binary%2C%20name%3D%22serves%22)%0A%20%20%20%20build%20%3D%20prob.addVariables(SITES%2C%20vartype%3Dxp.binary%2C%20name%3D%22build%22)%0A%0A%20%20%20%20%23%20Every%20school%20must%20be%20served%20by%20one%20park%20-%20one%20constraint%20per%20school%2C%20named%20served_0..n-1.%0A%20%20%20%20for%20i%20in%20SCHOOLS%3A%0A%20%20%20%20%20%20%20%20prob.addConstraint(xp.Sum(serves%5Bi%2C%20j%5D%20for%20j%20in%20SITES)%20%3D%3D%201%2C%20name%3Df%22served_%7Bi%7D%22)%0A%0A%20%20%20%20%23%20Exactly%20p%20parks%20are%20built.%0A%20%20%20%20prob.addConstraint(xp.Sum(build%5Bj%5D%20for%20j%20in%20SITES)%20%3D%3D%20num_parks%2C%20name%3D%22num_parks%22)%0A%0A%20%20%20%20%23%20Only%20parks%20that%20are%20built%20can%20serve%20schools%20-%20one%20constraint%20per%20site%2C%20named%20capacity_0..m-1.%0A%20%20%20%20for%20j%20in%20SITES%3A%0A%20%20%20%20%20%20%20%20prob.addConstraint(xp.Sum(serves%5Bi%2C%20j%5D%20for%20i%20in%20SCHOOLS)%20%3C%3D%20num_schools%20*%20build%5Bj%5D%2C%20name%3Df%22capacity_%7Bj%7D%22)%0A%0A%20%20%20%20%23%20Objective%20function%3A%20switches%20between%20minimizing%20average%20distance%20and%20minimizing%20maximum%20distance.%0A%20%20%20%20if%20objective_selector.value%20%3D%3D%20%22avg%22%3A%0A%20%20%20%20%20%20%20%20prob.setObjective(xp.Sum(dist%5Bi%2C%20j%5D%20*%20serves%5Bi%2C%20j%5D%20for%20i%20in%20SCHOOLS%20for%20j%20in%20SITES)%20%2F%20num_schools)%0A%20%20%20%20else%3A%0A%20%20%20%20%20%20%20%20%23%20Auxiliary%20variable%20z%20and%20constraints%20to%20model%20the%20maximum%20distance.%0A%20%20%20%20%20%20%20%20z%20%3D%20prob.addVariable(name%3D%22z%22)%0A%20%20%20%20%20%20%20%20prob.addConstraint(z%20%3E%3D%20xp.Sum(dist%5Bi%2C%20j%5D%20*%20serves%5Bi%2C%20j%5D%20for%20j%20in%20SITES)%20for%20i%20in%20SCHOOLS)%0A%20%20%20%20%20%20%20%20prob.setObjective(z)%20%20%23%20Replaces%20the%20old%20objective%20function.%0A%0A%20%20%20%20prob.controls.outputlog%20%3D%200%20%20%23%20Turn%20off%20output%20log%20for%20cleaner%20output.%0A%20%20%20%20prob.optimize()%0A%0A%20%20%20%20%23%20Get%20the%20solution.%0A%20%20%20%20sol_serves%20%3D%20prob.getSolution(serves)%0A%20%20%20%20sol_build%20%3D%20prob.getSolution(build)%0A%20%20%20%20mo.show_code()%0A%20%20%20%20return%20sol_build%2C%20sol_serves%0A%0A%0A%40app.cell%0Adef%20_(SCHOOLS%2C%20SITES%2C%20dist%2C%20mo%2C%20num_schools%2C%20sol_serves)%3A%0A%20%20%20%20avg_dist%20%3D%20sum(dist%5B_i%2C%20_j%5D%20*%20sol_serves%5B_i%2C%20_j%5D%20for%20_i%20in%20SCHOOLS%20for%20_j%20in%20SITES)%20%2F%20num_schools%0A%20%20%20%20max_dist%20%3D%20max(dist%5B_i%2C%20_j%5D%20for%20_i%20in%20SCHOOLS%20for%20_j%20in%20SITES%20if%20sol_serves%5B_i%2C%20_j%5D%20%3E%200.5)%0A%0A%20%20%20%20mo.md(f%22%22%22%0A%20%20%20%20**Average%20distance%20from%20a%20school%20to%20its%20assigned%20park%3A**%20%7Bavg_dist%3A.2f%7D%20km%0A%0A%20%20%20%20**Maximum%20distance%20from%20a%20school%20to%20its%20assigned%20park%3A**%20%7Bmax_dist%3A.2f%7D%20km%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%20Solution%20Visualization%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%20The%20plot%20below%20draws%20the%20schools%20(blue)%2C%20all%20candidate%20sites%20(grey)%2C%20the%20built%20parks%20(red)%2C%20and%20the%20school-to-park%20assignment%20lines.%0A%20%20%20%20%22%22%22)%0A%20%20%20%20return%0A%0A%0A%40app.cell%0Adef%20_(SCHOOLS%2C%20SITES%2C%20coord_schools%2C%20coord_sites%2C%20plt%2C%20sol_build%2C%20sol_serves)%3A%0A%20%20%20%20fig_map%2C%20ax_map%20%3D%20plt.subplots(figsize%3D(7%2C%207))%0A%0A%20%20%20%20for%20_i%20in%20SCHOOLS%3A%0A%20%20%20%20%20%20%20%20for%20_j%20in%20SITES%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20if%20sol_serves%5B_i%2C%20_j%5D%20%3E%200.5%3A%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20ax_map.plot(%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Bcoord_schools%5B_i%2C%200%5D%2C%20coord_sites%5B_j%2C%200%5D%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%5Bcoord_schools%5B_i%2C%201%5D%2C%20coord_sites%5B_j%2C%201%5D%5D%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20color%3D%22%23cccccc%22%2C%20linewidth%3D1%2C%20zorder%3D1%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20)%0A%0A%20%20%20%20built_sites%20%3D%20%5B_j%20for%20_j%20in%20SITES%20if%20sol_build%5B_j%5D%20%3E%200.5%5D%0A%20%20%20%20unbuilt_sites%20%3D%20%5B_j%20for%20_j%20in%20SITES%20if%20sol_build%5B_j%5D%20%3C%3D%200.5%5D%0A%0A%20%20%20%20ax_map.scatter(coord_schools%5B%3A%2C%200%5D%2C%20coord_schools%5B%3A%2C%201%5D%2C%20color%3D%22%235555ff%22%2C%20s%3D80%2C%20zorder%3D3%2C%20label%3D%22Schools%22)%0A%20%20%20%20ax_map.scatter(coord_sites%5Bunbuilt_sites%2C%200%5D%2C%20coord_sites%5Bunbuilt_sites%2C%201%5D%2C%20color%3D%22%23a0a0a0%22%2C%20s%3D80%2C%20zorder%3D3%2C%20label%3D%22Candidate%20sites%22)%0A%20%20%20%20ax_map.scatter(coord_sites%5Bbuilt_sites%2C%200%5D%2C%20coord_sites%5Bbuilt_sites%2C%201%5D%2C%20color%3D%22%23ff5555%22%2C%20s%3D120%2C%20zorder%3D3%2C%20label%3D%22Built%20parks%22)%0A%0A%20%20%20%20for%20_i%20in%20SCHOOLS%3A%0A%20%20%20%20%20%20%20%20ax_map.annotate(str(_i)%2C%20(coord_schools%5B_i%2C%200%5D%2C%20coord_schools%5B_i%2C%201%5D)%2C%20textcoords%3D%22offset%20points%22%2C%20xytext%3D(4%2C%204))%0A%20%20%20%20for%20_j%20in%20SITES%3A%0A%20%20%20%20%20%20%20%20ax_map.annotate(str(_j)%2C%20(coord_sites%5B_j%2C%200%5D%2C%20coord_sites%5B_j%2C%201%5D)%2C%20textcoords%3D%22offset%20points%22%2C%20xytext%3D(4%2C%204))%0A%0A%20%20%20%20ax_map.set_aspect(%22equal%22%2C%20%22box%22)%0A%20%20%20%20ax_map.set_title(%22Schools%2C%20candidate%20sites%2C%20and%20selected%20parks%22)%0A%20%20%20%20ax_map.legend(loc%3D%22upper%20center%22%2C%20bbox_to_anchor%3D(0.5%2C%20-0.05)%2C%20ncol%3D3)%0A%20%20%20%20plt.tight_layout()%0A%20%20%20%20fig_map%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
515d652451abd5bf73ca07065e635ee2