<?xml version="1.0" encoding="UTF-8" standalone="no"?><rss xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/" xmlns:sy="http://purl.org/rss/1.0/modules/syndication/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" version="2.0">

<channel>
	<title>Operations Research</title>
	<atom:link href="https://blogs.sas.com/content/operations/feed/" rel="self" type="application/rss+xml"/>
	<link>https://blogs.sas.com/content/operations/</link>
	<description></description>
	<lastBuildDate>Tue, 06 Aug 2024 18:19:26 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.6</generator>
	<item>
		<title>We are joining forces with the SAS Data Science blog!</title>
		<link>https://blogs.sas.com/content/operations/2021/02/10/we-are-joining-forces-with-the-sas-data-science-blog/</link>
		
		<dc:creator><![CDATA[Udo Sglavo]]></dc:creator>
		<pubDate>Wed, 10 Feb 2021 14:30:41 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[operations research]]></category>
		<category><![CDATA[R&D Analytics]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/operations/?p=4161</guid>

					<description><![CDATA[<p>It's been a great ride Since 2014, the Operations Research Blog has covered a broad range of topics related to real applications of operations research.  Paraphrasing Principal Product Manager for Optimization Ed Hughes' first post - this blog covered how OR methods could be applied to organizational and business planning [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2021/02/10/we-are-joining-forces-with-the-sas-data-science-blog/">We are joining forces with the SAS Data Science blog!</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>It's been a great ride</h2>
<p>Since 2014, the Operations Research Blog has covered a broad range of topics related to real applications of operations research.  Paraphrasing Principal Product Manager for Optimization Ed Hughes' first post - this blog covered how OR methods could be applied to organizational and business planning problems as well as our individual lives. It demonstrated how other SAS analytical products, business intelligence, and reporting capabilities interact with and enhance operations research techniques.  Topics covered everything from computing an optimal blackjack strategy with SAS/OR to improving hidden Markov models with black-box optimization.</p>
<h2>Life moves on and so are we</h2>
<p>The term data science isn’t new, but it's gotten more and more press over the last several years. As developing methods to handle big data became more important, so did the prominence of data science. Data science encompasses mathematicians, statisticians, computer scientists, and many other related fields. The <a href="https://blogs.sas.com/content/subconsciousmusings/">SAS Data Science Blog</a> evolved from discussing trends that drove innovation and challenges that expanded the boundaries of what we thought was possible to one that features the perspectives of SAS data scientists as they share the technical methods used to solve many of the challenging problems facing organizations today.</p>
<p>While data scientists typically are concerned with machine learning techniques such as supervised and unsupervised learning, many realized that broadening their horizon helps them address business challenges as well. We believe that an advanced analytics ecosystem must feature optimization capabilities at SAS, or it is not complete. In particular, in times of COVID-19 and its specific challenges, it has become clear that optimization techniques are fundamental for data scientists.</p>
<h2>Come visit our new home</h2>
<p>You will still be able to access all our <a href="https://blogs.sas.com/content/tag/operations-research/">previous posts.</a> Look for new operations research posts and the different ways optimization can be used with <a href="https://www.sas.com/en_us/software/viya.html">SAS Viya</a> at our new home. We hope you will join us on the SAS Data Science blog site to discover how operations research will move forward helping data scientists in the future.</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2021/02/10/we-are-joining-forces-with-the-sas-data-science-blog/">We are joining forces with the SAS Data Science blog!</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></content:encoded>
					
		
		
			<enclosure url="https://blogs.sas.com/content/operations/files/2021/02/sunset-150x150.jpg"/>
	</item>
		<item>
		<title>Why venue optimization is critical and how it works</title>
		<link>https://blogs.sas.com/content/operations/2020/11/09/venue-optimization/</link>
					<comments>https://blogs.sas.com/content/operations/2020/11/09/venue-optimization/#comments</comments>
		
		<dc:creator><![CDATA[Sertalp B. Cay]]></dc:creator>
		<pubDate>Mon, 09 Nov 2020 14:04:53 +0000</pubDate>
				<category><![CDATA[mixed integer linear optimization]]></category>
		<category><![CDATA[sports analytics]]></category>
		<category><![CDATA[application]]></category>
		<category><![CDATA[containers]]></category>
		<category><![CDATA[COVID-19]]></category>
		<category><![CDATA[DECOMP]]></category>
		<category><![CDATA[MILP]]></category>
		<category><![CDATA[network]]></category>
		<category><![CDATA[operations research]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[OPTMODEL]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[R&D Analytics]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/operations/?p=3581</guid>

					<description><![CDATA[<p>A year ago we could not imagine stadiums being empty during the most exciting sports events, but it is a common sight now. The entertainment sector is one of the hardest hit sectors because of the COVID-19 pandemic [1]. Social distancing requirements made it impossible to have viewers in stadiums [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2020/11/09/venue-optimization/">Why venue optimization is critical and how it works</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<figure id="attachment_3737" aria-describedby="caption-attachment-3737" style="width: 768px" class="wp-caption aligncenter"><img fetchpriority="high" decoding="async" class="wp-image-3737 size-full" src="https://blogs.sas.com/content/operations/files/2020/11/photo4.jpg" alt="" width="768" height="512" srcset="https://blogs.sas.com/content/operations/files/2020/11/photo4.jpg 768w, https://blogs.sas.com/content/operations/files/2020/11/photo4-300x200.jpg 300w" sizes="(max-width: 768px) 100vw, 768px" /><figcaption id="caption-attachment-3737" class="wp-caption-text">NCSU used SAS Venue Optimization for their seating layout on the October 17th game versus Duke.<br />(Image by ACC Digital Network @ YouTube)</figcaption></figure>
<p>A year ago we could not imagine stadiums being empty during the most exciting sports events, but it is a common sight now. The entertainment sector is one of the hardest hit sectors because of the COVID-19 pandemic [1]. Social distancing requirements made it impossible to have viewers in stadiums and indoor venues.</p>
<p>The main discussion around venues started with the Champions League game (football/soccer) between Atalanta and Valencia on February 19th, an event that accelerated the spread of the pandemic in Italy [2]. The spread of the virus in Lombardy was quite noticeable, especially compared to the rest of the country. Extreme social distancing rules were implemented after this incident, preventing fans from being in the stands all over the world. Obviously, stadiums and event centers are some of the most difficult places to practice social distancing unless enough precautions are taken. Many leagues were either postponed or cancelled in April, even though most of the leagues eventually continued without fans despite the profit loss [3]. Countries are still trying to decide whether to have fans in venues, and the rules keep changing as number fluctuate. Belgium banned fans in March, allowed them in June, and banned them again in October because of increasing numbers of patients.</p>
<p>There is a growing uncertainty about the future of viewers in venues. Back in April, we had an internal discussion about the issue in our Sports Analytics discussion group when Brian Miles asked us a question. The question was whether we can utilize analytics and optimization to figure out how many people can be accepted into venues while adhering to social distancing. I quickly wrote an optimization model for a small section, and the result from that experiment holds true even after we eventually developed a full solution:</p>
<ul>
<li>With 6 feet social distancing restrictions, you can fill seated venues up to 30% utilization.</li>
<li>If you want to allow people to have clear access to exits, it is between 11 to 15%.</li>
</ul>
<p>This is the cold hard fact about venues right now. This number depends on the seat and section dimensions and also what kind of regulations you have, but the results we have seen so far are usually around this number. Our belief is that with all the financial troubles sports clubs are going through, if clubs are allowed to have fans in stadiums, then they should optimize it. You might think it is not a difficult problem to solve on the surface, but it gets complicated when details are added:</p>
<ul>
<li>Whether to accept people as individuals or as groups</li>
<li>Restrictions on seats that are close to the stairs and exits</li>
<li>Different rules for section places; for example, NC State University (NCSU) has student sections at the end zones</li>
<li>Expected demand for various group sizes</li>
<li>Maximizing number of people may not be equivalent to maximizing the revenue</li>
</ul>
<p>Because of the difficult nature of the problem, we collaborated with NCSU to build plans for their Carter-Finley Stadium.</p>
<p><img decoding="async" class="aligncenter size-full wp-image-3599" src="https://blogs.sas.com/content/operations/files/2020/10/30053461851_9a36d78a00_b1.jpg" alt="" width="1024" height="267" srcset="https://blogs.sas.com/content/operations/files/2020/10/30053461851_9a36d78a00_b1.jpg 1024w, https://blogs.sas.com/content/operations/files/2020/10/30053461851_9a36d78a00_b1-300x78.jpg 300w, https://blogs.sas.com/content/operations/files/2020/10/30053461851_9a36d78a00_b1-768x200.jpg 768w" sizes="(max-width: 1024px) 100vw, 1024px" /></p>
<figure id="attachment_3662" aria-describedby="caption-attachment-3662" style="width: 450px" class="wp-caption aligncenter"><img decoding="async" class="wp-image-3662" src="https://blogs.sas.com/content/operations/files/2020/10/news_photo.jpg" alt="" width="450" height="267" srcset="https://blogs.sas.com/content/operations/files/2020/10/news_photo.jpg 954w, https://blogs.sas.com/content/operations/files/2020/10/news_photo-300x178.jpg 300w, https://blogs.sas.com/content/operations/files/2020/10/news_photo-768x456.jpg 768w" sizes="(max-width: 450px) 100vw, 450px" /><figcaption id="caption-attachment-3662" class="wp-caption-text">Photo by Ethan Hyman @ The News &amp; Observer</figcaption></figure>
<p>Ticket sales are complicated by nature, but there are more aspects to consider during this pandemic: If fans can buy any ticket they want in the venue (and can have any number of people in their group as they want), it will lead to results that are far from the optimal values. The one question that every venue manager should answer is this: "How can I maximize my revenue (or fans in stands) under ever-changing COIVD-19 restrictions?" More than anything, venues need a way to build seating plans in an automated way.</p>
<p>Enter <a href="https://www.sas.com/en_us/news/press-releases/2020/august/sas-for-venue-optimization.html">SAS Venue Optimization</a>.</p>
<h2>Group Selections</h2>
<p>SAS Venue Optimization is powered by <a href="https://www.sas.com/en_us/software/optimization.html">SAS Optimization</a> under the hood. I will show how we built the optimization model below.</p>
<p>Let me start with a simple problem: consider a section with 2 rows and 5 seats in each row.</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-3635 size-medium" src="https://blogs.sas.com/content/operations/files/2020/10/section_blog-300x169.png" alt="" width="300" height="169" srcset="https://blogs.sas.com/content/operations/files/2020/10/section_blog-300x169.png 300w, https://blogs.sas.com/content/operations/files/2020/10/section_blog.png 692w" sizes="(max-width: 300px) 100vw, 300px" /></p>
<p>The total number of different groups we can sell in this small section is astonishing: there are 30 different configurations. In each row you can sell:</p>
<ul>
<li>5 individual seats (S1, S2, S3, S4, S5)</li>
<li>4 groups of pairs (S1-S2, S2-S3, S3-S4, S4-S5)</li>
<li>3 groups of triplets (S1-S2-S3, S2-S3-S4, S3-S4-S5)</li>
<li>2 groups of 4 (S1-S2-S3-S4, S2-S3-S4-S5)</li>
<li>1 group of 5 (S1-S2-S3-S4-S5)</li>
</ul>
<p>In total, we have 5+4+3+2+1=15 configurations per row, 30 in total.</p>
<p>Assume that the social distancing limit is equal to twice the width of a seat and depth of one row. The optimal strategy for this small section is clear; we can sell either the entire first row or the entire second row. A suboptimal solution would be selling R1S1-R1S2 and R2S4-R2S5. Consider this: North Carolina State University's Carter-Finley stadium has over 65,000 seats, and the total number of configurations is over 268,000. The question you need to answer is this: "Which one of these 268,000 groups should I choose?" For each group you can either sell or block: There are <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="2^{268,000}" /></span><script type='math/tex'>2^{268,000}</script> combinations.</p>
<p>Of course the total combinations are much less than this number; we cannot really sell S1-S2 and S1-S2-S3 at the same time. Because of social distancing, we cannot sell the following combinations either:</p>
<ul>
<li>S1-S2 and S3-S4</li>
<li>S1-S2 and S4-S5</li>
<li>R1S1 and R2S1</li>
<li>R1S1 and R2S2</li>
</ul>
<p>The problem boils down to finding all these combinations you cannot sell at the same time and choosing the ones that maximize your objective.</p>
<figure id="attachment_3638" aria-describedby="caption-attachment-3638" style="width: 300px" class="wp-caption aligncenter"><img loading="lazy" decoding="async" class="size-medium wp-image-3638" src="https://blogs.sas.com/content/operations/files/2020/10/section_hover-300x296.png" alt="" width="300" height="296" srcset="https://blogs.sas.com/content/operations/files/2020/10/section_hover-300x296.png 300w, https://blogs.sas.com/content/operations/files/2020/10/section_hover.png 518w" sizes="(max-width: 300px) 100vw, 300px" /><figcaption id="caption-attachment-3638" class="wp-caption-text">SAS Venue Optimization can show the safety bubble for individual and group seats.</figcaption></figure>
<p>Let set <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="G" /></span><script type='math/tex'>G</script> be the set of all possible groups in the section, let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="d" /></span><script type='math/tex'>d</script> be the distance parameter between each pair of groups, and let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="t" /></span><script type='math/tex'>t</script> be the social distancing limit. We can define the set <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="P" /></span><script type='math/tex'>P</script> of conflicting pairs of groups as follows:</p>
<p><p style='text-align:center;'><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none;' class='tex' alt="P = \{g_1 \in G, g_2 \in G: g_1 < g_2\text{ and }d(g_1,g_2) \lt t\}" /></span><script type='math/tex;  mode=display'>P = \{g_1 \in G, g_2 \in G: g_1 < g_2\text{ and }d(g_1,g_2) \lt t\}</script></p></p>
<p>Using conflicting pairs, we can write the optimization model as follows, where <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="n_g" /></span><script type='math/tex'>n_g</script> is the number of people in group <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script> and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="u_g" /></span><script type='math/tex'>u_g</script> is a binary decision variable that indicates whether group <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script> is selected:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\begin{align} &\text{maximize} & \sum_{g \in G} n_g u_g \\ &\text{subject to} & u_{g_i} + u_{g_j} &\le 1 && \text{for $(g_i, g_j) \in P$} \\ && u_g &\in \{0,1\} && \text{for $g \in G$}\end{align}" /></span><script type='math/tex'>\begin{align} &\text{maximize} & \sum_{g \in G} n_g u_g \\ &\text{subject to} & u_{g_i} + u_{g_j} &\le 1 && \text{for $(g_i, g_j) \in P$} \\ && u_g &\in \{0,1\} && \text{for $g \in G$}\end{align}</script></p>
<p>The objective (1) is to maximize the total number of people. The conflict constraint (2) prevents selecting both groups in a conflicting pair. Constraint (3) forces <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="u_g" /></span><script type='math/tex'>u_g</script> to be binary.</p>
<p>The major challenge with this formulation is that there are too many conflicting pairs. We can strengthen the formulation by finding maximal cliques. This is where the <a href="https://documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_networksolver_overview.htm&amp;locale=en">network solver</a> comes into play. Using the network solver, we can generate the set of maximal cliques <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="C" /></span><script type='math/tex'>C</script> such that at most one group in each clique can be selected. For <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="c\in C" /></span><script type='math/tex'>c\in C</script>, let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="G_c" /></span><script type='math/tex'>G_c</script> be the groups that appear in clique <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="c" /></span><script type='math/tex'>c</script>. Then we can call the <a href="https://documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_milpsolver_overview.htm&amp;locale=en">mixed integer linear optimization solver</a> to choose which groups to sell, in order to maximize the objective:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\begin{align} &\text{maximize} &\sum_{g \in G} n_g u_g \nonumber \\ &\text{subject to} &\sum_{g \in G_c} u_g &\le 1 && \text{for $c \in C$} \\ &&u_g &\in \{0,1\} && \text{for $g \in G$} \nonumber \end{align}" /></span><script type='math/tex'>\begin{align} &\text{maximize} &\sum_{g \in G} n_g u_g \nonumber \\ &\text{subject to} &\sum_{g \in G_c} u_g &\le 1 && \text{for $c \in C$} \\ &&u_g &\in \{0,1\} && \text{for $g \in G$} \nonumber \end{align}</script></p>
<p>The clique constraint (4) enforces selection of at most one group per clique.</p>
<p>This intermediate operation to find maximal cliques reduces the optimization time roughly 60x.</p>
<h2>Venue-wide Restrictions</h2>
<p>We call this optimization model through the SAS Optimization Python interface, namely the <a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casactmopt&amp;docsetTarget=casactmopt_optimization_details03.htm&amp;locale=en">runOptmodel</a> CAS action. This action has a nice feature called "<a href="https://documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casactmopt&amp;docsetTarget=cas-optimization-runoptmodel.htm&amp;locale=en#PYTHON.cas-optimization-runoptmodel-groupby">groupBy</a>" that can split tables into groups at run time. By using it, we are able to run scenarios for all 82 sections of NCSU's Carter-Finley Stadium in parallel.</p>
<p>NCSU came up with a challenging question: What if there is a lower limit on the number of people for a certain group type? Consider that the forecasted demand for groups of 2 is 500. So the solution should have at least 500 groups of 2 to meet this demand. For this purpose, we split the problem into two parts. In the first part, we find all cliques in each section separately and in parallel. Then we combine all these cliques into a single optimization problem and use the <a href="https://documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_decomp_overview.htm&amp;locale=en">DECOMP</a> algorithm which uses decomposition to exploit the decomposed structure of this combined instance.</p>
<p>You can find a simplified version of our optimization model in our <a href="https://github.com/sascommunities/sas-optimization-blog/tree/master/venue_optimization" target="_blank" rel="noopener noreferrer">GitHub repository</a>.</p>
<h2>Performance</h2>
<p>As I mentioned, Carter-Finley has 268K group combinations. In our test server with 96 CPU cores, the problem is solved between 15 seconds and 7 minutes, depending on what kinds of constraints are active. Our original model was taking 30 minutes to solve before we used the maximal cliques, where most of time was being spent on writing the problem.</p>
<p>To solve a seating layout problem of this size, you need to use optimization or accept the fact that your solution is suboptimal at best. It is why having an optimization method is critical for venues.</p>
<h2>SAS Venue Optimization</h2>
<p>Once we were convinced that we can solve these problems efficiently, we started offering SAS Venue Optimization in a few ways. It can be deployed to the customer's existing Viya services, offered as SaaS, or offered as RaaS. I am really happy with two aspects of how this project turned out:</p>
<ol>
<li><a href="https://www.sas.com/en_us/software/viya.html">SAS Viya</a> and <a href="https://www.sas.com/en_us/software/optimization.html">SAS Optimization</a> provide a great Python interface to develop custom apps, and we were able to connect the pieces quite easily</li>
<li>How everyone in our sports analytics channel worked on full cylinders in a cross-divisional team effort to come up with an offering that can help sports clubs and venues</li>
</ol>
<p>We turned this idea into a product with impressive speed. If you would like to learn more, see our <a href="https://www.linkedin.com/pulse/venue-optimization-after-lockdown-update-demo-bas-belfi/">post on LinkedIn</a>.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-3584" src="https://blogs.sas.com/content/operations/files/2020/10/sas_vo_index.png" alt="" width="1289" height="800" srcset="https://blogs.sas.com/content/operations/files/2020/10/sas_vo_index.png 1289w, https://blogs.sas.com/content/operations/files/2020/10/sas_vo_index-300x186.png 300w, https://blogs.sas.com/content/operations/files/2020/10/sas_vo_index-1024x636.png 1024w, https://blogs.sas.com/content/operations/files/2020/10/sas_vo_index-768x477.png 768w" sizes="(max-width: 1289px) 100vw, 1289px" /></p>
<h2>Flow</h2>
<p>SAS Optimization is the biggest piece in this puzzle and does the heavy lifting to solve massive problem instances. The remainder of the tool is written in Python for the most part: initial data processing input, web server, parameter selection, optimization calls, and export utility are all in Python. We used <a href="https://flask.palletsprojects.com/">Flask</a> and <a href="https://gunicorn.org/">Gunicorn</a> to create a web application that is responsive and can work with multiple clients. The web application benefits from several JavaScript technologies, including <a href="https://vuejs.org/">Vue.js</a> and <a href="https://leafletjs.com/">Leaflet</a>. We also used <a href="https://developer.mozilla.org/en-US/docs/Web/API/EventSource">EventSource</a> technology to give a visual feedback to the user about the progress of the optimization.</p>
<p>The application depends on a SAS Viya installation and can be deployed very quickly using a Docker container. Usually after spending a few days with customers to standardize their input data, we can deploy SAS Venue Optimization within 30 minutes.</p>
<h2>Seating App</h2>
<p>If you are planning to attend <a href="https://www.sas.com/sas/events/uki/sasforum.html">SAS Forum UK &amp; Ireland</a>, <a href="https://www.sas.com/sas/events/nordic/sasforum.html">SAS Forum Nordics</a> or <a href="https://www.sas.com/sas/events/20/beyond-tomorrow.html">Beyond Tomorrow by SAS CEMEA</a> virtual events this November, there is a surprise for you!</p>
<p>We prepared a smaller version of the venue optimization for an interactive corner. Here, you can try to beat SAS Venue Optimization to find (or get closer to) the optimal solution.</p>
<p>The app tells you if you are satisfying the social distance rule and how close you are to the optimal result when you submit your solution. You can see a sneak peek below:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-3587" src="https://blogs.sas.com/content/operations/files/2020/10/seating_app_demo-300x196.png" alt="" width="500" height="327" srcset="https://blogs.sas.com/content/operations/files/2020/10/seating_app_demo-300x196.png 300w, https://blogs.sas.com/content/operations/files/2020/10/seating_app_demo-1024x670.png 1024w, https://blogs.sas.com/content/operations/files/2020/10/seating_app_demo-768x502.png 768w, https://blogs.sas.com/content/operations/files/2020/10/seating_app_demo-214x140.png 214w, https://blogs.sas.com/content/operations/files/2020/10/seating_app_demo.png 1125w" sizes="(max-width: 500px) 100vw, 500px" />If you are attending any of these events, let me know about your experience! This was the first time I have built something like this, so I am especially curious. You can reach me here or on Twitter <a href="https://twitter.com/sertalpbilal">@sertalpbilal</a>. I am looking forward to see how close you can get to the optimal!</p>
<p>As I have mentioned, a ready-to-use and simplified version of SAS Venue Optimization is in our <a href="https://github.com/sascommunities/sas-optimization-blog/tree/master/venue_optimization" target="_blank" rel="noopener noreferrer">GitHub repository</a>.</p>
<h3>References</h3>
<p>[1] <a href="https://www.mckinsey.com/featured-insights/coronavirus-leading-through-the-crisis/charting-the-path-to-the-next-normal/covid-19-recovery-in-hardest-hit-sectors-could-take-more-than-5-years">https://www.mckinsey.com/featured-insights/coronavirus-leading-through-the-crisis/charting-the-path-to-the-next-normal/covid-19-recovery-in-hardest-hit-sectors-could-take-more-than-5-years</a><br />
[2] <a href="https://www.wsj.com/articles/the-soccer-match-that-kicked-off-italys-coronavirus-disaster-11585752012">https://www.wsj.com/articles/the-soccer-match-that-kicked-off-italys-coronavirus-disaster-11585752012</a><br />
[3] <a href="https://www.dw.com/en/coronavirus-sports-cancellations/a-52569936">https://www.dw.com/en/coronavirus-sports-cancellations/a-52569936</a></p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2020/11/09/venue-optimization/">Why venue optimization is critical and how it works</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/operations/2020/11/09/venue-optimization/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/operations/files/2020/11/photo4-150x150.jpg"/>
	</item>
		<item>
		<title>Back to School Optimization</title>
		<link>https://blogs.sas.com/content/operations/2020/10/26/backtoschooloptimization/</link>
		
		<dc:creator><![CDATA[Subbu Pazhani]]></dc:creator>
		<pubDate>Mon, 26 Oct 2020 15:12:22 +0000</pubDate>
				<category><![CDATA[mixed integer linear optimization]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[MILP]]></category>
		<category><![CDATA[operations research]]></category>
		<category><![CDATA[R&D Analytics]]></category>
		<category><![CDATA[SAS Optimization]]></category>
		<category><![CDATA[SAS Visual Analytics]]></category>
		<category><![CDATA[SAS Viya]]></category>
		<category><![CDATA[scheduling]]></category>
		<category><![CDATA[school reopening]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/operations/?p=2657</guid>

					<description><![CDATA[<p>Public and private schools are struggling to figure out how to bring face-to-face instruction to students during this pandemic. Health risks to students and teachers, parents struggling with child-care options and/or support for virtual learning, and schools’ capacities and budget limitations make this problem a severe logistical challenge. Schools need [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2020/10/26/backtoschooloptimization/">Back to School Optimization</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Public and private schools are struggling to figure out how to bring face-to-face instruction to students during this pandemic. Health risks to students and teachers, parents struggling with child-care options and/or support for virtual learning, and schools’ capacities and budget limitations make this problem a severe logistical challenge. Schools need to abide by CDC social distancing regulations as well as by each individual state’s guidance to minimize exposure and risk. In North Carolina, three different plans were considered by the state government:</p>
<ul>
<li>Plan A: All students back to school with minimal social distancing</li>
<li>Plan B: Schools must limit the number of students and staff to ensure six feet of separation when stationary</li>
<li>Plan C: Remote-only learning</li>
</ul>
<p>In July 2020, <a href="https://www.ednc.org/nc-governor-announces-what-schools-will-look-like-in-the-fall-reopen-2020/" target="_blank" rel="noopener noreferrer">Governor Cooper announced guidance</a> for all schools to operate under Plans B or C. On September 17th, he issued a <a href="https://governor.nc.gov/news/public-schools-now-able-implement-plan-elementary-schools" target="_blank" rel="noopener noreferrer">statement</a> allowing elementary schools to open under Plan A, reiterating that Plan A might not be right for all schools and each district is in control of their reopening plan.</p>
<p>Most school districts are considering Plan B, which presents specific challenges to school administrators. One of the challenges is to find the best possible schedule for groups of students assigned to classrooms and matching teachers to those groups such that state and federal mandates are respected. Given the number of possible combinations of schedules, this is not a trivial problem to solve. Fortunately, operations research is the best analytical tool to support decision makers with data insights and scenario recommendations for this case.</p>
<p>In this article, we consider Plan B and propose a mixed integer linear programming (MILP) model that maximizes the number of student hours of in-class instruction, given restricted room capacities due to social distancing requirements.</p>
<h1><strong>Problem Description</strong></h1>
<p>In this section, we describe the structure of the problem. Let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="S = \{1,2,\dots,\text{n}_{S}\}" /></span><script type='math/tex'>S = \{1,2,\dots,\text{n}_{S}\}</script> be the set of schools in a school district. The set of schools includes elementary, middle, and high school grade levels in that school district. We assume that the students are not transferrable between schools and the schools do not share room spaces. Thus, the optimization model can be defined independently of the school index and is executed in parallel for the schools in <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="S" /></span><script type='math/tex'>S</script>. Let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="G = \{1,2,\dots,\text{n}_{G}\}" /></span><script type='math/tex'>G = \{1,2,\dots,\text{n}_{G}\}</script> be the set of grades, and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="R = \{1,2,\dots,\text{n}_{R}\}" /></span><script type='math/tex'>R = \{1,2,\dots,\text{n}_{R}\}</script> be the set of rooms in a school <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="s \in S" /></span><script type='math/tex'>s \in S</script>.  Due to the social distancing requirement, the rooms have a maximum occupancy restriction that is lower than its original capacity. Let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{capacity}_{r}" /></span><script type='math/tex'>\text{capacity}_{r}</script> be the restricted capacity of room <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r \in R" /></span><script type='math/tex'>r \in R</script>, and let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{population}_{g}" /></span><script type='math/tex'>\text{population}_{g}</script> be the number of students enrolled in grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g \in G" /></span><script type='math/tex'>g \in G</script>. </p>
<p>The scheduling time horizon is divided into time blocks <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="B = \{1,2,\dots,\text{n}_{B}\}" /></span><script type='math/tex'>B = \{1,2,\dots,\text{n}_{B}\}</script>. We consider three different time horizon scenarios (all under Plan B): monthly rotating, weekly rotating, and daily rotating. Figure 1 shows the different time horizon scenarios. Under the <em>monthly rotation</em> scenario, students attend full day school during certain weeks of a month and do remote learning for the rest of the time. For example (as shown in Figure 1 below), 1<sup>st</sup> grade might be scheduled to come on the third and fourth week of each month. Under the <em>weekly rotation</em> scenario, students attend full day school during certain days of a week, every week. For example, 3<sup>rd</sup> grade might be scheduled to come to school on Tuesdays and Thursdays. Under the <em>daily rotation</em> scenario, students attend school daily, but for a predefined block of time. For example, 5<sup>th</sup> grade might be scheduled to attend school between 8 and 10am each day.</p>
<figure id="attachment_2996" aria-describedby="caption-attachment-2996" style="width: 1753px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2022/12/Figure_1-ConsolidatedImage.png"><img loading="lazy" decoding="async" class="wp-image-2996 size-full" src="https://blogs.sas.com/content/operations/files/2022/12/Figure_1-ConsolidatedImage.png" alt="" width="1753" height="427" srcset="https://blogs.sas.com/content/operations/files/2022/12/Figure_1-ConsolidatedImage.png 1753w, https://blogs.sas.com/content/operations/files/2022/12/Figure_1-ConsolidatedImage-300x73.png 300w, https://blogs.sas.com/content/operations/files/2022/12/Figure_1-ConsolidatedImage-1024x249.png 1024w, https://blogs.sas.com/content/operations/files/2022/12/Figure_1-ConsolidatedImage-768x187.png 768w, https://blogs.sas.com/content/operations/files/2022/12/Figure_1-ConsolidatedImage-1536x374.png 1536w" sizes="(max-width: 1753px) 100vw, 1753px" /></a><figcaption id="caption-attachment-2996" class="wp-caption-text">Figure 1: Time horizon scenarios</figcaption></figure>
<p>The set <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="B" /></span><script type='math/tex'>B</script> of time blocks for monthly, weekly, and daily plans are defined as {Week1, Week2, Week3, Week4}, {Monday, Tuesday, Wednesday, Thursday, Friday}, and {8am, 9am, 10am, 11am, 12pm, 1pm}, respectively. Let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{duration}_{b}" /></span><script type='math/tex'>\text{duration}_{b}</script> be the duration of each time block <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="b \in B" /></span><script type='math/tex'>b \in B</script>. In this study, we set <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{duration}_{b}" /></span><script type='math/tex'>\text{duration}_{b}</script> to be 1. It is defined as a parameter to give flexibility for the user to change if needed.</p>
<p>The proposed model is developed to create a schedule by assigning students in grades to time blocks and rooms. The following assumptions are considered in the development of the proposed model:</p>
<ol>
<li>Students in a grade cannot be split between time blocks. If a grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script> is assigned to a time block <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="b" /></span><script type='math/tex'>b</script>, then all the students in that grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g \ \text{(population}_{g}\text{)}" /></span><script type='math/tex'>g \ \text{(population}_{g}\text{)}</script> should be accommodated in time block <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="b" /></span><script type='math/tex'>b</script>.</li>
<li>Each grade in a school <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="s" /></span><script type='math/tex'>s</script> should attend an equal number of time blocks. Say, in an elementary school, if 1<sup>st</sup> grade attends two time blocks, then all other grades (K, 2<sup>nd</sup>-5<sup>th</sup> grades) should attend two time blocks.</li>
<li>In the <em>daily rotation</em> scenario, the assignment of time blocks for grades should be consecutive as transportation of students multiple times a day is not feasible.</li>
<li>Under the <em>daily rotation</em> scenario, a common break or transition time is added to the schedule to enable transportation, cleaning/sanitation of classrooms, other logistics, etc. Let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{transitionwindow}" /></span><script type='math/tex'>\text{transitionwindow}</script> be the transition time, defined in number of time blocks, in the <em>daily rotation.</em></li>
<li>A fraction of students, denoted by <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{virtualpercent}" /></span><script type='math/tex'>\text{virtualpercent}</script>, attend full online / remote learning sessions. This fraction of students is not included in the scheduling.</li>
</ol>
<h1><strong>Input Data</strong></h1>
<p>This section describes the input data tables required for the optimization model. The input data include three tables: the schools table, the rooms table, and the time blocks table. The schools table contains the population (enrollment) data for each grade at the school. The rooms table contains data on the rooms available in the school along with its restricted capacity (<em>capacity</em> column) and room size (<em>room_size_sqft</em> column).</p>
<p>Figures 2 and 3 shows sample school and room data for an elementary school (School1). We can use the restricted capacity from the <em>capacity</em> column or we can calculate it using the room size and square feet per student guideline. For example, suppose you want to analyze a scenario with 60 square feet per student. In the sample data (Figure 3), room R1 is 923 square feet and can accommodate <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\lfloor 923 / 60\rfloor = 15" /></span><script type='math/tex'>\lfloor 923 / 60\rfloor = 15</script> students.</p>
<figure id="attachment_2717" aria-describedby="caption-attachment-2717" style="width: 400px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/10/Figure_2-SchoolsTable.png"><img loading="lazy" decoding="async" class="wp-image-2717 size-full" src="https://blogs.sas.com/content/operations/files/2020/10/Figure_2-SchoolsTable.png" alt="" width="400" height="201" srcset="https://blogs.sas.com/content/operations/files/2020/10/Figure_2-SchoolsTable.png 560w, https://blogs.sas.com/content/operations/files/2020/10/Figure_2-SchoolsTable-300x151.png 300w, https://blogs.sas.com/content/operations/files/2020/10/Figure_2-SchoolsTable-164x82.png 164w" sizes="(max-width: 400px) 100vw, 400px" /></a><figcaption id="caption-attachment-2717" class="wp-caption-text">Figure 2: Schools table</figcaption></figure>
<figure id="attachment_2729" aria-describedby="caption-attachment-2729" style="width: 736px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/10/Figure_3-RoomsTable.png"><img loading="lazy" decoding="async" class="wp-image-2729 size-full" src="https://blogs.sas.com/content/operations/files/2020/10/Figure_3-RoomsTable.png" alt="" width="736" height="601" srcset="https://blogs.sas.com/content/operations/files/2020/10/Figure_3-RoomsTable.png 736w, https://blogs.sas.com/content/operations/files/2020/10/Figure_3-RoomsTable-300x245.png 300w, https://blogs.sas.com/content/operations/files/2020/10/Figure_3-RoomsTable-168x137.png 168w" sizes="(max-width: 736px) 100vw, 736px" /></a><figcaption id="caption-attachment-2729" class="wp-caption-text">Figure 3: Rooms table</figcaption></figure>
<p>The data in the time blocks table depend on the selection of the time horizon scenario. Figure 4 shows a time blocks table for the monthly rotation scenario. It has four time blocks: {Week_1, Week_2, Week_3, Week_4}.</p>
<figure id="attachment_2732" aria-describedby="caption-attachment-2732" style="width: 343px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/10/Figure_4-BlocksTable.png"><img loading="lazy" decoding="async" class="wp-image-2732 size-full" src="https://blogs.sas.com/content/operations/files/2020/10/Figure_4-BlocksTable.png" alt="" width="343" height="146" srcset="https://blogs.sas.com/content/operations/files/2020/10/Figure_4-BlocksTable.png 343w, https://blogs.sas.com/content/operations/files/2020/10/Figure_4-BlocksTable-300x128.png 300w" sizes="(max-width: 343px) 100vw, 343px" /></a><figcaption id="caption-attachment-2732" class="wp-caption-text">Figure 4: Time blocks table</figcaption></figure>
<p>Figures 5 (a) and (b) show the time blocks tables for the weekly and daily rotation scenarios, respectively.</p>
<figure id="attachment_2999" aria-describedby="caption-attachment-2999" style="width: 702px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2022/12/Figure_5-ConsolidatedImage.png"><img loading="lazy" decoding="async" class="wp-image-2999 size-full" src="https://blogs.sas.com/content/operations/files/2022/12/Figure_5-ConsolidatedImage.png" alt="" width="702" height="276" /></a><figcaption id="caption-attachment-2999" class="wp-caption-text">Figure 5: Weekly and Daily rotation scenarios</figcaption></figure>
<h1><strong>Model Formulation</strong></h1>
<p>Considering the student enrollment, and a finite number of rooms with restricted capacity, the problem can be formulated as a mixed integer linear programming (MILP) model, where the objective is to maximize the in-person instructional hours for the students. The decision variables for the model are shown below:</p>
<h3><u>Decision Variables</u></h3>
<ul>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{NumStudents}_{g, r, b} \ge 0 " /></span><script type='math/tex'>\text{NumStudents}_{g, r, b} \ge 0 </script> is the number of students in grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script>, room <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r" /></span><script type='math/tex'>r</script>, and time block <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="b" /></span><script type='math/tex'>b</script></li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{AssignGrRmBl}_{g,r,b} \in \{0,1\} " /></span><script type='math/tex'>\text{AssignGrRmBl}_{g,r,b} \in \{0,1\} </script> is a binary variable indicating if room <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r" /></span><script type='math/tex'>r</script> is in use at time block <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="b" /></span><script type='math/tex'>b</script> for grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script></li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{AssignGrBl}_{g,b} \in \{0,1\} " /></span><script type='math/tex'>\text{AssignGrBl}_{g,b} \in \{0,1\} </script> is a binary variable indicating if grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script> is assigned to time block <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="b" /></span><script type='math/tex'>b</script></li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{AssignGrRm}_{g,r} \in \{0,1\} " /></span><script type='math/tex'>\text{AssignGrRm}_{g,r} \in \{0,1\} </script> is a binary variable indicating if grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script> is assigned to room <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r" /></span><script type='math/tex'>r</script></li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{AvgNumStudents} \ge 0 " /></span><script type='math/tex'>\text{AvgNumStudents} \ge 0 </script> is the average number of student hours per time block in any grade</li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{ConsecutiveGrBl}_{g,b} \in \{0,1\} " /></span><script type='math/tex'>\text{ConsecutiveGrBl}_{g,b} \in \{0,1\} </script> is a binary variable indicating if block <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="b" /></span><script type='math/tex'>b</script> is the starting time block for grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script><br />
Note that <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{ConsecutiveGrBl}_{g,b}" /></span><script type='math/tex'>\text{ConsecutiveGrBl}_{g,b}</script> is used only in the daily rotation scenario where assignment of time blocks for grades must be consecutive.</li>
</ul>
<h3><u>Objective Function</u></h3>
<p>The objective function <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(M1)}" /></span><script type='math/tex'>\text{(M1)}</script> is to maximize the instructional hours for the students and can be written as:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(M1) maximize TotalStudentHours}=\sum\limits_{g,r,b} \text{duration}_b \ \text{NumStudents}_{g,r,b} " /></span><script type='math/tex'>\text{(M1) maximize TotalStudentHours}=\sum\limits_{g,r,b} \text{duration}_b \ \text{NumStudents}_{g,r,b} </script></p>
<h3><u>Constraints</u></h3>
<p>Constraint (1) is the room capacity constraint. This constraint ensures that the number of students in grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script> assigned to room <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r" /></span><script type='math/tex'>r</script> in time block <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="b" /></span><script type='math/tex'>b</script> should not exceed the capacity of the room <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r" /></span><script type='math/tex'>r</script>. Recall that the capacity of each room has already been modified to allow for social distancing restrictions. When <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{AssignGrRmBl}_{g,r,b}" /></span><script type='math/tex'>\text{AssignGrRmBl}_{g,r,b}</script> is zero, the right-hand side of the constraint becomes zero and forces <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{NumStudents}_{g, r, b}" /></span><script type='math/tex'>\text{NumStudents}_{g, r, b}</script> to be zero.</p>
<p style="text-align:center"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{NumStudents}_{g,r,b} \leq \text{capacity}_r \ \text{AssignGrRmBl}_{g,r,b} \quad \forall~g,r,b" /></span><script type='math/tex'>\text{NumStudents}_{g,r,b} \leq \text{capacity}_r \ \text{AssignGrRmBl}_{g,r,b} \quad \forall~g,r,b</script> <span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(1)}" /></span><script type='math/tex'>\text{(1)}</script> </span> </p>
<p>The room block assignment constraint (2) ensures that a room <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r" /></span><script type='math/tex'>r</script> in time block <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="b" /></span><script type='math/tex'>b</script> should be assigned to at most one grade.</p>
<p style="text-align:center"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\sum_{g} \text{AssignGrRmBl}_{g,r,b} \leq 1 \quad \forall~r,b" /></span><script type='math/tex'>\sum_{g} \text{AssignGrRmBl}_{g,r,b} \leq 1 \quad \forall~r,b</script> <span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(2)}" /></span><script type='math/tex'>\text{(2)}</script> </span> </p>
<p>Constraint (3) ensures that the number of students assigned in a grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script> and time block <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="b" /></span><script type='math/tex'>b</script> across all rooms should be equal to the enrollment <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="(\text{population}_{g})" /></span><script type='math/tex'>(\text{population}_{g})</script> in that grade attending in-class instruction.</p>
<p style="text-align:center"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\sum_r \text{NumStudents}_{g,r,b} = (1 - \text{virtualpercent}) \ \times \ \text{population}_{g} \ \times \ \text{AssignGrBl}_{g,b} \quad \forall~g,b" /></span><script type='math/tex'>\sum_r \text{NumStudents}_{g,r,b} = (1 - \text{virtualpercent}) \ \times \ \text{population}_{g} \ \times \ \text{AssignGrBl}_{g,b} \quad \forall~g,b</script> <span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(3)}" /></span><script type='math/tex'>\text{(3)}</script> </span> </p>
<p>Constraint (4) ensures that the average number of student hours per block in a grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script> is the same across all grades.</p>
<p style="text-align:center"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\sum_{r,b} \frac{\text{NumStudents}_{g,r,b}}{\text{n}_{b} \ \times \ \text{population}_{g} \ \times \ (1-\text{virtualpercent})} = \text{AvgNumStudents} \quad \forall~g" /></span><script type='math/tex'>\sum_{r,b} \frac{\text{NumStudents}_{g,r,b}}{\text{n}_{b} \ \times \ \text{population}_{g} \ \times \ (1-\text{virtualpercent})} = \text{AvgNumStudents} \quad \forall~g</script> <span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(4)}" /></span><script type='math/tex'>\text{(4)}</script> </span> </p>
<p>Constraints (5) and (6) compute grade-block assignment and grade-room assignment variables from the grade, room, and block assignment variable.</p>
<p style="text-align:center"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{AssignGrRmBl}_{g,r,b} \leq \text{AssignGrBl}_{g,b} \quad \forall~g,r,b" /></span><script type='math/tex'>\text{AssignGrRmBl}_{g,r,b} \leq \text{AssignGrBl}_{g,b} \quad \forall~g,r,b</script> <span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(5)}" /></span><script type='math/tex'>\text{(5)}</script> </span> </p>
<p style="text-align:center"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{AssignGrRmBl}_{g,r,b} \leq \text{AssignGrRm}_{g,r} \quad \forall~g,r,b" /></span><script type='math/tex'>\text{AssignGrRmBl}_{g,r,b} \leq \text{AssignGrRm}_{g,r} \quad \forall~g,r,b</script> <span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(6)}" /></span><script type='math/tex'>\text{(6)}</script> </span> </p>
<h3><u>Constraints specific to the daily rotation scenario</u></h3>
<p>When students are scheduled to attend part-day classes, the constraints (7) through (11) ensure that the students in a grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script> are assigned to consecutive time blocks.</p>
<p style="text-align:center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{ConsecutiveGrBl}_{g,1} = \text{AssignGrBl}_{g,1} \quad \forall~g" /></span><script type='math/tex'>\text{ConsecutiveGrBl}_{g,1} = \text{AssignGrBl}_{g,1} \quad \forall~g</script><span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(7)}" /></span><script type='math/tex'>\text{(7)}</script> </span> </p>
<p style="text-align:center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{ConsecutiveGrBl}_{g,b} \geq \text{AssignGrBl}_{g,b} - \text{AssignGrBl}_{g,b-1} \quad \forall~g, b \in \{2,\dots,\text{n}_{B}\}" /></span><script type='math/tex'>\text{ConsecutiveGrBl}_{g,b} \geq \text{AssignGrBl}_{g,b} - \text{AssignGrBl}_{g,b-1} \quad \forall~g, b \in \{2,\dots,\text{n}_{B}\}</script><span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(8)}" /></span><script type='math/tex'>\text{(8)}</script> </span> </p>
<p style="text-align:center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\sum_{b} \text{ConsecutiveGrBl}_{g,b} \leq 1 \quad \forall~g" /></span><script type='math/tex'>\sum_{b} \text{ConsecutiveGrBl}_{g,b} \leq 1 \quad \forall~g</script><span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(9)}" /></span><script type='math/tex'>\text{(9)}</script> </span> </p>
<p style="text-align:center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{ConsecutiveGrBl}_{g,b} \leq \text{AssignGrBl}_{g,b} \quad \forall~g,b \in \{2,\dots,\text{n}_{B}\}" /></span><script type='math/tex'>\text{ConsecutiveGrBl}_{g,b} \leq \text{AssignGrBl}_{g,b} \quad \forall~g,b \in \{2,\dots,\text{n}_{B}\}</script><span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(10)}" /></span><script type='math/tex'>\text{(10)}</script> </span> </p>
<p style="text-align:center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{ConsecutiveGrBl}_{g,b} \leq 1 - \text{AssignGrBl}_{g,b-1} \quad \forall~g,b \in \{2,\dots,\text{n}_{B}\}" /></span><script type='math/tex'>\text{ConsecutiveGrBl}_{g,b} \leq 1 - \text{AssignGrBl}_{g,b-1} \quad \forall~g,b \in \{2,\dots,\text{n}_{B}\}</script><span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(11)}" /></span><script type='math/tex'>\text{(11)}</script> </span> </p>
<p>Constraint (12) ensures that there is a break for <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{transitionwindow}" /></span><script type='math/tex'>\text{transitionwindow}</script> time blocks for cleaning and/or transportation.</p>
<p style="text-align:center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{AssignGrBl}_{g1, b-\text{transitionwindow}} \leq 1- \text{ConsecutiveGrBl}_{g,b} \quad \forall~g,g1 \in G, \text{and } b \in \{1+\text{transitionwindow},\dots,\text{n}_{B}\}" /></span><script type='math/tex'>\text{AssignGrBl}_{g1, b-\text{transitionwindow}} \leq 1- \text{ConsecutiveGrBl}_{g,b} \quad \forall~g,g1 \in G, \text{and } b \in \{1+\text{transitionwindow},\dots,\text{n}_{B}\}</script><span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(12)}" /></span><script type='math/tex'>\text{(12)}</script> </span> </p>
<p>We will now discuss an optional constraint that tightens constraint (12) and improves computational time of the model. Let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="Q = \{1,2,\dots,\text{n}_{G}\}" /></span><script type='math/tex'>Q = \{1,2,\dots,\text{n}_{G}\}</script> be the reordered set of grades, where <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{population}_{1} \leq \text{population}_{2} \le \dots \leq \text{population}_{n_{G}}" /></span><script type='math/tex'>\text{population}_{1} \leq \text{population}_{2} \le \dots \leq \text{population}_{n_{G}}</script>. Let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{cum_population}_{q}= \sum_{i=1}^{q} \text{population}_{i}" /></span><script type='math/tex'>\text{cum_population}_{q}= \sum_{i=1}^{q} \text{population}_{i}</script> be the cumulative sum of enrollments for the smallest <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="q" /></span><script type='math/tex'>q</script> grades. Let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{totcapacity}" /></span><script type='math/tex'>\text{totcapacity}</script> be the total capacity of the school across all rooms. Let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{MaxGradesBlock} = \text{max} \{q \in \text{Q: } \text{cum_population}_{q} \leq \text{totcapacity} \}" /></span><script type='math/tex'>\text{MaxGradesBlock} = \text{max} \{q \in \text{Q: } \text{cum_population}_{q} \leq \text{totcapacity} \}</script> be the maximum number of grades in a time block. Constraint (13) ensures that no grades are assigned to the break period.</p>
<p style="text-align:center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\sum_{g1 \in G} \text{AssignGrBl}_{g1, b-\text{transitionwindow}} \leq \text{MaxGradesBlock} \ \times \ (1- \text{ConsecutiveGrBl}_{g,b}) \quad \forall~g,b \in \{1+\text{transitionwindow},\dots,\text{n}_{B}\}" /></span><script type='math/tex'>\sum_{g1 \in G} \text{AssignGrBl}_{g1, b-\text{transitionwindow}} \leq \text{MaxGradesBlock} \ \times \ (1- \text{ConsecutiveGrBl}_{g,b}) \quad \forall~g,b \in \{1+\text{transitionwindow},\dots,\text{n}_{B}\}</script><span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(13)}" /></span><script type='math/tex'>\text{(13)}</script> </span> </p>
<p>Constraints (14) and (15) ensure that the students assigned to room <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r" /></span><script type='math/tex'>r</script> in grade <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script> do not change rooms. There is no reason for a grade to switch rooms during the day, because room capacities per time block and number of students per grade are constant. These constraints force grades to stay in the same rooms once they have been assigned. Note that the constraint (14), which ensures students to stay in the same room, is valid only if the <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{transitionwindow}" /></span><script type='math/tex'>\text{transitionwindow}</script> is at least one period. If the <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{transitionwindow}" /></span><script type='math/tex'>\text{transitionwindow}</script> is zero, it might be optimal to move students to a different room in a time block when a new grade is starting.</p>
<p style="text-align:center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{AssignGrRmBl}_{g,r,b} + 1 \geq \text{AssignGrBl}_{g,b} + \text{AssignGrRm}_{g,r} \quad \forall~g,r,b" /></span><script type='math/tex'>\text{AssignGrRmBl}_{g,r,b} + 1 \geq \text{AssignGrBl}_{g,b} + \text{AssignGrRm}_{g,r} \quad \forall~g,r,b</script><span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(14)}" /></span><script type='math/tex'>\text{(14)}</script> </span> </p>
<p style="text-align:center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{AssignGrRmBl}_{g,r,b} \leq \text{NumStudents}_{g,r,b} \quad \forall~g,r,b" /></span><script type='math/tex'>\text{AssignGrRmBl}_{g,r,b} \leq \text{NumStudents}_{g,r,b} \quad \forall~g,r,b</script><span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(15)}" /></span><script type='math/tex'>\text{(15)}</script> </span> </p>
<h3><u>Special case</u></h3>
<p>When solving for the daily rotation scenario and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{transitionwindow}" /></span><script type='math/tex'>\text{transitionwindow}</script> = 0, the assigned time blocks for two grades can overlap. For example, in the optimal solution 1<sup>st</sup> grade could attend school between 8am - 11am and 2<sup>nd</sup> grade could attend between 10am - 1pm. It might be beneficial for 1<sup>st</sup> grade students to change rooms at 10am in order to accommodate the 2<sup>nd</sup> grade. Therefore, when solving the daily rotation scenario and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{transitionwindow}" /></span><script type='math/tex'>\text{transitionwindow}</script> = 0, we drop constraints (12) and (13) because they are not relevant, and we drop constraint (14) because we want to allow students to change rooms. We also add a secondary objective <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(M2)}" /></span><script type='math/tex'>\text{(M2)}</script> to minimize the number of room changes across all grades.</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(M2) minimize RoomChanges}=\sum\limits_{g,r} \text{AssignGrRm}_{g,r}" /></span><script type='math/tex'>\text{(M2) minimize RoomChanges}=\sum\limits_{g,r} \text{AssignGrRm}_{g,r}</script></p>
<p>We solve a bi-criteria problem using a sequential approach – solve first for (M1), and then solve for (M2) with a constraint on (M1). Let <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{primary_objective_value}" /></span><script type='math/tex'>\text{primary_objective_value}</script> be the objective value obtained when solving for (M1). Constraint (16) is added when we solve for minimizing room changes (M2):</p>
<p style="text-align:center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\sum\limits_{g,r,b} \text{duration}_{b} \ \text{NumStudents}_{g,r,b}  \geq \text{primary_objective_value}" /></span><script type='math/tex'>\sum\limits_{g,r,b} \text{duration}_{b} \ \text{NumStudents}_{g,r,b}  \geq \text{primary_objective_value}</script><span style="float:right"> <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{(16)}" /></span><script type='math/tex'>\text{(16)}</script> </span> </p>
<h1><strong>Solving the Model</strong></h1>
<p>The model is coded and solved using the <a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casactmopt&amp;docsetTarget=cas-optimization-runoptmodel.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">runOptmodel CAS action</a> in <a href="https://www.sas.com/en_us/software/optimization.html" target="_blank" rel="noopener noreferrer">SAS Optimization</a>. We start by defining the index sets, parameters, and read data into the index sets and parameters by using the <a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_optmodel_syntax11.htm&amp;locale=en#casmopt.optmodel.npxreadstmt" target="_blank" rel="noopener noreferrer">READ DATA </a> statements. The decision variables are then declared by using the index sets.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc cas</span>;
   loadactionset <span style="color: #a020f0;">'optimization'</span>;
   <span style="color: #000080; font-weight: bold;">run</span>;
   source pgm;
&nbsp;
   <span style="color: #006400; font-style: italic;">/*************************************************/</span>
   <span style="color: #006400; font-style: italic;">/* Define sets                                   */</span>
   <span style="color: #006400; font-style: italic;">/*************************************************/</span>
   <span style="color: #0000ff;">set</span> &lt;str&gt; ROOMS;
   <span style="color: #0000ff;">set</span> &lt;str&gt; GRADES;
   <span style="color: #0000ff;">set</span> &lt;num&gt; BLOCKS;
&nbsp;
   <span style="color: #006400; font-style: italic;">/*************************************************/</span>
   <span style="color: #006400; font-style: italic;">/* Define inputs                                 */</span>
   <span style="color: #006400; font-style: italic;">/*************************************************/</span>
   str block_id <span style="color: #66cc66;">&#123;</span>BLOCKS<span style="color: #66cc66;">&#125;</span>;
   num duration <span style="color: #66cc66;">&#123;</span>BLOCKS<span style="color: #66cc66;">&#125;</span>;
   num capacity <span style="color: #66cc66;">&#123;</span>ROOMS<span style="color: #66cc66;">&#125;</span>;
   num population <span style="color: #66cc66;">&#123;</span>GRADES<span style="color: #66cc66;">&#125;</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/*************************************************/</span>
   <span style="color: #006400; font-style: italic;">/* Read data                                     */</span>
   <span style="color: #006400; font-style: italic;">/*************************************************/</span>
   read <span style="color: #000080; font-weight: bold;">data</span> <span style="color: #0000ff; font-weight: bold;">&amp;caslib</span>..input_room_data <span style="color: #0000ff;">into</span> ROOMS=<span style="color: #66cc66;">&#91;</span>roomID<span style="color: #66cc66;">&#93;</span> capacity;
   read <span style="color: #000080; font-weight: bold;">data</span> <span style="color: #0000ff; font-weight: bold;">&amp;caslib</span>..input_school_data <span style="color: #0000ff;">into</span> GRADES=<span style="color: #66cc66;">&#91;</span>grade<span style="color: #66cc66;">&#93;</span> population; 
   read <span style="color: #000080; font-weight: bold;">data</span> <span style="color: #0000ff; font-weight: bold;">&amp;caslib</span>..input_block_data <span style="color: #0000ff;">into</span> BLOCKS=<span style="color: #66cc66;">&#91;</span>block<span style="color: #66cc66;">&#93;</span> block_id duration;
&nbsp;
   <span style="color: #006400; font-style: italic;">/*************************************************/</span>
   <span style="color: #006400; font-style: italic;">/* Decision Variables                            */</span>
   <span style="color: #006400; font-style: italic;">/*************************************************/</span>
   <span style="color: #0000ff;">var</span> NumStudents <span style="color: #66cc66;">&#123;</span>GRADES, ROOMS, BLOCKS<span style="color: #66cc66;">&#125;</span> &gt;= <span style="color: #2e8b57; font-weight: bold;">0</span>;
   <span style="color: #0000ff;">var</span> AssignGrRmBl <span style="color: #66cc66;">&#123;</span>GRADES, ROOMS, BLOCKS<span style="color: #66cc66;">&#125;</span> binary;
   <span style="color: #0000ff;">var</span> AssignGrBl <span style="color: #66cc66;">&#123;</span>GRADES, BLOCKS<span style="color: #66cc66;">&#125;</span> binary;
   <span style="color: #0000ff;">var</span> AssignGrRm <span style="color: #66cc66;">&#123;</span>GRADES, ROOMS<span style="color: #66cc66;">&#125;</span> binary;
   <span style="color: #0000ff;">var</span> AvgNumStudents &gt;= <span style="color: #2e8b57; font-weight: bold;">0</span>;
   <span style="color: #0000ff;">var</span> ConsecutiveGrBl <span style="color: #66cc66;">&#123;</span>GRADES, BLOCKS<span style="color: #66cc66;">&#125;</span> binary;</pre></td></tr></table></div>

<p>The parameters needed to compute maxGradesinBlock are calculated in a DATA step prior to calling PROC CAS. As discussed, the maxGradesinBlock parameter is used in constraint (13) to improve the computation time.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   <span style="color: #006400; font-style: italic;">/* Number of grades in a block */</span> 
   num totcapacity = <span style="color: #0000ff;">sum</span> <span style="color: #66cc66;">&#123;</span>r <span style="color: #0000ff;">in</span> ROOMS<span style="color: #66cc66;">&#125;</span> capacity<span style="color: #66cc66;">&#91;</span>r<span style="color: #66cc66;">&#93;</span>;
   num cum_population<span style="color: #66cc66;">&#123;</span>GRADES<span style="color: #66cc66;">&#125;</span>;
   num grade_count<span style="color: #66cc66;">&#123;</span>GRADES<span style="color: #66cc66;">&#125;</span>;
&nbsp;
   read <span style="color: #000080; font-weight: bold;">data</span> <span style="color: #0000ff; font-weight: bold;">&amp;caslib</span>..output_grades_in_block <span style="color: #0000ff;">into</span> <span style="color: #66cc66;">&#91;</span>grade<span style="color: #66cc66;">&#93;</span> cum_population grade_count;
   num maxGradesinBlock = <span style="color: #0000ff;">max</span> <span style="color: #66cc66;">&#123;</span>q <span style="color: #0000ff;">in</span> GRADES: cum_population<span style="color: #66cc66;">&#91;</span>q<span style="color: #66cc66;">&#93;</span> &lt;= totcapacity<span style="color: #66cc66;">&#125;</span> grade_count<span style="color: #66cc66;">&#91;</span>q<span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>

<p>The objective of the problem is to maximize the instructional hours for the students and is written as follows:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">   /*************************************************/
   /* Objective Functions                           */
   /*************************************************/
   /* Objective function returns the total student hours (weighted by duration of the block) in the classroom */
   max TotalStudentsHours = sum {g in GRADES, r in ROOMS, b in BLOCKS} duration[b] * NumStudents[g, r, b];</pre></td></tr></table></div>

<p> The RoomChanges objective function is used only under the special case and is written as follows: </p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   <span style="color: #006400; font-style: italic;">/* Adding a small penalty for choosing different rooms */</span>
   <span style="color: #0000ff;">min</span> RoomChanges = <span style="color: #0000ff;">sum</span> <span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GRADES, r <span style="color: #0000ff;">in</span> ROOMS<span style="color: #66cc66;">&#125;</span> AssignGrRm<span style="color: #66cc66;">&#91;</span>g, r<span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>

<p>Next, we show the sets of constraints in the model. The constraints are shown in the order discussed in the model formulation section. As you can observe, both <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{virtualpercent}" /></span><script type='math/tex'>\text{virtualpercent}</script> and  <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{transitionwindow}" /></span><script type='math/tex'>\text{transitionwindow}</script> parameters are defined as macro variables (<span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{virtual_percent}" /></span><script type='math/tex'>\text{virtual_percent}</script>, <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{transition_window}" /></span><script type='math/tex'>\text{transition_window}</script>) and can be modified by the user.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">   /*************************************************/
   /* Constraints                                   */
   /*************************************************/
   /********************* Room Capacity related constraints *********************/  
   /* Students assigned to a grade,block,room should not exceed the capacity the room. */
   con GradeRoomBlockAssignment {g in GRADES, r in ROOMS, b in BLOCKS}:
      NumStudents[g, r, b] &lt;= capacity[r] * AssignGrRmBl[g, r, b];
&nbsp;
   /* Room/block assigned to a grade should be less than or equal to 1. - A room can be assigned to a max. of one grade in a time block */
   con RoomBlockAssignment {r in ROOMS, b in BLOCKS}:
      sum {g in GRADES} AssignGrRmBl[g, r, b] &lt;= 1;
&nbsp;
   /********************* Number of students constraints *********************/  
   /* Number of students in a grade, block across all rooms should be equal to the population in that grade */
   con GradePop {g in GRADES, b in BLOCKS}:
      sum {r in ROOMS} NumStudents[g, r, b] = (1 - (&amp;virtual_percent. / 100)) * population[g] * AssignGrBl[g, b];
&nbsp;
   /********************* Student Equality constraints *********************/  
   /* Computing average number students attending in a grade and Ensuring that the AvgNumStu is same across all grades*/
   con GradeAvg {g in GRADES}:  
      sum {r in ROOMS, b in BLOCKS} NumStudents[g, r, b] / (card(BLOCKS)* population[g] * (1 - (&amp;virtual_percent. / 100))) = AvgNumStudents;
&nbsp;
   /********************* Deriving Grade-Block and Grade-Room variables constraints **********************/  
   /* Deducing grade, block assignment from grade,block,room assignment. */
   con GradeBlockAssignment {g in GRADES, r in ROOMS, b in BLOCKS}:
      AssignGrRmBl[g, r, b] &lt;= AssignGrBl[g, b];
&nbsp;
   /* Deducing grade, room assignment from grade,block,room assignment. */
   con GradeRoomAssignment {g in GRADES, r in ROOMS, b in BLOCKS}:
      AssignGrRmBl[g, r, b] &lt;= AssignGrRm[g, r];
&nbsp;
   /********************* Constraints specific to Plan 4 *********************/
   /* Constraint that ensures a grade is assigned only to continuous blocks */
   con ConsFirstBlock {g in GRADES}:
      ConsecutiveGrBl[g, 1] = AssignGrBl[g, 1];
&nbsp;
   con ConsPattern {g in GRADES, b in 2..card(BLOCKS)}:
      ConsecutiveGrBl[g, b] &gt;= AssignGrBl[g, b] - AssignGrBl[g, b-1];
&nbsp;
   con ConsPatternRes {g in GRADES}:
      sum {b in BLOCKS} ConsecutiveGrBl[g, b] &lt;= 1;
&nbsp;
   /* If grade g is assigned to block b-1 or if grade g is not assigned to block b, then force ConsecutiveGrBl[g, b] to be 0 */
   con ConsAddCuts {g in GRADES, b in 2..card(BLOCKS)}:
      ConsecutiveGrBl[g, b] &lt;= AssignGrBl[g, b];
&nbsp;
   con ConsAddCuts1 {g in GRADES, b in 2..card(BLOCKS)}:
      ConsecutiveGrBl[g, b] &lt;= 1 - AssignGrBl[g, b-1];
&nbsp;
   /* Breaks in between for cleaning */
   con Breakconstraint {g in GRADES, g1 in GRADES, b in (1+&amp;transition_window.) ..card(BLOCKS)}:
      AssignGrBl[g1, (b-&amp;transition_window.)] &lt;= 1 - ConsecutiveGrBl[g, b];
&nbsp;
   con Breakconstraint1 {g in GRADES, b in (1+&amp;transition_window.) ..card(BLOCKS)}:
      sum {g1 in GRADES} AssignGrBl[g1, (b-&amp;transition_window.)] &lt;= maxGradesinBlock * (1 - ConsecutiveGrBl[g, b]);
&nbsp;
   /* Constraint that ensures students assigned to a room r in a grade g does not change rooms */
   con NoRoomChanges {g in GRADES, r in ROOMS, b in BLOCKS}:
      AssignGrRmBl[g, r, b] + 1 &gt;= AssignGrBl[g, b] + AssignGrRm[g, r];
&nbsp;
   con NoRoomChanges2 {g in GRADES, r in ROOMS, b in BLOCKS}:
      AssignGrRmBl[g, r, b] &lt;= NumStudents[g, r, b];
&nbsp;
   /* Constrain each of the objective functions to prevent worse solutions */
   num primary_objective_value init 0;
&nbsp;
   con PrimaryObjConstraint:
      sum {g in GRADES, r in ROOMS, b in BLOCKS} duration[b] * NumStudents[g, r, b] &gt;= primary_objective_value;</pre></td></tr></table></div>

<p>Next, we discuss the solve statements. We have two solve cases: one for the monthly and weekly rotation scenarios and the other for the daily rotation scenario. The plan_num macro variable is used to define the rotation scenario: plan_num=2 is the monthly rotation scenario, plan_num=3 is the weekly rotation scenario, and plan_num=4 is the daily rotation scenario. For the monthly and weekly rotation scenarios, we drop the constraints that are specific to the daily rotation scenario.<br />
In the special case of daily rotation scenario with <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{transitionwindow}" /></span><script type='math/tex'>\text{transitionwindow}</script>=0, we drop the constraints named Breakconstraint, Breakconstraint1, and NoRoomChanges. We also solve for objective <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{M2}" /></span><script type='math/tex'>\text{M2}</script> (<span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{minimize RoomChanges}" /></span><script type='math/tex'>\text{minimize RoomChanges}</script>), given the optimal value of objective <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{M1}" /></span><script type='math/tex'>\text{M1}</script> (<span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{maximize TotalStudentHours}" /></span><script type='math/tex'>\text{maximize TotalStudentHours}</script>) is preserved. </p>
<p>We are using the <a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_milpsolver_toc.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">MILP solver</a> to solve the problem, allowing a relative objective gap of 1%.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   <span style="color: #006400; font-style: italic;">/*************************************************/</span>
   <span style="color: #006400; font-style: italic;">/* Solve                                         */</span>
   <span style="color: #006400; font-style: italic;">/*************************************************/</span>
   <span style="color: #0000ff;">if</span> <span style="color: #0000ff; font-weight: bold;">&amp;plan_num</span>. = <span style="color: #2e8b57; font-weight: bold;">2</span> <span style="color: #0000ff;">or</span> <span style="color: #0000ff; font-weight: bold;">&amp;plan_num</span>. = <span style="color: #2e8b57; font-weight: bold;">3</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
      <span style="color: #0000ff;">drop</span> Breakconstraint;
      <span style="color: #0000ff;">drop</span> Breakconstraint1;
      <span style="color: #0000ff;">drop</span> ConsFirstBlock;
      <span style="color: #0000ff;">drop</span> ConsPattern;
      <span style="color: #0000ff;">drop</span> ConsPatternRes;
      <span style="color: #0000ff;">drop</span> ConsAddCuts;
      <span style="color: #0000ff;">drop</span> ConsAddCuts1;
      <span style="color: #0000ff;">drop</span> NoRoomChanges;
      <span style="color: #0000ff;">drop</span> PrimaryObjConstraint;
&nbsp;
      solve obj TotalStudentsHours with milp / loglevel=<span style="color: #2e8b57; font-weight: bold;">3</span> relobjgap=<span style="color: #2e8b57; font-weight: bold;">0.01</span>;  
   <span style="color: #0000ff;">end</span>;
&nbsp;
   <span style="color: #0000ff;">if</span> <span style="color: #0000ff; font-weight: bold;">&amp;plan_num</span>. = <span style="color: #2e8b57; font-weight: bold;">4</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
      <span style="color: #0000ff;">drop</span> PrimaryObjConstraint;
&nbsp;
      <span style="color: #0000ff;">if</span> <span style="color: #0000ff; font-weight: bold;">&amp;transition_window</span>. = <span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
         <span style="color: #0000ff;">drop</span> Breakconstraint; 
         <span style="color: #0000ff;">drop</span> Breakconstraint1;
         <span style="color: #0000ff;">drop</span> NoRoomChanges;
      <span style="color: #0000ff;">end</span>;
&nbsp;
      solve obj TotalStudentsHours with milp / loglevel=<span style="color: #2e8b57; font-weight: bold;">3</span> relobjgap=<span style="color: #2e8b57; font-weight: bold;">0.01</span>;
&nbsp;
      <span style="color: #006400; font-style: italic;">/* Cleaning step - before primalin */</span>
      for <span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GRADES, r <span style="color: #0000ff;">in</span> ROOMS, b <span style="color: #0000ff;">in</span> BLOCKS<span style="color: #66cc66;">&#125;</span> AssignGrRmBl<span style="color: #66cc66;">&#91;</span>g, r, b<span style="color: #66cc66;">&#93;</span> = <span style="color: #0000ff;">round</span><span style="color: #66cc66;">&#40;</span>AssignGrRmBl<span style="color: #66cc66;">&#91;</span>g, r, b<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
      for <span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GRADES, b <span style="color: #0000ff;">in</span> BLOCKS<span style="color: #66cc66;">&#125;</span> AssignGrBl<span style="color: #66cc66;">&#91;</span>g, b<span style="color: #66cc66;">&#93;</span> = <span style="color: #0000ff;">round</span><span style="color: #66cc66;">&#40;</span>AssignGrBl<span style="color: #66cc66;">&#91;</span>g, b<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
      for <span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GRADES, r <span style="color: #0000ff;">in</span> ROOMS<span style="color: #66cc66;">&#125;</span> AssignGrRm<span style="color: #66cc66;">&#91;</span>g, r<span style="color: #66cc66;">&#93;</span> = <span style="color: #0000ff;">round</span><span style="color: #66cc66;">&#40;</span>AssignGrRm<span style="color: #66cc66;">&#91;</span>g, r<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
      for <span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GRADES, b <span style="color: #0000ff;">in</span> BLOCKS<span style="color: #66cc66;">&#125;</span> ConsecutiveGrBl<span style="color: #66cc66;">&#91;</span>g, b<span style="color: #66cc66;">&#93;</span> = <span style="color: #0000ff;">round</span><span style="color: #66cc66;">&#40;</span>ConsecutiveGrBl<span style="color: #66cc66;">&#91;</span>g, b<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
      <span style="color: #0000ff;">if</span> <span style="color: #0000ff; font-weight: bold;">&amp;transition_window</span>. = <span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
         <span style="color: #006400; font-style: italic;">/* Solve for secondary objective only if primary objective solve was successful */</span>
         <span style="color: #0000ff;">if</span> _NSOL_ &gt; <span style="color: #2e8b57; font-weight: bold;">0</span> <span style="color: #0000ff;">then</span>
            <span style="color: #0000ff;">do</span>;
            primary_objective_value = TotalStudentsHours.sol;
            restore PrimaryObjConstraint;
            solve obj RoomChanges with milp / primalin loglevel=<span style="color: #2e8b57; font-weight: bold;">3</span> relobjgap=<span style="color: #2e8b57; font-weight: bold;">0.01</span>;
         <span style="color: #0000ff;">end</span>;
      <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">end</span>;</pre></td></tr></table></div>

<p> After we solve the problem, we use a <a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_optmodel_syntax11.htm&amp;locale=en#casmopt.optmodel.npxcreatestmt" target="_blank" rel="noopener noreferrer">CREATE DATA statement</a> to create the output table with all the required data for the output visualization. As mentioned previously, the optimization model can be run independent of the school index and can be executed in parallel for the schools in <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="S" /></span><script type='math/tex'>S</script> (i.e., for the entire school district). We use the <a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casactmopt&amp;docsetTarget=cas-optimization-runoptmodel.htm&amp;locale=en#SAS.cas-optimization-runoptmodel-groupby" target="_blank" rel="noopener noreferrer">groupBy </a> option in the runOptmodel statement to execute the model for multiple schools in parallel. Note that if the input data have only school, the runOptmodel statement is still relevant and is executed for that one school. The <a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casactmopt&amp;docsetTarget=cas-optimization-runoptmodel.htm&amp;locale=en#SAS.cas-optimization-runoptmodel-ngroupbytasks" target="_blank" rel="noopener noreferrer">nGroupByTasks = ‘ALL’</a> option in the runOptmodel statement enables the model to use all the threads on each worker node when optimizing the BY groups in parallel. </p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">   /*************************************************/
   /* Create output data                            */
   /*************************************************/
   num total_capacity = 6 * sum {r in ROOMS} capacity[r];
   num total_population = sum {g in GRADES} population[g];
   num StudentHoursDay = (TotalStudentsHours.sol * 6) / card(BLOCKS);
   num AvgHoursPerStuWeek = (StudentHoursDay*5) / total_population;
   num totHoursStuWeek = (StudentHoursDay*5);
   num num_blocks_scen = card(BLOCKS);
&nbsp;
   num num_students {g in GRADES, r in ROOMS, b in BLOCKS} = round(NumStudents[g,r,b].sol);
   num grade_room_block_assignment {g in GRADES, r in ROOMS, b in BLOCKS} = round(AssignGrRmBl[g,r,b].sol);
   num AssignGrBlSol {g in GRADES, b in BLOCKS} = round(AssignGrBl[g,b].sol);
&nbsp;
   create data &amp;caslib..output_full_assignment from [grade roomID block] = {g in GRADES, r in ROOMS, b in BLOCKS} 
      num_students 
      grade_room_block_assignment
      AssignGrBl = AssignGrBlSol[g,b]
      block_id[b] 
      duration[b] 
      population[g]
      capacity[r]
      total_capacity
      StudentHoursDay
      AvgHoursPerStuWeek
      num_blocks_scen
      total_population
      totHoursStuWeek;
&nbsp;
   endsource;
   runOptmodel / code=pgm groupBy='School_Name' nGroupByTasks='ALL';
   run;
quit;</pre></td></tr></table></div>

<p>Figure 6 shows the optimization log from executing the model for an elementary school under the weekly rotation scenario. The data for this problem are available in <a href="https://github.com/sascommunities/sas-optimization-blog/tree/master/back_to_school_optimization" target="_blank" rel="noopener noreferrer">GitHub</a>. First, the optimization log shows the details of this problem instance such as the numbers of variables and constraints, before and after the <a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_milpsolver_details06.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">presolver step</a>.. Next, you can see the iteration log with information about each iteration until convergence criteria are reached (we specified a relative objective gap of 1%). For this problem instance, the solver was able to find an optimal solution of 2091 student hours in 4 seconds of run time.</p>
<figure id="attachment_3470" aria-describedby="caption-attachment-3470" style="width: 834px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2022/12/Figure6_NewLog.png"><img loading="lazy" decoding="async" class="wp-image-3470 size-full" src="https://blogs.sas.com/content/operations/files/2022/12/Figure6_NewLog.png" alt="" width="834" height="482" srcset="https://blogs.sas.com/content/operations/files/2022/12/Figure6_NewLog.png 834w, https://blogs.sas.com/content/operations/files/2022/12/Figure6_NewLog-300x173.png 300w, https://blogs.sas.com/content/operations/files/2022/12/Figure6_NewLog-768x444.png 768w" sizes="(max-width: 834px) 100vw, 834px" /></a><figcaption id="caption-attachment-3470" class="wp-caption-text">Figure 6: Optimization log</figcaption></figure>
<h1><strong>Visualizing the Output</strong></h1>
<p>To begin analyzing solutions and options for a full school district, we wanted to compare the relevant KPIs such as average weekly in-person instructional hours per student, average room utilization, and teacher workload. We ran the optimization engine described above for the three rotation scenarios (monthly, weekly, and daily) and for different capacity settings (measured as minimum square footage per student). Figure 7 displays the results of these optimization runs. We used <a href="https://www.sas.com/en_us/software/visual-analytics.html" target="_blank" rel="noopener noreferrer">SAS Visual Analytics</a> to build user-friendly visual representations. Note: These results have been fully anonymized and do not represent any specific school or school district. </p>
<figure id="attachment_2966" aria-describedby="caption-attachment-2966" style="width: 702px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2022/12/Figure_8-ResultsConsolidated.png"><img loading="lazy" decoding="async" class="wp-image-2966 size-full" src="https://blogs.sas.com/content/operations/files/2022/12/Figure_8-ResultsConsolidated.png" alt="" width="702" height="432" srcset="https://blogs.sas.com/content/operations/files/2022/12/Figure_8-ResultsConsolidated.png 1159w, https://blogs.sas.com/content/operations/files/2022/12/Figure_8-ResultsConsolidated-300x185.png 300w, https://blogs.sas.com/content/operations/files/2022/12/Figure_8-ResultsConsolidated-1024x630.png 1024w, https://blogs.sas.com/content/operations/files/2022/12/Figure_8-ResultsConsolidated-768x472.png 768w" sizes="(max-width: 702px) 100vw, 702px" /></a><figcaption id="caption-attachment-2966" class="wp-caption-text">Figure 7: Minimum square footage per student vs. student hours per week</figcaption></figure>
<p>In most capacity settings, the weekly rotation outperforms the other scenarios, meaning we are able to deliver more in-person instructional hours while satisfying capacity constraints and better utilizing the limited room availability. Figure 8 shows the scenario for a weekly rotation and 60 square feet per student. Most schools can only accommodate an average of 12 weekly in-person instructional hours while a few schools can accommodate as high as 30 hours. The number of required teachers to generate this schedule has a wider distribution across all schools, varying from 17 to 40. The numbers of hours a teacher works per week varies between 21 and 33.</p>
<figure id="attachment_2972" aria-describedby="caption-attachment-2972" style="width: 702px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2022/12/Figure_9-Results.png"><img loading="lazy" decoding="async" class="wp-image-2972 size-full" src="https://blogs.sas.com/content/operations/files/2022/12/Figure_9-Results.png" alt="" width="702" height="432" /></a><figcaption id="caption-attachment-2972" class="wp-caption-text">Figure 8: KPIs for weekly rotation scenario and 60 square feet per student</figcaption></figure>
<p>Once the school administrators choose a time-horizon plan and the social distancing guideline, we can then drill down into recommend detailed student schedule and classroom assignments. Figure 9 shows the grade and time block schedule for one elementary school under the monthly rotation scenario. In this example, K and 5<sup>th</sup> grade students attend in-person instruction in weeks 1 and 2; 1<sup>st</sup>, 2<sup>nd</sup>, 3<sup>rd</sup>, and 4<sup>th</sup> grade students come to school in weeks 3 and 4.</p>
<figure id="attachment_2936" aria-describedby="caption-attachment-2936" style="width: 741px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2022/12/Figure_10-GradeBlockSchedule.png"><img loading="lazy" decoding="async" class="wp-image-2936 size-full" src="https://blogs.sas.com/content/operations/files/2022/12/Figure_10-GradeBlockSchedule.png" alt="" width="741" height="479" srcset="https://blogs.sas.com/content/operations/files/2022/12/Figure_10-GradeBlockSchedule.png 741w, https://blogs.sas.com/content/operations/files/2022/12/Figure_10-GradeBlockSchedule-300x194.png 300w" sizes="(max-width: 741px) 100vw, 741px" /></a><figcaption id="caption-attachment-2936" class="wp-caption-text">Figure 9: Grade, time block schedule</figcaption></figure>
<p>Figure 10 shows the grade, room, and time block assignment schedule for students. The blue dots in the chart represent the room number where the students are assigned in the corresponding time block.</p>
<figure id="attachment_3017" aria-describedby="caption-attachment-3017" style="width: 1000px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2022/12/Figure_11-GradeRoomBlockSchedule_New.png"><img loading="lazy" decoding="async" class="wp-image-3017 size-large" src="https://blogs.sas.com/content/operations/files/2022/12/Figure_11-GradeRoomBlockSchedule_New.png" alt="" width="1000" height="556" srcset="https://blogs.sas.com/content/operations/files/2022/12/Figure_11-GradeRoomBlockSchedule_New.png 1334w, https://blogs.sas.com/content/operations/files/2022/12/Figure_11-GradeRoomBlockSchedule_New-300x167.png 300w, https://blogs.sas.com/content/operations/files/2022/12/Figure_11-GradeRoomBlockSchedule_New-1024x570.png 1024w, https://blogs.sas.com/content/operations/files/2022/12/Figure_11-GradeRoomBlockSchedule_New-768x427.png 768w" sizes="(max-width: 1000px) 100vw, 1000px" /></a><figcaption id="caption-attachment-3017" class="wp-caption-text">Figure 10: Grade, room, time block schedule</figcaption></figure>
<p>Figure 11 shows the number of teachers required at the school by time block. In this example, the school needs 20 teachers in weeks 1 and 2, and 37 teachers in weeks 3 and 4.</p>
<figure id="attachment_2945" aria-describedby="caption-attachment-2945" style="width: 1872px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2022/12/Figure_12-TeacherSchedule.png"><img loading="lazy" decoding="async" class="wp-image-2945 size-full" src="https://blogs.sas.com/content/operations/files/2022/12/Figure_12-TeacherSchedule.png" alt="" width="1872" height="607" srcset="https://blogs.sas.com/content/operations/files/2022/12/Figure_12-TeacherSchedule-300x98.png 300w, https://blogs.sas.com/content/operations/files/2022/12/Figure_12-TeacherSchedule-1024x333.png 1024w, https://blogs.sas.com/content/operations/files/2022/12/Figure_12-TeacherSchedule-768x250.png 768w" sizes="(max-width: 1872px) 100vw, 1872px" /></a><figcaption id="caption-attachment-2945" class="wp-caption-text">Figure 11: Number of teachers by time block</figcaption></figure>
<h1><strong>Summary</strong></h1>
<p>This article addresses an important and relevant problem of reopening schools and safely bringing students back to in-class instruction. Given a finite number of rooms and maximum occupancy restrictions, this study proposes an optimal schedule to get students back to school in order to maximize the in-person instructional hours of students. The optimization problem is modeled as a mixed integer linear programming model and solved using the runOptmodel action in SAS Optimization. We use realistic data from a school district and execute the model for different time horizon scenarios and capacity settings. We also developed visualizations using SAS Visual Analytics to aggregate and analyze the results from the runs. For a desired scenario, SAS Visual Analytics reports can be used to drill down into detailed schedules for students, classrooms, and teachers. This model supports school administrators in making analytics-based decisions for safely bringing students back to classrooms. We are eager to partner with school organizations to share these insights and potentially aid their decision planning during these challenging times.</p>
<h3>Acknowledgments</h3>
<p>The author would like to thank <a href="https://blogs.sas.com/content/author/micopp/" target="_blank" rel="noopener noreferrer">Michelle Opp</a>, <a href="https://blogs.sas.com/content/author/robpratt/" target="_blank" rel="noopener noreferrer">Rob Pratt</a>, <a href="https://blogs.sas.com/content/author/natalia/" target="_blank" rel="noopener noreferrer">Natalia Summerville</a>, and Matt Fletcher for their contributions to the model formulation and the blog content. </p>
<h3>Additional reading</h3>
<p>A high level description of the problem can be found in the <a href="https://www.linkedin.com/pulse/mathematical-optimization-support-safe-back-to-school-summerville" target="_blank" rel="noopener noreferrer">Mathematical Optimization to Support Safe Back-to-School</a> LinkedIn article.</p>
<h3>Code</h3>
<p>The code and data for this problem are available in GitHub at <br />
<a href="https://github.com/sascommunities/sas-optimization-blog/tree/master/back_to_school_optimization" target="_blank" rel="noopener noreferrer">https://github.com/sascommunities/sas-optimization-blog/tree/master/back_to_school_optimization</a>.</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2020/10/26/backtoschooloptimization/">Back to School Optimization</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></content:encoded>
					
		
		
			<enclosure url="https://blogs.sas.com/content/operations/files/2022/12/Featured-image-Elbow-bumping-with-credits-150x150.jpg"/>
	</item>
		<item>
		<title>Workforce Scheduling at an Animal Shelter</title>
		<link>https://blogs.sas.com/content/operations/2020/09/14/workforce-scheduling-at-an-animal-shelter/</link>
					<comments>https://blogs.sas.com/content/operations/2020/09/14/workforce-scheduling-at-an-animal-shelter/#comments</comments>
		
		<dc:creator><![CDATA[Michelle Opp]]></dc:creator>
		<pubDate>Mon, 14 Sep 2020 13:00:07 +0000</pubDate>
				<category><![CDATA[linear optimization]]></category>
		<category><![CDATA[mixed integer linear optimization]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[DECOMP]]></category>
		<category><![CDATA[MILP]]></category>
		<category><![CDATA[operations research]]></category>
		<category><![CDATA[OPTMODEL]]></category>
		<category><![CDATA[R&D Analytics]]></category>
		<category><![CDATA[SAS Optimization]]></category>
		<category><![CDATA[SAS Visual Analytics]]></category>
		<category><![CDATA[scheduling]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/operations/?p=1996</guid>

					<description><![CDATA[<p>[Nabaruna Karmakar was coauthor of this post] A study was conducted at the University of Denver on The Economic Impacts of the Austin, Texas "No Kill" Resolution. The study found great value in creating an animal welfare-focused community. It highlighted the benefits of economic growth due to an increased need in [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2020/09/14/workforce-scheduling-at-an-animal-shelter/">Workforce Scheduling at an Animal Shelter</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p><em>[<a href="https://blogs.sas.com/content/author/nakarm/" target="_blank" rel="noopener noreferrer">Nabaruna Karmakar</a> was coauthor of this post]</em></p>

<p>A <a href="https://www.nathanwinograd.com/wp-content/uploads/IHAC-MageeAustinNoKillStudy_final.pdf" target="_blank" rel="noopener noreferrer">study</a> was conducted at the University of Denver on The Economic Impacts of the Austin, Texas "No Kill" Resolution. The study found great value in creating an animal welfare-focused community. It highlighted the benefits of economic growth due to an increased need in veterinary care, and the potential for attracting a like-minded, pet-loving workforce for companies located in Austin. The study also spoke about the costs associated with developing such a community – part of which included expenses related to staffing the animal shelters.</p>

<p>Like many non-profits, a shelter’s necessity to keep staffing costs low while scheduling enough employees to cover the demand during peak hours can be a struggle. In many organizations, managers often rely on personal judgment to create staff schedules, leading to higher labor costs in the case of overstaffing, or unmet needs in the case of understaffing.</p>

<p>In this article, we present a mixed integer linear programming (MILP) model to help an animal shelter create an optimal workforce schedule that meets the staffing demands at minimum cost.  With minor tweaks, the model could easily be adapted to many other non-profit or for-profit settings, such as scheduling bartenders, servers, and hosts in a restaurant, or scheduling nurses, nursing assistants, and administrative staff at a COVID-19 testing site.</p>

<h1>Problem Description</h1>

<p>We begin any optimization problem by identifying the three main components: the decisions we need to make, the goal we want to achieve by those decisions, and the constraints that prevent us from making certain decisions. We describe each of these next.</p>

<p>At this animal shelter, we have three types of employees: full-time employees, part-time employees, and volunteers. The animal shelter is open daily from 9 a.m. to 8 p.m. Therefore, our decisions are which employees should be scheduled during each hour of each day in a one-week time frame. Our goal (or <em>objective</em>) is to minimize the total labor cost, which includes regular wages and overtime wages for the full-time and part-time employees. The volunteers have no labor cost.</p>

<p>The constraints are what make the optimization problem interesting. Without constraints, our minimum labor cost would be zero, because we simply wouldn’t schedule any full-time or part-time employees. But we have a limited number of volunteers, and they are not qualified to perform every job at the shelter. Therefore, relying only on volunteers is not an acceptable solution because it leaves many needs unmet. In our problem, the schedule must satisfy the following constraints:
<ol>
 	<li>Full-time employees must be assigned either five or six shifts per week, with each shift being between eight and 10 consecutive hours.</li>
 	<li>Part-time employees must be assigned either five or six shifts per week, with each shift being between four and seven consecutive hours.</li>
 	<li>Volunteers can be scheduled for shifts of up to three consecutive hours, and they can specify the maximum number of days they are willing to volunteer in each week.</li>
 	<li>Full-time and part-time employees must receive overtime pay equal to 1.5 times their hourly wage for any hours worked beyond a specified threshold. For full-time employees, this threshold is 40 hours; for part-time employees, it is 20 hours.</li>
 	<li>Full-time and part-time employees should be given two consecutive days off unless they are assigned overtime, and volunteers should be given two consecutive days off. The employees and volunteers can specify which two days they would like to take off. If no preference is specified for some employees and volunteers, the model chooses two consecutive days off for these employees and volunteers.</li>
 	<li>The shelter has four different jobs that the employees and volunteers must perform: walking, feeding, administrative, and grooming. The schedule must satisfy the demand for each job in each hour of each day.</li>
 	<li>Employees and volunteers have different skill sets, which dictate the set of jobs each person can perform. We assume the jobs are nested, such that an employee who is qualified for one job is also qualified for all the jobs that require a lower skill level. Specifically:
<ul>
 	<li>An employee or volunteer who is qualified for grooming can also perform administrative jobs, feeding, and walking.</li>
 	<li>An employee or volunteer who is qualified for administrative jobs can also perform feeding and walking.</li>
 	<li>An employee or volunteer who is qualified for feeding can also perform walking.</li>
 	<li>An employee or volunteer who is qualified only for walking cannot perform any other jobs.</li>
</ul>
</li>
</ol>
</p>

<h1>Input Data Overview</h1>

<p>Before we describe the mathematical formulation for this optimization problem, let's take a look at the input data in order to better understand the decisions, objective, and constraints described in the previous section. The input data include an Employees table, a Jobs table, a Demand table, and a Demand Coefficients table.</p>

<h2>Employees Table and Jobs Table</h2>

<p>The Employees table contains the full employee and volunteer roster, consisting of 16 full-time employees, 15 part-time employees, and 10 volunteers. A portion of the table is shown in Figure 1. The <strong>skill</strong> column identifies the employee’s skill level; the numeric values {1, 2, 3, 4} are mapped to the jobs of {Walking, Feeding, Administrative, Grooming} in the Jobs table shown in Figure 2.</p>

<p>The <strong>cost</strong> column in the Employees table contains the hourly wage for each employee; note that the hourly wage for volunteers is always zero. In the <strong>off_days</strong> column, you can see that some employees and volunteers have specified which two consecutive days they would like to receive off, but other employees and volunteers have not indicated a preference and will let the scheduler decide. Finally, the <strong>max_volunteer_days</strong> column indicates the maximum number of days that a volunteer is willing to work each week. In our data, volunteers are willing to work either two or three days per week.</p>

<figure id="attachment_2161" aria-describedby="caption-attachment-2161" style="width: 745px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/07/employees_table.png"><img loading="lazy" decoding="async" class="wp-image-2161 size-full" src="https://blogs.sas.com/content/operations/files/2020/07/employees_table.png" alt="" width="745" height="482" srcset="https://blogs.sas.com/content/operations/files/2020/07/employees_table.png 745w, https://blogs.sas.com/content/operations/files/2020/07/employees_table-300x194.png 300w" sizes="(max-width: 745px) 100vw, 745px" /></a><figcaption id="caption-attachment-2161" class="wp-caption-text">Figure 1: Employees Table</figcaption></figure>

<figure id="attachment_2164" aria-describedby="caption-attachment-2164" style="width: 291px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/07/jobs_table.png"><img loading="lazy" decoding="async" class="wp-image-2164 size-full" src="https://blogs.sas.com/content/operations/files/2020/07/jobs_table.png" alt="" width="291" height="152" /></a><figcaption id="caption-attachment-2164" class="wp-caption-text">Figure 2: Jobs Table</figcaption></figure>

<h2>Demand Table and Demand Coefficients Table</h2>

<p>The Demand table shown in Figure 3 contains a prediction of the number of animals that will be in the animal shelter during each hour of each day of the week; <strong>col9</strong> represents the predicted number of animals at 9 a.m., <strong>col10</strong> represents the predicted number of animals at 10 a.m., and so on. We calculate the demand for the four jobs (Walking, Feeding, Administrative, and Grooming) as a proportion of the expected number of animals in the shelter, and the Demand Coefficients table shown in Figure 4 contains the multipliers that are used to calculate the demand for each job at each time. Note that the demand coefficients do not vary by day of the week, but they do vary by time. For example, feeding happens only between 9 a.m. and noon, and again between 5 p.m. and 8 p.m.</p>

<figure id="attachment_2158" aria-describedby="caption-attachment-2158" style="width: 661px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/07/demand_table.png"><img loading="lazy" decoding="async" class="wp-image-2158 size-full" src="https://blogs.sas.com/content/operations/files/2020/07/demand_table.png" alt="" width="661" height="242" srcset="https://blogs.sas.com/content/operations/files/2020/07/demand_table.png 661w, https://blogs.sas.com/content/operations/files/2020/07/demand_table-300x110.png 300w" sizes="(max-width: 661px) 100vw, 661px" /></a><figcaption id="caption-attachment-2158" class="wp-caption-text">Figure 3: Demand Table</figcaption></figure>

<figure id="attachment_2155" aria-describedby="caption-attachment-2155" style="width: 356px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/07/demand_coef_table.png"><img loading="lazy" decoding="async" class="wp-image-2155 size-full" src="https://blogs.sas.com/content/operations/files/2020/07/demand_coef_table.png" alt="" width="356" height="485" srcset="https://blogs.sas.com/content/operations/files/2020/07/demand_coef_table.png 356w, https://blogs.sas.com/content/operations/files/2020/07/demand_coef_table-220x300.png 220w" sizes="(max-width: 356px) 100vw, 356px" /></a><figcaption id="caption-attachment-2155" class="wp-caption-text">Figure 4: Demand Coefficients Table</figcaption></figure>

<p>The bar chart and accompanying detail table in Figure 5 show the number of employees required to cover each job throughout the week, calculated using both the predicted demand from the Demand table and the job demand coefficients from the Demand Coefficients table.</p>

<figure id="attachment_2032" aria-describedby="caption-attachment-2032" style="width: 804px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/07/Input-Demand2.png"><img loading="lazy" decoding="async" class="wp-image-2032" src="https://blogs.sas.com/content/operations/files/2020/07/Input-Demand2.png" alt="" width="804" height="512" srcset="https://blogs.sas.com/content/operations/files/2020/07/Input-Demand2.png 1220w, https://blogs.sas.com/content/operations/files/2020/07/Input-Demand2-300x191.png 300w, https://blogs.sas.com/content/operations/files/2020/07/Input-Demand2-1024x652.png 1024w" sizes="(max-width: 804px) 100vw, 804px" /></a><figcaption id="caption-attachment-2032" class="wp-caption-text">Figure 5: Demand for each job throughout the week</figcaption></figure>

<h1>Model Formulation</h1>

<p>We use the 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casactmopt&amp;docsetTarget=cas-optimization-runoptmodel.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">runOptmodel CAS action</a>
in
<a href="https://www.sas.com/en_us/software/optimization.html" target="_blank" rel="noopener noreferrer">SAS Optimization</a>
to model and solve this optimization problem, so we begin our code with a call to 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=proccas&amp;docsetTarget=p14hvzwg57dkg5n1fgrugckkgerl.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">PROC CAS</a>
followed by a 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=proccas&amp;docsetTarget=p1nh54k4wc2sgyn1g66ietjwmwsn.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">SOURCE statement</a>
that enables us to assign the OPTMODEL statements that follow to a variable called <strong>pgm</strong>. Then we declare the index sets and parameters that we use to store the data for the problem, and we use 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_optmodel_syntax11.htm&amp;locale=en#casmopt.optmodel.npxreadstmt" target="_blank" rel="noopener noreferrer">READ DATA statements</a>
to read the four input tables and populate the index sets and parameters.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc cas</span>; 
source pgm;
&nbsp;
   num first_hour = <span style="color: #2e8b57; font-weight: bold;">9</span>;
   num last_hour = <span style="color: #2e8b57; font-weight: bold;">19</span>;
   <span style="color: #0000ff;">set</span> &lt;str&gt; EMPLOYEES;
   <span style="color: #0000ff;">set</span> &lt;str&gt; DAYS;
   <span style="color: #0000ff;">set</span> &lt;str&gt; JOBS;
   <span style="color: #0000ff;">set</span> TIMES=<span style="color: #66cc66;">&#123;</span>first_hour..last_hour<span style="color: #66cc66;">&#125;</span>;
&nbsp;
   num demand<span style="color: #66cc66;">&#123;</span>DAYS, TIMES<span style="color: #66cc66;">&#125;</span>;
   num demand_coef<span style="color: #66cc66;">&#123;</span>JOBS, TIMES<span style="color: #66cc66;">&#125;</span>;
   num req_skill<span style="color: #66cc66;">&#123;</span>JOBS<span style="color: #66cc66;">&#125;</span>;
   num base_cost init <span style="color: #2e8b57; font-weight: bold;">1</span>;
   num over_cost init <span style="color: #2e8b57; font-weight: bold;">1.5</span>;
&nbsp;
   num skill<span style="color: #66cc66;">&#123;</span>EMPLOYEES<span style="color: #66cc66;">&#125;</span>;
   num cost<span style="color: #66cc66;">&#123;</span>EMPLOYEES<span style="color: #66cc66;">&#125;</span>;
   num min_shift<span style="color: #66cc66;">&#123;</span>EMPLOYEES<span style="color: #66cc66;">&#125;</span>;
   num max_shift<span style="color: #66cc66;">&#123;</span>EMPLOYEES<span style="color: #66cc66;">&#125;</span>;
   num min_hour<span style="color: #66cc66;">&#123;</span>EMPLOYEES<span style="color: #66cc66;">&#125;</span>;
   num max_hour<span style="color: #66cc66;">&#123;</span>EMPLOYEES<span style="color: #66cc66;">&#125;</span>;
   num max_volunteer_days<span style="color: #66cc66;">&#123;</span>EMPLOYEES<span style="color: #66cc66;">&#125;</span>;
   str type<span style="color: #66cc66;">&#123;</span>EMPLOYEES<span style="color: #66cc66;">&#125;</span>;
   str off_days<span style="color: #66cc66;">&#123;</span>EMPLOYEES<span style="color: #66cc66;">&#125;</span>;
&nbsp;
   read <span style="color: #000080; font-weight: bold;">data</span> casuser.ASO_employees 
      <span style="color: #0000ff;">into</span> EMPLOYEES=<span style="color: #66cc66;">&#91;</span>id<span style="color: #66cc66;">&#93;</span> skill type cost off_days max_volunteer_days;
&nbsp;
   read <span style="color: #000080; font-weight: bold;">data</span> casuser.ASO_jobs 
      <span style="color: #0000ff;">into</span> JOBS=<span style="color: #66cc66;">&#91;</span>job<span style="color: #66cc66;">&#93;</span> req_skill;
&nbsp;
   read <span style="color: #000080; font-weight: bold;">data</span> casuser.ASO_demand
      <span style="color: #0000ff;">into</span> DAYS=<span style="color: #66cc66;">&#91;</span><span style="color: #0000ff;">day</span><span style="color: #66cc66;">&#93;</span> <span style="color: #66cc66;">&#123;</span>t <span style="color: #0000ff;">in</span> TIMES<span style="color: #66cc66;">&#125;</span> &lt;demand<span style="color: #66cc66;">&#91;</span><span style="color: #0000ff;">day</span>,t<span style="color: #66cc66;">&#93;</span>= col<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'col'</span>||t<span style="color: #66cc66;">&#41;</span>&gt;;
&nbsp;
   read <span style="color: #000080; font-weight: bold;">data</span> casuser.ASO_demand_coef
      <span style="color: #0000ff;">into</span> <span style="color: #66cc66;">&#91;</span>job <span style="color: #0000ff;">time</span><span style="color: #66cc66;">&#93;</span> demand_coef;</pre></td></tr></table></div>




<p>Next we construct some additional derived sets that are helpful in modeling the problem. For each employee, we create the following derived sets:
<ul>
 	<li>QUALIFIED_JOBS: The set of jobs that the employee is qualified to perform.</li>
 	<li>EMPLOYEE_REGULAR_DAYS: The set of days that the employee has not specifically requested to take off.</li>
 	<li>EMPLOYEE_OT_DAYS: The set of days that the employee could potentially be assigned an overtime shift. For volunteers, this is an empty set because volunteers are not allowed to work overtime. For part-time and full-time employees, the set depends on whether the employee has requested specific days off. If an employee has requested specific days off, then EMPLOYEE_OT_DAYS contains the requested days off; if an employee is assigned to work on any of these days, it must be an overtime shift. If an employee has not requested specific days off, then EMPLOYEE_OT_DAYS contains the set of all days because any day is a potential overtime day if the employee exceeds the number of regular time shifts.</li>
 	<li>EMPLOYEE_DAYS: The set of days that an employee is eligible to work. This is simply the union of EMPLOYEE_REGULAR_DAYS and EMPLOYEE_OT_DAYS. For part-time and full-time employees, this is the same as the set DAYS, but for volunteers it excludes any days that the volunteer has specifically requested to take off.</li>
</ul>
</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   <span style="color: #0000ff;">set</span> QUALIFIED_JOBS<span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES<span style="color: #66cc66;">&#125;</span> = <span style="color: #66cc66;">&#123;</span>j <span style="color: #0000ff;">in</span> JOBS: skill<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> &gt;= req_skill<span style="color: #66cc66;">&#91;</span>j<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span>;
&nbsp;
   <span style="color: #0000ff;">set</span> EMPLOYEE_REGULAR_DAYS<span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES<span style="color: #66cc66;">&#125;</span> = DAYS diff <span style="color: #66cc66;">&#123;</span><span style="color: #0000ff;">scan</span><span style="color: #66cc66;">&#40;</span>off_days<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span>,<span style="color: #2e8b57; font-weight: bold;">1</span>,<span style="color: #a020f0;">'-'</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#125;</span> diff <span style="color: #66cc66;">&#123;</span><span style="color: #0000ff;">scan</span><span style="color: #66cc66;">&#40;</span>off_days<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span>,<span style="color: #2e8b57; font-weight: bold;">2</span>,<span style="color: #a020f0;">'-'</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#125;</span>;
&nbsp;
   <span style="color: #0000ff;">set</span> EMPLOYEE_OT_DAYS<span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES<span style="color: #66cc66;">&#125;</span> = <span style="color: #0000ff;">if</span> type<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span>=<span style="color: #a020f0;">'Volunteer'</span> <span style="color: #0000ff;">then</span> <span style="color: #66cc66;">&#123;</span><span style="color: #66cc66;">&#125;</span> 
                                          <span style="color: #0000ff;">else</span> <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">if</span> <span style="color: #0000ff;">missing</span><span style="color: #66cc66;">&#40;</span>off_days<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #0000ff;">then</span> DAYS 
                                                <span style="color: #0000ff;">else</span> DAYS diff EMPLOYEE_REGULAR_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
   <span style="color: #0000ff;">set</span> EMPLOYEE_DAYS<span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES<span style="color: #66cc66;">&#125;</span> = EMPLOYEE_REGULAR_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> union EMPLOYEE_OT_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>




<p>The next section of the code populates some parameters with the hard-coded values we are using for the minimum and maximum number of shifts and length of shifts for each type of employee. We could have included additional columns in the Employees table and added these columns to the READ DATA statement to populate the parameters, and the employees could have had different values for the minimum and maximum number of shifts and number of hours per shift. However, since we are assuming constant values for all employees within each employee type, we are populating the values directly within the OPTMODEL code. The 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=lefunctionsref&amp;docsetTarget=p1vjttz6nuankzn1gh4z3wgcu0bf.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">COALESCE function</a>
means that if a Volunteer has a missing value for <strong>max_volunteer_days</strong>, we are assigning a default value of 3.</p>

<p>We also create a parameter called next_day that stores the string corresponding to the next day of the week for each day. We use this parameter to ensure that employees get two consecutive days off even when they haven't requested specific days off.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   for <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
      <span style="color: #0000ff;">if</span> type<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #a020f0;">'Full-Time'</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
         min_shift<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">5</span>;
         max_shift<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">6</span>;
         min_hour<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">8</span>;
         max_hour<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">10</span>;
      <span style="color: #0000ff;">end</span>;
      <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> type<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #a020f0;">'Part-Time'</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
         min_shift<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">5</span>;
         max_shift<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">6</span>;
         min_hour<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">4</span>;
         max_hour<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">7</span>;
      <span style="color: #0000ff;">end</span>;
      <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> type<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #a020f0;">'Volunteer'</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
         min_shift<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">0</span>;
         max_shift<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = coalesce<span style="color: #66cc66;">&#40;</span>max_volunteer_days<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span>,<span style="color: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#41;</span>;
         min_hour<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">0</span>;
         max_hour<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">3</span>;
      <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">end</span>;
&nbsp;
   str next_day<span style="color: #66cc66;">&#123;</span>DAYS<span style="color: #66cc66;">&#125;</span>;
   for <span style="color: #66cc66;">&#123;</span>d <span style="color: #0000ff;">in</span> DAYS<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
      <span style="color: #0000ff;">if</span>      d = <span style="color: #a020f0;">'Mon'</span> <span style="color: #0000ff;">then</span> next_day<span style="color: #66cc66;">&#91;</span>d<span style="color: #66cc66;">&#93;</span> = <span style="color: #a020f0;">'Tue'</span>;
      <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> d = <span style="color: #a020f0;">'Tue'</span> <span style="color: #0000ff;">then</span> next_day<span style="color: #66cc66;">&#91;</span>d<span style="color: #66cc66;">&#93;</span> = <span style="color: #a020f0;">'Wed'</span>;
      <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> d = <span style="color: #a020f0;">'Wed'</span> <span style="color: #0000ff;">then</span> next_day<span style="color: #66cc66;">&#91;</span>d<span style="color: #66cc66;">&#93;</span> = <span style="color: #a020f0;">'Thu'</span>;
      <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> d = <span style="color: #a020f0;">'Thu'</span> <span style="color: #0000ff;">then</span> next_day<span style="color: #66cc66;">&#91;</span>d<span style="color: #66cc66;">&#93;</span> = <span style="color: #a020f0;">'Fri'</span>;
      <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> d = <span style="color: #a020f0;">'Fri'</span> <span style="color: #0000ff;">then</span> next_day<span style="color: #66cc66;">&#91;</span>d<span style="color: #66cc66;">&#93;</span> = <span style="color: #a020f0;">'Sat'</span>;
      <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> d = <span style="color: #a020f0;">'Sat'</span> <span style="color: #0000ff;">then</span> next_day<span style="color: #66cc66;">&#91;</span>d<span style="color: #66cc66;">&#93;</span> = <span style="color: #a020f0;">'Sun'</span>;
      <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> d = <span style="color: #a020f0;">'Sun'</span> <span style="color: #0000ff;">then</span> next_day<span style="color: #66cc66;">&#91;</span>d<span style="color: #66cc66;">&#93;</span> = <span style="color: #a020f0;">'Mon'</span>;
   <span style="color: #0000ff;">end</span>;</pre></td></tr></table></div>




<p>The main decision variables in this problem are Assign_To_Job[<em>i</em>,<em>j</em>,<em>t</em>,<em>d</em>],  and they are binary variables. Assign_To_Job[<em>i</em>,<em>j</em>,<em>t</em>,<em>d</em>] has a value of 1 if employee <em>i</em> is assigned to job <em>j</em> at time <em>t</em> on day <em>d</em>, and it has a value of 0 otherwise. If we know the values of these decision variables, we have everything we need to construct the employee schedule and determine the associated cost. However, the model needs some additional auxiliary variables to force relationships in the constraints; for example, to make sure that each employee satisfies the minimum and maximum number of shifts.</p>

<p> The additional variables are also binary. For example, Assign_To_Regular_Day[<em>i</em>,<em>d</em>] has a value of 1 if employee <em>i</em> is assigned to work a regular shift on day <em>d</em>, and it has a value of 0 otherwise. Likewise, Assign_To_Overtime_Day[<em>i</em>,<em>d</em>] has a value of 1 if employee <em>i</em> is assigned to work an overtime shift on day <em>d</em>, and it has a value of 0 otherwise.</p>

<p>Note that the variables Is_Working_Hour and Is_Working_Day could have been modeled as implicit variables by using the 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_optmodel_syntax03.htm&amp;locale=en#casmopt.optmodel.npximpvarstmt" target="_blank" rel="noopener noreferrer">IMPVAR statement</a>
instead of the VAR statement. Is_Working_Hour[<em>i</em>,<em>t</em>,<em>d</em>] is simply the sum of Assign_To_Job[<em>i</em>,<em>j</em>,<em>t</em>,<em>d</em>] over all qualified jobs <em>j</em>, and Is_Working_Day[<em>i</em>,<em>d</em>] is simply the sum of Assign_To_Regular_Day[<em>i</em>,<em>d</em>] and Assign_To_Overtime_Day[<em>i</em>,<em>d</em>]. We tested both formulations, and for this particular problem, modeling them as explicit variables resulted in a slight improvement in the run time. In general, the explicit variable formulation can reduce the number of constraint coefficients when the variable appears in several places, but it will not always result in faster performance. You should experiment with both formulations in your models if run-time performance is a primary concern.</p>

<p>We use the variables called Two_Days_Off_Start_Day to indicate the first day off in a consecutive two-day period. For example, if Two_Days_Off_Start_Day[<em>i</em>,'Mon'] = 1, it means that employee <em>i</em> is not scheduled on Monday or Tuesday as a regular day. These variables are defined only for employees who did not request specific days off and whose maximum number of shifts per week is greater than three. If an employee works three or fewer days in the week, we know that the employee must already receive at least two consecutive days off. In our problem instance, this is the case only for volunteers.</p>

<p>The Switch variables are needed to make sure that employees work consecutive hours during a shift. For example, if a full-time employee is scheduled for eight hours on Monday, we would not want those hours to be 8 a.m. to 9 a.m., then 11 a.m. to 1 p.m., then 2 p.m. to 4 p.m., then 5 p.m. to 8 p.m. The Switch variables will be explained in more detail when we get to the corresponding constraints.</p>

<p>Finally, we can fix the values of some variables for decisions that we already know must be true. Full-time and part-time employees must work a minimum of five days. If an employee has requested two specific days off, then we know which five days must be worked as regular shifts before any overtime shifts can be assigned to the employee on one of the requested days off. In general, if the number of regular days available for an employee is equal to the minimum number of days the employee must work, then we know the employee must be assigned to work on each available regular day. Therefore, we can fix both Assign_To_Regular_Day[<em>i</em>,<em>d</em>] and Is_Working_Day[<em>i</em>,<em>d</em>] to 1 for these days.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   <span style="color: #006400; font-style: italic;">/******************************** Variables *******************************/</span>
&nbsp;
   <span style="color: #0000ff;">var</span> Assign_To_Job <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES, QUALIFIED_JOBS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span>, TIMES, EMPLOYEE_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> binary;
&nbsp;
   <span style="color: #0000ff;">var</span> Assign_To_Regular_Day <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES, EMPLOYEE_REGULAR_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> binary;
   <span style="color: #0000ff;">var</span> Assign_To_Overtime_Day <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES, EMPLOYEE_OT_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> binary;
&nbsp;
   <span style="color: #0000ff;">var</span> Is_Working_Hour <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES, TIMES, EMPLOYEE_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> binary;
   <span style="color: #0000ff;">var</span> Is_Working_Day <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES, EMPLOYEE_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> binary;
&nbsp;
   <span style="color: #0000ff;">var</span> Two_Days_Off_Start_Day <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES, DAYS: <span style="color: #0000ff;">missing</span><span style="color: #66cc66;">&#40;</span>off_days<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #0000ff;">and</span> max_shift<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span> &gt; <span style="color: #2e8b57; font-weight: bold;">3</span><span style="color: #66cc66;">&#125;</span> binary;
&nbsp;
   <span style="color: #0000ff;">var</span> Switch <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES, TIMES, EMPLOYEE_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> binary;
&nbsp;
   for <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES: card<span style="color: #66cc66;">&#40;</span>EMPLOYEE_REGULAR_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>=min_shift<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
      for <span style="color: #66cc66;">&#123;</span>d <span style="color: #0000ff;">in</span> EMPLOYEE_REGULAR_DAYS<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
         fix Assign_To_Regular_Day<span style="color: #66cc66;">&#91;</span>i,d<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span>;
         fix Is_Working_Day<span style="color: #66cc66;">&#91;</span>i,d<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span>;
      <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">end</span>;</pre></td></tr></table></div>




<p>The objective is to minimize the total cost, which consists of a base cost and an overtime cost. Notice that we are calculating the overtime cost as only the additional premium beyond an employee's regular hourly wage. For example, suppose a full-time employee with an hourly wage of $20 works 48 hours in the week. We are considering the base cost to be $20 * 48, and the overtime cost to be $10 * 8. In some situations, you might want to instead consider the base cost to be $20 * 40 and the overtime cost to be $30 * 8. Since we are minimizing the total cost, both interpretations of overtime cost are equivalent in terms of the objective, and we have chosen the former interpretation because it simplifies the model. If you want to report base and overtime costs differently in the solution, a simple postprocessing step after the SOLVE statement could be used to calculate different costs per employee.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">   /******************************** Objective *******************************/
&nbsp;
   min BaseCost
      = base_cost * sum{i in EMPLOYEES, t in TIMES, d in EMPLOYEE_DAYS[i]} cost[i] * Is_Working_Hour[i,t,d];
&nbsp;
   min OverTimeCost
      = (over_cost - base_cost) * sum {i in EMPLOYEES} 
                                       cost[i] * (sum {t in TIMES, d in EMPLOYEE_DAYS[i]} Is_Working_Hour[i,t,d] 
                                                  - min_shift[i] * min_hour[i]);
&nbsp;
   min TotalCost
      = BaseCost + OverTimeCost;</pre></td></tr></table></div>




<p>Before we move on to the constraints, we first need to say a word about 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_decomp_toc.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">DECOMP</a>.
Most of our constraints are applied individually to each employee, and the only constraints that span multiple employees are the demand satisfaction constraints. Therefore, this problem's structure is a natural fit for using a decomposition algorithm. When we define the constraints, we use a 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_optmodel_details48.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">constraint suffix</a>
to identify each constraint with a particular decomposition block. First, however, we need to define the blocks. We have chosen to define the blocks according to each employee/day pair. We tested blocks at the employee level and at employee/day level and we saw slightly better performance from the employee/day level. In your applications, you might want to experiment with different block assignments to find the one that works best. If you don't know what your blocks should be, you can still use the DECOMP algorithm, which first attempts to identify suitable blocks. But because we already know something about the structure of the constraints, it is helpful to identify them to the DECOMP algorithm. Here we are simply creating a unique numeric index called block_id[<em>i</em>,<em>d</em>] for each [<em>i</em>,<em>d</em>] pair.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   <span style="color: #006400; font-style: italic;">/****************************** Blocks Setup ******************************/</span>
   num block_id<span style="color: #66cc66;">&#123;</span>EMPLOYEES, DAYS<span style="color: #66cc66;">&#125;</span>;
   num id init <span style="color: #2e8b57; font-weight: bold;">0</span>;
   for <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> EMPLOYEES, d <span style="color: #0000ff;">in</span> DAYS<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
      block_id<span style="color: #66cc66;">&#91;</span>i,d<span style="color: #66cc66;">&#93;</span> = id;
      id = id + <span style="color: #2e8b57; font-weight: bold;">1</span>;
   <span style="color: #0000ff;">end</span>;</pre></td></tr></table></div>




<p>Next we describe the constraints. The first few constraints are fairly intuitive. Is_Working_Hour and Is_Working_Day are the variables that we chose to model as explicit variables rather than implicit variables for a slight boost in performance, and the corresponding constraints simply define the relationship between these variables and the other binary variables.</p>

<p>The Satisfy_Demand constraint ensures that we schedule enough employees to cover the requirements for each job during each time period on each day. Assign_Day_If_Assign_Job is a linking constraint that ensures that if an employee is working during any time period of a day, the employee is considered to be working on that day.</p>

<p>Note the <strong>suffixes=(block=block_id[i,d])</strong> option at the end of each of these constraints except the Satisfy_Demand constraint. This is how we identify a constraint with a particular block to be used in the decomposition algorithm&mdash;we assign to the <strong>block</strong> keyword the unique numeric index block_id[<em>i</em>,<em>d</em>] that we created in a previous step. We cannot include this suffix on the Satisfy_Demand constraint because it spans multiple employees, but the other constraints are applied individually to each employee/day pair and therefore can be assigned to the DECOMP blocks.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">   /******************************* Constraints ******************************/
&nbsp;
   con Define_Is_Working_Hour {i in EMPLOYEES, t in TIMES, d in EMPLOYEE_DAYS[i]}:
      Is_Working_Hour[i,t,d] = sum{j in QUALIFIED_JOBS[i]} Assign_To_Job[i,j,t,d]
         suffixes=(block=block_id[i,d]);
&nbsp;
   con Define_Is_Working_Day {i in EMPLOYEES, d in EMPLOYEE_DAYS[i]}:
      Is_Working_Day[i,d] = (if d in EMPLOYEE_REGULAR_DAYS[i] then Assign_To_Regular_Day[i,d]) 
                          + (if d in EMPLOYEE_OT_DAYS[i] then Assign_To_Overtime_Day[i,d])
         suffixes=(block=block_id[i,d]);
&nbsp;
   con Satisfy_Demand {j in JOBS, t in TIMES, d in DAYS}:
      sum {i in EMPLOYEES: j in QUALIFIED_JOBS[i] and d in EMPLOYEE_DAYS[i]} Assign_To_Job[i,j,t,d] 
      &gt;= demand_coef[j,t] * demand[d,t];
&nbsp;
   con Assign_Day_If_Assign_Job {i in EMPLOYEES, j in QUALIFIED_JOBS[i], t in TIMES, d in EMPLOYEE_DAYS[i]}:
      Is_Working_Day[i,d] &gt;= Assign_To_Job[i,j,t,d]
         suffixes=(block=block_id[i,d]);</pre></td></tr></table></div>




<p>The next constraints enforce the minimum and maximum number of shifts (that is, days) per employee, and the minimum and maximum number of hours per shift. Note that Min_Num_Shifts uses the Assign_To_Regular_Day variables, while Max_Num_Shifts uses Is_Working_Day. This is because we want to include the possibility of overtime shifts
in the maximum, but we only want to assign an overtime day if the employee has already been assigned the
minimum number of shifts for regular time. Also note that we cannot use the <strong>suffixes=(block=block_id[i,d])</strong> option on the Min_Num_Shifts and Max_Num_Shifts constraints. Even though these constraints are applied individually for each employee, they span multiple days, and we have defined our decomposition blocks according to employee/day pairs.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">   con Min_Num_Shifts {i in EMPLOYEES: min_shift[i] &gt; 0}:
      sum {d in EMPLOYEE_REGULAR_DAYS[i]} Assign_To_Regular_Day[i,d] &gt;= min_shift[i];
&nbsp;
   con Max_Num_Shifts {i in EMPLOYEES}:
      sum{d in EMPLOYEE_DAYS[i]} Is_Working_Day[i,d] &lt;= max_shift[i]; 
&nbsp;
   con Min_Hours_Per_Shift {i in EMPLOYEES, d in EMPLOYEE_DAYS[i]: min_hour[i] &gt; 0}:
      sum {t in TIMES} Is_Working_Hour[i,t,d] &gt;= min_hour[i] * Is_Working_Day[i,d]
         suffixes=(block=block_id[i,d]);
&nbsp;
   con Max_Hours_Per_Shift {i in EMPLOYEES, d in EMPLOYEE_DAYS[i]}:
      sum {t in TIMES} Is_Working_Hour[i,t,d] &lt;= max_hour[i]
         suffixes=(block=block_id[i,d]);</pre></td></tr></table></div>




<p>The next three constraints are a bit more complicated than the previous constraints. We need to force the hours that an employee works in a day to be consecutive, and for this we use the Switch variables. We are defining Switch[<em>i</em>,<em>t</em>,<em>d</em>] to have a value of 1 if employee <em>i</em> is not working in time <em>t</em> and begins working in time <em>t</em>+1 on day <em>d</em>, and 0 otherwise. We need to create constraints that force Switch to follow this definition, and then we can force Switch[<em>i</em>,<em>t</em>,<em>d</em>] to take a value of 1 for at most one time period <em>t</em>. In other words, we have at most one "switch" from non-working to working on a given day. The comments in the following section of code describe the conditions that we are forcing with each constraint; you can substitute different combinations of values of 0 and 1 for the other variables to understand how Switch[<em>i</em>,<em>t</em>,<em>d</em>] must behave in each case.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">   /* If employee is not working in time t but working in time t+1, force Switch[i,t,d] to be 1. */
   con Force_Switch_to_1 {i in EMPLOYEES, t in TIMES, d in EMPLOYEE_DAYS[i]: t+1 &lt;= last_hour}: 
      Is_Working_Hour[i,t,d] + Switch[i,t,d] &gt;= Is_Working_Hour[i,t+1,d]
         suffixes=(block=block_id[i,d]);
&nbsp;
   /* If employee is working in time t and also working in time t+1, force Switch[i,t,d] to be 0. 
      If employee is not working in time t and also not working in time t+1, force Switch[i,t,d] to be 0.
      If employee is working in time t and not working in time t+1, force Switch[i,t,d] to be 0. */
   con Force_Switch_to_0 {i in EMPLOYEES, t in TIMES, d in EMPLOYEE_DAYS[i]: t+1 &lt;= last_hour}:
      Is_Working_Hour[i,t,d] + 2 * Switch[i,t,d] &lt;= 1 + Is_Working_Hour[i,t+1,d]
         suffixes=(block=block_id[i,d]);
&nbsp;
   /* Allow at most one switch per day. But if the employee begins work in the first hour, don't allow 
      any switches. */
   con Max_One_Switch_Per_Day {i in EMPLOYEES, d in EMPLOYEE_DAYS[i]}:
      sum{t in TIMES: t+1 &lt;= last_hour} Switch[i,t,d] &lt;= 1 - Is_Working_Hour[i,first_hour,d] 
         suffixes=(block=block_id[i,d]);</pre></td></tr></table></div>




<p>Finally, we need to add constraints to make sure that the employees who did request specific days off do get two consecutive days off unless they are assigned overtime. Recall that the binary variable called Two_Days_Off_Start_Day[<em>i</em>,<em>d</em>] has a value of 1 if day <em>d</em> is the first of two consecutive days off for employee <em>d</em>, and 0 otherwise. The first constraint below forces Two_Days_Off_Start_Day to have a value of 0 if either of the two days is assigned as a regular day, and the second constraint forces us to choose at least one day <em>d</em> with Two_Days_Off_Start_day[<em>i</em>,<em>d</em>] = 1. We only need to apply these constraints to the set of employees who did not request specific days off and who can work more than three days per week. If an employee can only work three or fewer days per week, then we know the employee is already receiving two consecutive days off.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">   con Two_Days_Off_Zero_if_Working {i in EMPLOYEES, d in DAYS: missing(off_days[i]) and max_shift[i] &gt; 3}:
      Assign_To_Regular_Day[i,d] + Assign_To_Regular_Day[i,next_day[d]] + 2*Two_Days_Off_Start_Day[i,d] &lt;= 2; 
&nbsp;
   con Force_Two_Days_Off {i in EMPLOYEES: missing(off_days[i]) and max_shift[i] &gt; 3}:
      sum{d in DAYS} Two_Days_Off_Start_Day[i,d] &gt;= 1;</pre></td></tr></table></div>




<h1>Solving the Problem</h1>

<p>After doing the work of modeling this optimization problem, solving it is the easy part! We are using the 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_milpsolver_toc.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">MILP solver</a>
to minimize the TotalCost objective. We have specified a 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_milpsolver_syntax02.htm&amp;locale=en#casmopt.milpsolver.milpmaxtime" target="_blank" rel="noopener noreferrer">maximum time</a>
of 300 seconds. Although this problem solves very quickly in under 30 seconds, it is a good practice to include a time bound in case we decide to modify the problem formulation in the future to accommodate additional constraints or to try different solver options. We are also specifying a 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_milpsolver_syntax02.htm&amp;locale=en#casmopt.milpsolver.milprelobjgap" target="_blank" rel="noopener noreferrer">relative objective gap</a>
of 0.1%.</p>

<p>We are using the 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_decomp_syntax03.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">DECOMP</a> algorithm,
and we specify 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_decomp_syntax03.htm&amp;locale=en#casmopt.decomp.dcmphybrid" target="_blank" rel="noopener noreferrer">HYBRID=FALSE</a>
so the root node processing is handled entirely by the decomposition algorithm, instead of first processing the root node using standard MILP techniques. Recall that we added the <strong>suffixes=(block=block_id[i,d])</strong> option to the decomposable constraints in order to identify the block for each constraint. When blocks are defined, the default DECOMP option is 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_decomp_syntax03.htm&amp;locale=en#casmopt.decomp.dcmpmethod" target="_blank" rel="noopener noreferrer">METHOD=USER</a>,
so we do not need to specify it as an option in the SOLVE statement. </p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   <span style="color: #006400; font-style: italic;">/********************************** Solve *********************************/</span>
&nbsp;
   solve obj TotalCost with milp / maxtime=<span style="color: #2e8b57; font-weight: bold;">300</span> relobjgap=<span style="color: #2e8b57; font-weight: bold;">0.001</span> decomp=<span style="color: #66cc66;">&#40;</span>hybrid=false<span style="color: #66cc66;">&#41;</span>;</pre></td></tr></table></div>




<p>After we solve the problem, we calculate the weekly base cost and overtime cost for each employee, and we use 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_optmodel_syntax11.htm&amp;locale=en#casmopt.optmodel.npxcreatestmt" target="_blank" rel="noopener noreferrer">CREATE DATA statements</a>
to create output tables containing the job assignments and the employee costs.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">   /****************************** Create Output *****************************/
&nbsp;
   num regCost{EMPLOYEES}, OTCost{EMPLOYEES} init 0;
   for {i in EMPLOYEES} do;
      regCost[i] = base_cost * cost[i] * sum {t in TIMES, d in EMPLOYEE_DAYS[i]} 
                                               round(Is_Working_Hour[i,t,d].sol,1);
      OTcost[i] = (over_cost - base_cost) * cost[i] 
                   * (sum {t in TIMES, d in EMPLOYEE_DAYS[i]} round(Is_Working_Hour[i,t,d].sol,1) 
                      - min_shift[i] * min_hour[i]);
   end;
&nbsp;
   create data casuser.job_assignments
      from [employee job time day] = {i in EMPLOYEES, j in QUALIFIED_JOBS[i], t in TIMES, d in EMPLOYEE_DAYS[i]:
                                      Assign_To_Job[i,j,t,d].sol &gt; 0.9};
&nbsp;
   create data casuser.employee_costs
      from [employee] = {i in EMPLOYEES}
         base_cost = regCost[i]
         overtime_cost = OTcost[i];</pre></td></tr></table></div>




<p>As the last step in our code, we need to use an 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=proccas&amp;docsetTarget=n11xljv2nm1tlwn12vhi1rkmm6ob.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">ENDSOURCE statement</a>
to terminate the block of code that is stored in the <strong>pgm</strong> variable. Then we use the 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=proccas&amp;docsetTarget=n0w3x61bw2s5a3n1i2oywybloafc.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">ACTION statement</a>
to call the runOptmodel CAS action from the 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casactmopt&amp;docsetTarget=casactmopt_optimization_toc.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">optimization action set</a>,
and we use <strong>code=pgm</strong> to pass the OPTMODEL statements to the runOptmodel action.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">endsource;
&nbsp;
    action optimization.runOptmodel / code=pgm printlevel=<span style="color: #2e8b57; font-weight: bold;">2</span>; <span style="color: #000080; font-weight: bold;">run</span>; 
&nbsp;
<span style="color: #000080; font-weight: bold;">quit</span>;</pre></td></tr></table></div>




<h1>Optimization Log and Output Tables</h1>

<p>The optimization log is shown below. It first describes the problem structure, including the numbers of variables and constraints both before and after the 
<a href="https://go.documentation.sas.com/?cdcId=pgmsascdc&amp;cdcVersion=9.4_3.5&amp;docsetId=casmopt&amp;docsetTarget=casmopt_milpsolver_details06.htm&amp;locale=en" target="_blank" rel="noopener noreferrer">presolver step</a>.
Next you can see information about the DECOMP algorithm, such as the number of blocks (275) and the decomposition subproblem coverage (approximately 97% of variables and 95% of constraints). Finally, the iteration log shows information about each iteration until we reach the convergence criteria. The optimal total cost for our problem is $22,010.</p>


<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">NOTE: Active Session now MYCASSESSION.
NOTE: There were 41 rows read from table 'ASO_EMPLOYEES' in caslib 'CASUSER'.
NOTE: There were 4 rows read from table 'ASO_JOBS' in caslib 'CASUSER'.
NOTE: There were 7 rows read from table 'ASO_DEMAND' in caslib 'CASUSER'.
NOTE: There were 44 rows read from table 'ASO_DEMAND_COEF' in caslib 'CASUSER'.
NOTE: Problem generation will use 16 threads.
NOTE: The problem has 14957 variables (0 free, 210 fixed).
NOTE: The problem uses 2 implicit variables.
NOTE: The problem has 14957 binary and 0 integer variables.
NOTE: The problem has 18244 linear constraints (3411 LE, 3300 EQ, 11533 GE, 0 range).
NOTE: The problem has 62397 linear constraint coefficients.
NOTE: The problem has 0 nonlinear constraints (0 LE, 0 EQ, 0 GE, 0 range).
NOTE: The remaining solution time after problem generation and solver initialization is 299.65 seconds.
NOTE: The initial MILP heuristics are applied.
NOTE: The MILP presolver value AUTOMATIC is applied.
NOTE: The MILP presolver removed 5994 variables and 11044 constraints.
NOTE: The MILP presolver removed 31843 constraint coefficients.
NOTE: The MILP presolver added 2827 constraint coefficients.
NOTE: The MILP presolver modified 723 constraint coefficients.
NOTE: The presolved problem has 8963 variables, 7200 constraints, and 30554 constraint coefficients.
NOTE: The MILP solver is called.
NOTE: The Decomposition algorithm is used.
NOTE: The Decomposition algorithm is executing in single-machine mode.
NOTE: The DECOMP method value USER is applied.
NOTE: The problem has a decomposable structure with 275 blocks. The largest block covers 0.5556% of the constraints in the problem.
NOTE: The decomposition subproblems cover 8718 (97.27%) variables and 6817 (94.68%) constraints.
NOTE: The deterministic parallel mode is enabled.
NOTE: The Decomposition algorithm is using up to 16 threads.
      Iter         Best       Master         Best       LP       IP  CPU Real
                  Bound    Objective      Integer      Gap      Gap Time Time
NOTE: Starting phase 1.
         1       0.0000     589.0000            . 5.89e+02        .    4    3
        10       0.0000      14.5333            . 1.45e+01        .    5    4
        17       0.0000       0.0000            .    0.01%        .    6    4
        18       0.0000       0.0000            .    0.00%        .    6    4
NOTE: Starting phase 2.
        19   14510.0000   30203.5804            .  108.16%        .    7    5
        20   14510.0000   26027.3000   24617.0000   79.37%   69.66%   11    8
        26   14510.0000   23147.0000   23546.0000   59.52%   62.27%   14   10
        28   15701.9000   22998.5000   23457.5000   46.47%   49.39%   14   10
        30   15701.9000   22900.2500   23214.5000   45.84%   47.85%   14   11
        33   15701.9000   22697.5000   23133.5000   44.55%   47.33%   15   11
        35   15701.9000   22593.5000   23091.5000   43.89%   47.06%   15   11
        38   15701.9000   22541.5000   23046.5000   43.56%   46.78%   16   11
        40   15701.9000   22457.0000   22479.5000   43.02%   43.16%   18   14
        43   15701.9000   22422.5000   22472.0000   42.80%   43.12%   19   14
        46   21627.5000   22422.5000   22442.0000    3.68%    3.77%   19   14
        50   21627.5000   22415.0000   22359.5000    3.64%    3.38%   19   15
        52   21627.5000   22229.5000   22287.5000    2.78%    3.05%   20   15
        54   21627.5000   22182.5000   22251.5000    2.57%    2.89%   20   15
        59   21627.5000   22010.0000   22118.0000    1.77%    2.27%   20   15
        60   22010.0000   22010.0000   22118.0000    0.00%    0.49%   20   15
         .   22010.0000   22010.0000   22118.0000    0.00%    0.49%   21   16
NOTE: Starting branch and bound.
         Node  Active   Sols         Best         Best      Gap    CPU   Real
                                  Integer        Bound            Time   Time
            0       1     26   22118.0000   22010.0000    0.49%     21     16
            7       9     27   22091.0000   22010.0000    0.37%     44     27
            8       0     29   22010.0000   22010.0000    0.00%     44     27
NOTE: The Decomposition algorithm used 16 threads.
NOTE: The Decomposition algorithm time is 27.87 seconds.
NOTE: Optimal.
NOTE: Objective = 22010.
NOTE: The output table 'JOB_ASSIGNMENTS' in caslib 'CASUSER' has 1043 rows and 4 columns.
NOTE: The output table 'EMPLOYEE_COSTS' in caslib 'CASUSER' has 41 rows and 3 columns.
{,,,,,,,status=OK,algorithm=DECOMP,solutionStatus=OPTIMAL,objective=22010,numIterations=223,
presolveTime=1.9837138653,solutionTime=27.870479107,numSolutions=29,primalInf=4.440892E-16,
boundInf=4.440892E-16,bestBound=22010,numNodes=9,relObjGap=0,absObjGap=0,integerInf=4.440892E-16}</pre></td></tr></table></div>




<p>Figure 6 shows a portion of the output table called JOB_ASSIGNMENTS, and Figure 7 shows a portion of the output table called EMPLOYEE_COSTS. In these tables, we have everything we need to create an employee schedule for the week and to calculate the resulting wage costs. However, it is often useful to analyze the solution visually, which we do next.</p>

<figure id="attachment_2233" aria-describedby="caption-attachment-2233" style="width: 499px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/07/job_assignments_table.png"><img loading="lazy" decoding="async" class="wp-image-2233 size-full" src="https://blogs.sas.com/content/operations/files/2020/07/job_assignments_table.png" alt="" width="499" height="488" srcset="https://blogs.sas.com/content/operations/files/2020/07/job_assignments_table.png 499w, https://blogs.sas.com/content/operations/files/2020/07/job_assignments_table-300x293.png 300w" sizes="(max-width: 499px) 100vw, 499px" /></a><figcaption id="caption-attachment-2233" class="wp-caption-text">Figure 6: JOB_ASSIGNMENTS output table</figcaption></figure>

<figure id="attachment_2239" aria-describedby="caption-attachment-2239" style="width: 478px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/07/employee_costs_table.png"><img loading="lazy" decoding="async" class="wp-image-2239 size-full" src="https://blogs.sas.com/content/operations/files/2020/07/employee_costs_table.png" alt="" width="478" height="486" srcset="https://blogs.sas.com/content/operations/files/2020/07/employee_costs_table.png 478w, https://blogs.sas.com/content/operations/files/2020/07/employee_costs_table-295x300.png 295w" sizes="(max-width: 478px) 100vw, 478px" /></a><figcaption id="caption-attachment-2239" class="wp-caption-text">Figure 7: EMPLOYEE_COSTS output table</figcaption></figure>

<h1>Visualizing the Output</h1>

<p>Now let's take a look at some helpful reports to visualize the output from the optimization model. The runOptmodel action produced the tabular output tables that were shown in Figure 6 and Figure 7. We used 
<a href="https://www.sas.com/en_us/software/visual-analytics.html" target="_blank" rel="noopener noreferrer">SAS Visual Analytics</a>
to analyze these tables with user-friendly visual representations.</p>

<h2>Employee Schedule</h2>

<p>Figure 8 shows a heat map displaying the assignment of the full-time employees on each day of the week marked by the green boxes. We can see that all full-time employees except Morgan Sound have been given two consecutive days off. The assigned days off are a mix of those provided as input by the user for the full-time employees that requested specific days off, or a decision by the model for the full-time employees who did not request specific days off. Morgan Sound requested Wednesday and Thursday off, but the optimization model has assigned him to work overtime on Thursday, so he receives only one day off.</p>

<figure id="attachment_2176" aria-describedby="caption-attachment-2176" style="width: 1491px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/08/heat_map_full_time.png"><img loading="lazy" decoding="async" class="wp-image-2443 size-full" src="https://blogs.sas.com/content/operations/files/2020/08/heat_map_full_time.png" alt="" width="1327" height="486" srcset="https://blogs.sas.com/content/operations/files/2020/08/heat_map_full_time.png 1327w, https://blogs.sas.com/content/operations/files/2020/08/heat_map_full_time-300x110.png 300w, https://blogs.sas.com/content/operations/files/2020/08/heat_map_full_time-1024x375.png 1024w" sizes="(max-width: 1327px) 100vw, 1327px" /></a><figcaption id="caption-attachment-2176" class="wp-caption-text">Figure 8: Days worked for each full-time employee</figcaption></figure>

<p>Figure 9 shows a similar heat map for the part-time employees. We can see that five part-time employees work an additional overtime day and receive only one day off, while the remaining part-time employees receive two consecutive days off. Again, the assigned days off are a mix of those provided as input by the user for the part-time employees that requested specific days off, or a decision by the model for the part-time employees who did not request specific days off.</p>

<figure id="attachment_2179" aria-describedby="caption-attachment-2179" style="width: 1488px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/08/heat_map_part_time.png"><img loading="lazy" decoding="async" class="wp-image-2446 size-full" src="https://blogs.sas.com/content/operations/files/2020/08/heat_map_part_time.png" alt="" width="1328" height="485" srcset="https://blogs.sas.com/content/operations/files/2020/08/heat_map_part_time.png 1328w, https://blogs.sas.com/content/operations/files/2020/08/heat_map_part_time-300x110.png 300w, https://blogs.sas.com/content/operations/files/2020/08/heat_map_part_time-1024x374.png 1024w" sizes="(max-width: 1328px) 100vw, 1328px" /></a><figcaption id="caption-attachment-2179" class="wp-caption-text">Figure 9: Days worked for each part-time employee</figcaption></figure>

<p>The weekly schedule of the volunteers is shown in Figure 10. The number of shifts assigned varies according to the <strong>max_volunteer_days</strong> input provided for each volunteer.</p>

<figure id="attachment_2182" aria-describedby="caption-attachment-2182" style="width: 1496px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/08/heat_map_volunteers.png"><img loading="lazy" decoding="async" class="wp-image-2449 size-full" src="https://blogs.sas.com/content/operations/files/2020/08/heat_map_volunteers.png" alt="" width="1328" height="487" srcset="https://blogs.sas.com/content/operations/files/2020/08/heat_map_volunteers.png 1328w, https://blogs.sas.com/content/operations/files/2020/08/heat_map_volunteers-300x110.png 300w, https://blogs.sas.com/content/operations/files/2020/08/heat_map_volunteers-1024x376.png 1024w" sizes="(max-width: 1328px) 100vw, 1328px" /></a><figcaption id="caption-attachment-2182" class="wp-caption-text">Figure 10: Days worked for each volunteer</figcaption></figure>

<h2>Overtime Analysis</h2>

<p>The bar chart in Figure 11 shows the total cost for each full-time employee. The green portion of the bars represents regular wages, while the red portion represents overtime pay. We can see that Morgan Sound is receiving overtime pay for the extra shift that was assigned to him during the week. None of the other full-time employees receive overtime pay, which means that in addition to not working extra shifts, they are not assigned to work any additional hours for their regular shifts.</p>

<figure id="attachment_2185" aria-describedby="caption-attachment-2185" style="width: 1453px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/08/bar_chart_full_time.png"><img loading="lazy" decoding="async" class="wp-image-2437 size-full" src="https://blogs.sas.com/content/operations/files/2020/08/bar_chart_full_time.png" alt="" width="1328" height="486" srcset="https://blogs.sas.com/content/operations/files/2020/08/bar_chart_full_time.png 1328w, https://blogs.sas.com/content/operations/files/2020/08/bar_chart_full_time-300x110.png 300w, https://blogs.sas.com/content/operations/files/2020/08/bar_chart_full_time-1024x375.png 1024w" sizes="(max-width: 1328px) 100vw, 1328px" /></a><figcaption id="caption-attachment-2185" class="wp-caption-text">Figure 11: Full-time employee costs</figcaption></figure>

<p>Figure 12 shows a similar chart for the part-time employees. The five part-time employees who were assigned an additional shift are all receiving overtime pay. Similar to the full-time employees, none of the other part-time employees receive overtime pay, which means that in addition to not working extra shifts, they are not assigned to work any additional hours for their regular shifts.</p>

<figure id="attachment_2188" aria-describedby="caption-attachment-2188" style="width: 1776px" class="wp-caption aligncenter"><a href="https://blogs.sas.com/content/operations/files/2020/08/bar_chart_part_time.png"><img loading="lazy" decoding="async" class="wp-image-2440 size-full" src="https://blogs.sas.com/content/operations/files/2020/08/bar_chart_part_time.png" alt="" width="1327" height="485" srcset="https://blogs.sas.com/content/operations/files/2020/08/bar_chart_part_time.png 1327w, https://blogs.sas.com/content/operations/files/2020/08/bar_chart_part_time-300x110.png 300w, https://blogs.sas.com/content/operations/files/2020/08/bar_chart_part_time-1024x374.png 1024w" sizes="(max-width: 1327px) 100vw, 1327px" /></a><figcaption id="caption-attachment-2188" class="wp-caption-text">Figure 12: Part-time employee costs</figcaption></figure>

<h1>Summary</h1>

<p>In this article, we showed a useful application of using the runOptmodel action in SAS Optimization to determine an optimal employee schedule at an animal shelter, and we used SAS Visual Analytics to create user-friendly reports to analyze the optimal solution. OPTMODEL separates the model from the data, and this enables the same model to be used for different problem instances simply by changing the input data tables. For example, if an employee decides to switch from full-time to part-time, or if some employees want to change their days off, or if any employees receive a pay raise, the model can easily handle the changes in the input data and provide a new optimal schedule.</p>

<h3>Acknowledgments</h3>

<p>The authors would like to thank <a href="https://blogs.sas.com/content/author/robpratt/" target="_blank" rel="noopener noreferrer">Rob Pratt</a> and Hossein Tohidi for their contributions to the model formulation. </p>

<h3>Code</h3>

The code and data for this problem are available in GitHub at <br>
<a href="https://github.com/sascommunities/sas-optimization-blog/tree/master/animal_shelter_workforce_optimization" target="_blank" rel="noopener noreferrer">https://github.com/sascommunities/sas-optimization-blog/tree/master/animal_shelter_workforce_optimization</a>.<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2020/09/14/workforce-scheduling-at-an-animal-shelter/">Workforce Scheduling at an Animal Shelter</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/operations/2020/09/14/workforce-scheduling-at-an-animal-shelter/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/operations/files/2020/07/AdobeStock_58691098-680x380@2x-150x150.jpeg"/>
	</item>
		<item>
		<title>Improving Hidden Markov Models with Black-Box Optimization</title>
		<link>https://blogs.sas.com/content/operations/2020/08/10/improving-hidden-markov-models-with-black-box-optimization/</link>
					<comments>https://blogs.sas.com/content/operations/2020/08/10/improving-hidden-markov-models-with-black-box-optimization/#comments</comments>
		
		<dc:creator><![CDATA[Suyun Liu]]></dc:creator>
		<pubDate>Mon, 10 Aug 2020 14:20:49 +0000</pubDate>
				<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[Black-Box Optimization]]></category>
		<category><![CDATA[Econometrics]]></category>
		<category><![CDATA[Hidden Markov Models]]></category>
		<category><![CDATA[Hyperparameter Tuning]]></category>
		<category><![CDATA[operations research]]></category>
		<category><![CDATA[R&D Analytics]]></category>
		<category><![CDATA[SAS Optimization]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/operations/?p=2041</guid>

					<description><![CDATA[<p>Hidden Markov Models Introduction Statistical models of hidden Markov modeling (HMM) have become increasingly popular in the last several years. The models are very rich in mathematical structures and can form the theoretical basis of many real applications. In the classical continuous/discrete Markov process, each state corresponds to an observed [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2020/08/10/improving-hidden-markov-models-with-black-box-optimization/">Improving Hidden Markov Models with Black-Box Optimization</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h1 class="xisCas-name ng-scope"><strong>Hidden Markov Models</strong></h1>
<h3><strong>Introduction</strong></h3>
<p>Statistical models of hidden Markov modeling (HMM) have become increasingly popular in the last several years. The models are very rich in mathematical structures and can form the theoretical basis of many real applications. In the classical continuous/discrete Markov process, each state corresponds to an observed (physical) event. The hidden Markov modeling focuses on the case where the observation is a probabilistic function of the hidden state. In other words, the hidden Markov model is a doubly embedded stochastic process with an underlying stochastic process hidden (unobservable) and another observable stochastic process producing the sequence of observations.</p>
<h3><strong>Elements of an HMM</strong></h3>
<p>An HMM is characterized by the following five key elements:</p>
<ul>
<li><strong><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="K" /></span><script type='math/tex'>K</script></strong>, the number of hidden states. Denote the set of states by <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\{1, \ldots, K\}" /></span><script type='math/tex'>\{1, \ldots, K\}</script>.</li>
<li>Initial state probability vector (ISPV), <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\pi = \{\pi_i, i = 1, ...., K\}" /></span><script type='math/tex'>\pi = \{\pi_i, i = 1, ...., K\}</script>.</li>
<li>Transition Probability Matrix (TPM), <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="A =\{a_{ij}\}" /></span><script type='math/tex'>A =\{a_{ij}\}</script> where <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="a_{ij} = \mathbb{P}(S_t = j| S_{t - 1} = i)" /></span><script type='math/tex'>a_{ij} = \mathbb{P}(S_t = j| S_{t - 1} = i)</script>.</li>
<li><strong><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="M" /></span><script type='math/tex'>M</script></strong>, the number of distinct observation symbols per state.</li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="B = \{b_j(k)\}" /></span><script type='math/tex'>B = \{b_j(k)\}</script>, The probability distribution of observation symbols for each state <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="j" /></span><script type='math/tex'>j</script>.</li>
</ul>
<p>The set of model parameters is denoted by <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\lambda = (A, B, \pi)" /></span><script type='math/tex'>\lambda = (A, B, \pi)</script>.</p>
<h3><strong>The HMM Procedure/Action set</strong></h3>
<p>Given the sequence of observations <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="O" /></span><script type='math/tex'>O</script>, a basic problem of interest to be addressed for the HMM in real-world applications is: How do we adjust model parameters <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="(A, B, \pi)" /></span><script type='math/tex'>(A, B, \pi)</script> to maximize the probability of observing such a sequence of observations, that is, <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\max~\mathbb{P}(O | \lambda)" /></span><script type='math/tex'>\max~\mathbb{P}(O | \lambda)</script>? <a href="https://go.documentation.sas.com/?docsetId=casecon&amp;docsetTarget=casecon_hmm_toc.htm&amp;docsetVersion=8.5&amp;locale=en" rel="noopener noreferrer">The HMM procedure</a> in SAS Econometrics 8.5 (its corresponding CASL version is the <a href="https://go.documentation.sas.com/?docsetId=casactecon&amp;docsetTarget=casactecon_hiddenmarkovmodel_toc.htm&amp;docsetVersion=8.5&amp;locale=en" rel="noopener noreferrer">HMM action</a>) was developed to solve the problem, that is, to adjust model parameters to maximize the probability of the observation sequence given a model.</p>
<h4>Regime-Switching Autoregression Model</h4>
<p>We attempt to fit the observation sequence to the Regime-Switching model when the parameters of the data generating process (DGP) vary over a set of different unobserved states. The Regime-Switching Autoregression (RS-AR) model allows states to switch according to a Markov process and is often applied to lower frequency data (quarterly, yearly, and so on). A typical application of such a model is stock returns. Generally, the RS-AR model can be formulated as follows:</p>
<p style="text-align: center"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="y_t = \mu_{S_t} + x_t\alpha + z_t\beta_{S_t} + \displaystyle\sum_{i = 1}^P \phi_{i, S_t} (y_{t - i} - \mu_{S_{t - i}} - x_{t - i}\alpha + z_{t - i}\beta_{S_{t - i}}) + \epsilon_{S_t}" /></span><script type='math/tex'>y_t = \mu_{S_t} + x_t\alpha + z_t\beta_{S_t} + \displaystyle\sum_{i = 1}^P \phi_{i, S_t} (y_{t - i} - \mu_{S_{t - i}} - x_{t - i}\alpha + z_{t - i}\beta_{S_{t - i}}) + \epsilon_{S_t}</script></p>
<p style="text-align: left">where</p>
<ul>
<li style="text-align: left"><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="y_t" /></span><script type='math/tex'>y_t</script>: Dependent variables.</li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\mu_{S_t}" /></span><script type='math/tex'>\mu_{S_t}</script>: State-dependent intercept/mean.</li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="z_t" /></span><script type='math/tex'>z_t</script>: Vector of exogenous variables with state-dependent coefficients <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\beta_{S_t}" /></span><script type='math/tex'>\beta_{S_t}</script>.</li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\phi_{i, S_t}" /></span><script type='math/tex'>\phi_{i, S_t}</script>: <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="i" /></span><script type='math/tex'>i</script>-th AR term in state <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="S_t" /></span><script type='math/tex'>S_t</script>.</li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\epsilon_{S_t}" /></span><script type='math/tex'>\epsilon_{S_t}</script>: i.i.d. normal distribution <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="N(0, \sigma^2_{S_t})" /></span><script type='math/tex'>N(0, \sigma^2_{S_t})</script>.</li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="P" /></span><script type='math/tex'>P</script>: order of regression.</li>
</ul>
<p>Two key methods for parameter estimation include the maximum a posteriori method (MAP) and the maximum likelihood method (ML).  Three optimization algorithms supported by the HMM action are the active-set algorithm, the interior-point algorithm, and the stochastic gradient descent algorithm. As the HMM solver iteratively updates and improves model parameters, the choice of initial parameters is crucial in searching for a good local optimizer.</p>
<h4>An example: Discovering the Hidden Market States</h4>
<p>Each RS-AR model is characterized by the number of states <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k" /></span><script type='math/tex'>k</script> and the order of regression <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p" /></span><script type='math/tex'>p</script>.  Given a fixed pair <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="(k, p)" /></span><script type='math/tex'>(k, p)</script>, we can use the HMM action set to estimate the model parameters. The sequence of observations used in the numerical experiments is called <em>vwmi</em> and is extracted from the CRSP data. In the example, we tested 48 models with <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k = 2, \ldots, 9" /></span><script type='math/tex'>k = 2, \ldots, 9</script> and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 0,\ldots,5" /></span><script type='math/tex'>p = 0,\ldots,5</script>.  In order to select a good initial point for the iterative procedure, the multistart mode is enabled. The following CASL script shows how to call the HMM solver and get estimation results for all 48 AR models.<br />
<pre class="preserve-code-formatting" style="font-size: 11px;"> 
%macro estimateRSAR(myds, inEstDs, kStart, kEnd, pStart, pEnd, method, maxiter, qMultiStart);
&nbsp;&nbsp; proc cas;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hiddenMarkovModel.hmm result=r/
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; data = {caslib=&#039;casuser&#039;, name=&quot;&amp;myds.&quot;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id={time=&#039;date&#039;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; outstat={name=&quot;&amp;myds.&amp;method.Stat_k&amp;kStart.To&amp;kEnd._p&amp;pStart.To&amp;pEnd.&quot;, caslib=&quot;casuser&quot;, replace=true},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; model={depvars={&#039;returnw&#039;}, method=&quot;&amp;method.&quot;, nState=&amp;kStart., nStateTo=&amp;kEnd., ylag=&amp;pStart., yLagTo=&amp;pEnd., type = &#039;AR&#039;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optimize = {algorithm=&#039;interiorpoint&#039;, printLevel=3, printIterFreq=1, maxiter=&amp;maxiter., Multistart = &amp;qMultiStart.},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; score = {outmodel={name = &quot;&amp;myds.&amp;method.Model_k&amp;kStart.To&amp;kEnd._p&amp;pStart.To&amp;pEnd.&quot;, caslib=&quot;casuser&quot;, replace=true}},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; learn = {out={name = &quot;&amp;myds.&amp;method.Learn_k&amp;kStart.To&amp;kEnd._p&amp;pStart.To&amp;pEnd.&quot;, caslib=&quot;casuser&quot;, replace=true} 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;%if %length(&amp;inEstDs.)&gt;0 %then %do; , in={name = &quot;&amp;inEstDs.&quot;, caslib=&quot;casuser&quot;} %end;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; labelSwitch={sort=&quot;NONE&quot;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; display = {names = {&quot;Optimization.Algorithm&quot;, &quot;ModelInfo&quot;, &quot;ParameterMatrices.TPM&quot;, &quot;Optimization.FinalParameterEstimates&quot;, &quot;Optimization.IterHistory&quot;, 
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &quot;FitStatistics&quot;, &quot;Optimization.InitialObjectiveFunction&quot;, &quot;Optimization.FinalObjectiveFunction&quot;}};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;print r;
&nbsp;&nbsp; run; quit;
&nbsp;&nbsp; CAS mysess listhistory;
%mend;

/*use MAP and enable multistart for p = 0 models to obtain initial parameter estimations*/
%estimateRSAR(myds=vwmi, inEstDs=, kStart=2, kEnd=9, pStart=0, pEnd=0, method=MAP, maxiter=128, qMultiStart=1);

/*use ML and disable multistart for p = 0 to 5 models to get final paramter estimations*/
%estimateRSAR(myds=vwmi, inEstDs=vwmiMAPLEARN_K2TO9_P0TO0, kStart=2, kEnd=9, pStart=0, pEnd=5, method=ML, maxiter=128, qMultiStart=0);
</pre></p>
<p>For AR models with <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 0" /></span><script type='math/tex'>p = 0</script>, the common practice is first to take MAP as the estimation method and enable multistart mode to get an initial parameter estimate. Then we take the output of the MAP method to hot-start the ML method, using output parameter estimates as the initial values and calling the HMM procedure the second time using the ML method. For the other models of <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p \ge 1" /></span><script type='math/tex'>p \ge 1</script>, the HMM solver automatically takes outputs from the corresponding AR (<span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p - 1" /></span><script type='math/tex'>p - 1</script>) model as initial values.</p>
<p>To compare the quality of different statistical models, we could measure the Akaike information criterion (AIC) value, which is basically an estimator of out-of-sample prediction error. Therefore, the minimal AIC value in the following table identifies the best model (<span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k = 7, p = 2" /></span><script type='math/tex'>k = 7, p = 2</script>) among the 48 AR models. Note that the multistart mode of the HMM action takes around 17 hours to get the full AIC table as it currently supports only single-machine mode. Refer to <a href="https://go.documentation.sas.com/?docsetId=casecon&amp;docsetTarget=casecon_hmm_examples01.htm&amp;docsetVersion=8.5&amp;locale=en" rel="noopener noreferrer">Example 13.1</a> in the HMM documentation for a more detailed interpretation.</p>
<p><img loading="lazy" decoding="async" class="wp-image-2170 aligncenter" style="font-size: 14px" src="https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_ms-300x109.png" alt="" width="493" height="179" srcset="https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_ms-300x109.png 300w, https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_ms.png 814w" sizes="(max-width: 493px) 100vw, 493px" /></p>
<h1>Black-Box optimization solver</h1>
<p>The black-box optimization solver has rich applications in hyperparameter tuning whose objective functions are usually nonsmooth, discontinuous, and unpredictably varying in computational expense. The hyperparameters we want to tune might be mixtures of continuous, categorical, and integer parameters. The <a href="https://go.documentation.sas.com/?docsetId=casactmopt&amp;docsetTarget=cas-optimization-solveblackbox.htm&amp;docsetVersion=8.5&amp;locale=en" rel="noopener noreferrer">solveBlackbox action</a> was developed based on an automated parallel derivative-free optimization framework called Autotune. <a href="https://arxiv.org/abs/1804.07824#:~:text=Autotune%3A%20A%20Derivative%2Dfree%20Optimization%20Framework%20for%20Hyperparameter%20Tuning,-Patrick%20Koch%2C%20Oleg&amp;text=Machine%20learning%20applications%20often%20require,and%20the%20resulting%20model%20quality.">Autotune</a> combines a number of specialized sampling and search methods that are very efficient in tuning complex models like in machine learning. The Autotune framework is illustrated in Fig.2.<a href="https://blogs.sas.com/content/operations/files/2020/07/Autotune_framework.png"><img loading="lazy" decoding="async" class="wp-image-2110 alignright" src="https://blogs.sas.com/content/operations/files/2020/07/Autotune_framework-300x216.png" alt="" width="396" height="285" srcset="https://blogs.sas.com/content/operations/files/2020/07/Autotune_framework-300x216.png 300w, https://blogs.sas.com/content/operations/files/2020/07/Autotune_framework-536x386.png 536w, https://blogs.sas.com/content/operations/files/2020/07/Autotune_framework.png 644w" sizes="(max-width: 396px) 100vw, 396px" /></a> Autotune has the ability to simultaneously apply multiple instances of global and local search algorithms in parallel, among which a global algorithm is first applied to determine a good starting point to initialize a local algorithm. An extended suite of search methods is driven by the Hybrid Solver Manager that controls concurrent execution of different search methods. One can easily add new search methods to the framework. Overall, search methods can learn from each other, discover new opportunities, and increase the robustness of the system.</p>
<p>The solveBlackbox action based on the Autotune framework takes the Genetic Algorithm (GA) as the global solver and Generating Set Search (GSS) to perform local search in a neighborhood of selected members from the current GA population. It is notable that the solveBlackbox action can handle more than one objective and support both linear and nonlinear constraints. Furthermore, it can work well in either single-machine mode or distributed mode.</p>
<p>Due to the existence of many local optima, the choice of starting points (called initial values) in HMM has a significant impact on the quality of the solution. The usage of multistart mode helps identify a better initial point and hence better local optimum at the expense of time. The black-box optimization solver is a good alternative to the multistart approach due to the following key properties:</p>
<ul>
<li>The HMM objective function is a black-box function of the initial values/parameters. Most importantly, we do not need to have an explicit mathematical form of such an objective function.  It might be smooth or nonsmooth, continuous or discontinuous, very expensive in function value evaluations, and so on.</li>
<li>The hyperparameters considered in the black-box optimization solver could be continuous (for example, the initial values), integer (for example, the number of states and the order of regression), as well as categorical (for example, type of methods and algorithms).</li>
<li>The black-box optimization solver supports parallel function evaluations automatically, which parallelizes multiple HMM procedures starting from different initial points.</li>
</ul>
<h1>Numerical Experiments</h1>
<p>We then take initial values as the tuning hyperparameters. Specifically, the set of hyperparameters to be tuned includes three sets of variables: 1) Transition probability matrix of hidden states, where each row sums up to one and all elements should be nonnegative; 2) State-dependent intercepts, which can be any real numbers; 3) Covariance matrix or variance, which should be positive semidefinite or nonnegative.</p>
<p>Three configurations in solveBlackbox are tested for tuning hyperparameters for two data sets (<em>vwmi</em> and <em>jpusstock</em>).</p>
<ul>
<li>Blackbox-MAP: Tuning models with <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 0" /></span><script type='math/tex'>p = 0</script> using the MAP estimation method.</li>
<li>Blackbox-ML: Tuning models with <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 0" /></span><script type='math/tex'>p = 0</script> using the ML estimation method.</li>
<li>Blackbox-ML-All: Tuning each model (any <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k" /></span><script type='math/tex'>k</script>, <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p" /></span><script type='math/tex'>p</script>) using the ML estimation method.</li>
</ul>
<p>The following example code demonstrates how we use the solveBlackbox action to tune HMM initial values.<br />
<pre class="preserve-code-formatting" style="font-size: 11px;">
%macro TuneHmm(ns, nvars, pStart, maxgeneration, method, maxiter);
proc cas noqueue;
&nbsp;&nbsp; /*use an Initial function to load data to servers*/
&nbsp;&nbsp; source casInit;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;loadTable / caslib=&#039;casuser&#039;, path=&#039;vwmi.sashdat&#039;, casout=&#039;vwmi&#039;;
&nbsp;&nbsp; endsource;

&nbsp;&nbsp; /*specify blackbox objective function*/
&nbsp;&nbsp; source caslEval;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;hiddenMarkovModel.hmm result=r /
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; data = &quot;vwmi&quot;,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; id={time=&#039;date&#039;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; model={depvars={&#039;returnw&#039;}, method=&quot;&amp;method.&quot;, nState=&amp;ns., ylag=&amp;pStart., type = &#039;AR&#039;},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; initial={&#039;TPM={&#039; || (String) x1 || &#039; &#039; || (String) x2 || &#039;, &#039; || (String) x3 || &#039; &#039; || (String) x4 || &#039;}&#039;,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#039;MU={&#039;|| (String) x5 || &#039;, &#039; || (String) x6 || &#039;}&#039;,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&#039;SIGMA={&#039; || (String) x7 || &#039;, &#039; || (String) x8 || &#039;}&#039;}, /*the initial value list is different for different nState*/
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; optimize = {algorithm=&#039;interiorpoint&#039;, printLevel=3, printIterFreq=1, maxiter=&amp;maxiter., Multistart = 0},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; score= {outmodel={name = &quot;&amp;ds.&amp;method.Model_k&amp;ns.p&amp;pStart._&quot; || (String) _bbEvalTag_ || &quot;_&amp;nworkers.&quot;, caslib=&quot;casuser&quot;, replace=true}},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; learn= {out={name = &quot;&amp;ds.&amp;method.Learn_k&amp;ns.p&amp;pStart._&quot; || (String) _bbEvalTag_ || &quot;_&amp;nworkers.&quot;, caslib=&quot;casuser&quot;, promote=true}},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; labelSwitch={sort=&quot;NONE&quot;};
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;f[&#039;obj1&#039;] = r.FitStatistics[1, 2];  /*read log likelihood value from FitStatistics table*/
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;send_response(f);
&nbsp;&nbsp; endsource;

&nbsp;&nbsp; /* Invoke the solveBlackbox action */
&nbsp;&nbsp; optimization.solveBlackbox result=blackr/
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;decVars = myvars, /*define tuning variables as the initial values of hmm*/
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;obj = {{name=&#039;obj1&#039;, type=&#039;max&#039;}},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;maxGen= &amp;maxgeneration., /* by default 10 */
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;popSize=15, /* by default 40 */
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;maxTime = 3600,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;linCon={name = &quot;lindata&quot;, caslib = &quot;casuser&quot;}, /*read coefficients of linear constraints from lindata*/
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;func = {init=casInit, eval=caslEval},
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;nParallel=30; /* specify the number of parallel sessions*/
run; quit;
%mend;

/* call the tuning process for any k, p */
%TuneHmm(ns = 2, nvars = 8, pStart = 0, maxgeneration = 5, method = MAP, maxiter = 128); /*Blackbox-MAP*/
%TuneHmm(ns = 2, nvars = 8, pStart = 0, maxgeneration = 1, method = ML, maxiter = 128); /*Blackbox-ML and Blackbox-ML-All*/
</pre></p>
<h2>Tuning RS-AR models with <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 0" /></span><script type='math/tex'>p = 0</script></h2>
<p>Instead of using multistart for <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 0" /></span><script type='math/tex'>p = 0</script> models, we use the solveBlackbox action to tune <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 0" /></span><script type='math/tex'>p = 0</script> models using MAP and ML, denoted Blackbox-MAP and Blackbox-ML, respectively. Then we run the second-stage optimization using ML as the multistart did to obtain the full AIC table.</p>
<p><strong>First data set <em>vwmi</em></strong>: For this <em>vwmi</em> data set, we considered values of <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k" /></span><script type='math/tex'>k</script> ranging from 2 to 9, and values of <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p" /></span><script type='math/tex'>p</script> from 0 to 5. The following histogram shows the comparison of AIC values for Multistart, Blackbox-MAP, and Blackbox-ML. The 48 models are ordered by <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p" /></span><script type='math/tex'>p</script> and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k" /></span><script type='math/tex'>k</script> in ascending order. The model with <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k = 2" /></span><script type='math/tex'>k = 2</script> and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 0" /></span><script type='math/tex'>p = 0</script> is indexed by 1, the model with <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k = 2" /></span><script type='math/tex'>k = 2</script> and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 1" /></span><script type='math/tex'>p = 1</script> is indexed by 2, and so on.<a href="https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_set1.png"><img loading="lazy" decoding="async" class="aligncenter wp-image-2389 size-large" src="https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_set1-1024x347.png" alt="" width="702" height="238" srcset="https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_set1-1024x347.png 1024w, https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_set1-300x102.png 300w, https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_set1.png 1784w" sizes="(max-width: 702px) 100vw, 702px" /></a>The results tell us the black-box optimization solver does help identify a better local optimum among half of the 48 models, especially when the model size is large. Compared with multistart mode, Blackbox-MAP is 76% faster and Blackbox-ML is 91% faster. Overall, Blackbox-MAP performs slightly better than Blackbox-ML in terms of solution quality, while Blackbox-ML is more computationally efficient than Blackbox-MAP.</p>
<p><strong>Second data set <em>jpusstock</em></strong>: This data set contains two sets of historical weekly returns and results in RS-AR models of two dependent variables. Considering AR models with <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k = 2, \ldots, 10" /></span><script type='math/tex'>k = 2, \ldots, 10</script> and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 0 ,\ldots,6" /></span><script type='math/tex'>p = 0 ,\ldots,6</script>, some of the resulting AIC values of Multistart, Blackbox-MAP, and Blackbox-ML are presented below. Except for two models (<span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k = 9" /></span><script type='math/tex'>k = 9</script>, <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 3" /></span><script type='math/tex'>p = 3</script> and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="4" /></span><script type='math/tex'>4</script>), Blackbox-MAP and Blackbox-ML lead to a solution that is at least as good as Multistart, with significant improvements in large models. Furthermore, both tuning configurations are 90% faster than the Multistart method.</p>
<p>Note that among 63 models, the Multistart method is able to help find a reasonable parameter estimation for 39 models, while Blackbox-MAP and Blackbox-ML work for 48 and 50 models, respectively. These results indicate that using the output for <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p = 0" /></span><script type='math/tex'>p = 0</script> from these methods does not necessarily work well in the second-stage optimization. A possible good solution for this case is explored in the next experiments, that is, tuning each model directly using ML.<a href="https://blogs.sas.com/content/operations/files/2020/07/datajpusstock_results_set1.png"><img loading="lazy" decoding="async" class="aligncenter wp-image-2362 size-large" src="https://blogs.sas.com/content/operations/files/2020/07/datajpusstock_results_set1-1024x347.png" alt="" width="702" height="238" srcset="https://blogs.sas.com/content/operations/files/2020/07/datajpusstock_results_set1-1024x347.png 1024w, https://blogs.sas.com/content/operations/files/2020/07/datajpusstock_results_set1-300x102.png 300w, https://blogs.sas.com/content/operations/files/2020/07/datajpusstock_results_set1.png 1784w" sizes="(max-width: 702px) 100vw, 702px" /></a></p>
<h2>Tuning RS-AR models with any <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k" /></span><script type='math/tex'>k</script>, <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p" /></span><script type='math/tex'>p</script></h2>
<p>Due to the ill-posed objective functions of HMM, we observed that the two-stage strategy based on results from Multistart, Blackbox-MAP, and Blackbox-ML sometimes results in unrealistic parameter estimations, especially for <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="p > 0" /></span><script type='math/tex'>p > 0</script> models<strong>. </strong>For instance, the eigenvalues of the covariance matrix in one of the hidden states are very close to zero when <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="k = 8" /></span><script type='math/tex'>k = 8</script>. This motivates us to try to find a good initial point for any model directly. Therefore, we further consider tuning each model directly using ML, denoted Blackbox-ML-All. The resulting AIC values from this strategy are compared with Multistart and Blackbox-MAP for both data sets.<a href="https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_set2.png"><img loading="lazy" decoding="async" class="aligncenter wp-image-2392 size-large" src="https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_set2-1024x348.png" alt="" width="702" height="239" srcset="https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_set2-1024x348.png 1024w, https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_set2-300x102.png 300w, https://blogs.sas.com/content/operations/files/2020/07/vwmiO_results_set2.png 1782w" sizes="(max-width: 702px) 100vw, 702px" /></a><a href="https://blogs.sas.com/content/operations/files/2020/07/datajpusstock_results_set2.png"><img loading="lazy" decoding="async" class="aligncenter wp-image-2368 size-large" src="https://blogs.sas.com/content/operations/files/2020/07/datajpusstock_results_set2-1024x347.png" alt="" width="702" height="238" srcset="https://blogs.sas.com/content/operations/files/2020/07/datajpusstock_results_set2-1024x347.png 1024w, https://blogs.sas.com/content/operations/files/2020/07/datajpusstock_results_set2-300x102.png 300w, https://blogs.sas.com/content/operations/files/2020/07/datajpusstock_results_set2.png 1784w" sizes="(max-width: 702px) 100vw, 702px" /></a></p>
<p>We can see from the above results that Blackbox-ML-All further improves the solution quality for most of the models, though the time of Blackbox-ML-All is around six times slower than Blackbox-ML. Despite Blackbox-ML-All being slower than Blackbox-ML, it provides higher quality solutions and is still 50% faster than Multistart in finding the best model and the corresponding parameter estimation. More importantly, for the second data set, Blackbox-ML-All is able to produce good final parameter estimations for all 63 models. Therefore, we believe that Blackbox-ML-All is a good alternative to Multistart mode whenever Multistart results in bad estimations of parameters.</p>
<h1>Summary</h1>
<p>The multistart mode supported by the HMM procedure and the HMM action can only run in a single machine, leading to high time cost. The numerical test for the data set <em>jpusstock </em>revealed that multistart is not reliable to find a good estimation of parameters. Given that the solveBlackbox action supports parallel function evaluations and applies to any type of black-box functions and hyperparameters, we frame the solveBlackbox action to tune the initial values for the HMM. Exploring three different black-box tuning configurations, we are able to improve the solutions of the HMM models in terms of both solution quality and computational cost. In conclusion, the black-box optimization solver is a good alternative to identify good initial parameter estimates in the HMMs. </p>
<h3>References</h3>
<p>[1] Patrick Koch, Oleg Golovidov, Steven Gardner, Brett Wujek, Joshua Griffin, and Yan Xu. 2018. Autotune: A Derivative-free Optimization Framework for Hyperparameter Tuning. In KDD. 443--452.</p>
<p>[2] S. Gardner <em>et al</em>., "Constrained Multi-Objective Optimization for Automated Machine Learning," <em>2019 IEEE International Conference on Data Science and Advanced Analytics (DSAA)</em>, Washington, DC, USA, 2019, pp. 364-373, doi: 10.1109/DSAA.2019.00051.</p>
<p>[3] L. R. Rabiner, "A tutorial on hidden Markov models and selected applications in speech recognition", <em>Proc. IEEE</em>, vol. 77, pp. 257-286, Feb. 1989.</p>
<p>[4] Kim, C.-J., C. R. Nelson, and R. Startz. 1998. Testing for mean reversion in heteroskedastic data based on Gibbs-sampling-augmented randomization. Journal of Empirical Finance 5: 115-143.</p>
<p>[5] <a href="https://cse.buffalo.edu/~jcorso/t/CSE555/files/lecture_hmm.pdf">https://cse.buffalo.edu/~jcorso/t/CSE555/files/lecture_hmm.pdf</a></p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2020/08/10/improving-hidden-markov-models-with-black-box-optimization/">Improving Hidden Markov Models with Black-Box Optimization</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/operations/2020/08/10/improving-hidden-markov-models-with-black-box-optimization/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/operations/files/2017/02/AdvancedAnalytics-3-150x150.png"/>
	</item>
		<item>
		<title>SAS® Optimization Web App Using REST APIs with Embedded SAS® Visual Analytics Reports</title>
		<link>https://blogs.sas.com/content/operations/2020/03/11/sas-optimization-web-app-using-rest-apis-with-embedded-sas-visual-analytics-reports/</link>
		
		<dc:creator><![CDATA[Nabaruna Karmakar]]></dc:creator>
		<pubDate>Wed, 11 Mar 2020 14:26:59 +0000</pubDate>
				<category><![CDATA[mixed integer linear optimization]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[CASL Language]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[MILP]]></category>
		<category><![CDATA[operations research]]></category>
		<category><![CDATA[R&D Analytics]]></category>
		<category><![CDATA[ReactJS]]></category>
		<category><![CDATA[REST APIs]]></category>
		<category><![CDATA[restAF]]></category>
		<category><![CDATA[SAS Optimization]]></category>
		<category><![CDATA[SAS Visual Analytics]]></category>
		<category><![CDATA[SAS Viya]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/operations/?p=1891</guid>

					<description><![CDATA[<p>Mathematical optimization can help business leaders make better decisions in every aspect of their business. After a model has been built, end users are usually interested in doing some sort of scenario analysis to test its robustness and visualizing key performance metrics. SAS has various products that can work with [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2020/03/11/sas-optimization-web-app-using-rest-apis-with-embedded-sas-visual-analytics-reports/">SAS&reg; Optimization Web App Using REST APIs with Embedded SAS&reg; Visual Analytics Reports</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>Mathematical optimization can help business leaders make better decisions in every aspect of their business. After a model has been built, end users are usually interested in doing some sort of scenario analysis to test its robustness and visualizing key performance metrics. SAS has various products that can work with each other to provide a holistic view of the analytical problem. Using <a href="https://www.sas.com/en_us/software/optimization.html">SAS Optimization</a>, we can solve myriads of complicated real-world problems, while <a href="https://www.sas.com/en_us/software/visual-analytics.html">SAS Visual Analytics</a> allows us to interactively visualize the optimal solution.</p>
<p>In this article, I will provide a demo of a web app that connects SAS Visual Analytics and other SAS® Viya services through REST APIs. The web interface provides an interactive platform to demonstrate small-scale MILP (Mixed Integer Linear Programming) models to non-technical audiences and the ability to perform some scenario analysis by changing certain parameters.The purpose of this demo tool is to enable users with little to no knowledge of SAS programming to invoke the power of SAS Optimization by simply clicking a few buttons.</p>
<p>Since this is a web application project, instead of describing all components of the project I will provide only the sample code of the optimization model, but you may clone my <a href="https://github.com/sassoftware/viya-optimization-apps">GitHub</a> repository to recreate this project.</p>
<h1>High-level Architecture</h1>
<h2>Server-side library - restaf-server</h2>
<p>Everyone with moderate to good front-end coding experience knows the necessity of finding good JavaScript libraries for developing dynamic web applications. The server-side library is <a href="https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fgithub.com%2Fsassoftware%2Frestaf-server&amp;data=02%7C01%7CNabaruna.Karmakar%40sas.com%7C544bb3e7f13f4152b59108d7c5ed1a5c%7Cb1c14d5c362545b3a4309552373a0c2f%7C0%7C0%7C637195494462286612&amp;sdata=DNheKdcNAz%2B1j%2F3N6qt9WPRZHAiGue1UAeQTgHPex1Q%3D&amp;reserved=0">restaf-server</a>, which authenticates the connection to a SAS Viya server. It serves up the entry point to the app or the first page and provides the session context to the <a href="https://github.com/sassoftware/restaf">restaf</a> library.</p>
<h2>Front-end library - ReactJS</h2>
<p>The restaf library is UI framework agnostic, which means it can be seamlessly integrated into any front-end framework like Angular, React, etc. It provides a user-friendly way to make REST API calls to SAS Viya. A very simple web application showing how to run a small optimization problem and fetch a SAS Visual Analytics report to visualize output using restaf can be found <a href="https://blogs.sas.com/content/sgf/2018/12/21/sas-rest-apis-sample-application/">here</a>. In this blog we use the concepts from the simple example to build a much richer application. The UI framework used in this project is <a href="https://reactjs.org/">ReactJS</a> due to its component-based rendering. ReactJS is easily regarded as the UI framework of choice for most front-end developers and is reportedly being used by all the big companies like Facebook, Uber, Netflix, etc. In fact, it was started by a software engineer at Facebook and has steadily gained popularity. The styling of the web page is done using <a href="https://material-ui.com/">MaterialUI</a>, which includes styling of the sidebar and the fonts. Some other libraries are also used in this web application, notably, <a href="http://allenfang.github.io/react-bootstrap-table/">react-bootstrap-table</a>. This library helped me in creating the editable table which ultimately lets the user change input parameters and perform scenario analysis. When you look at the video below, it may seem like a multi-page app. However, it is a misnomer. This app is in fact a single-page app and I am rendering different React components onto the same HTML element. Pretty cool, huh? I am using<a href="https://reacttraining.com/react-router/"> React Router</a>, another JavaScript library, to enable the routing between the different components.</p>
<p>See below a flowchart of the underlying technologies:</p>
<p><a href="https://blogs.sas.com/content/operations/files/2020/03/App-frameowrk-5.png"><img loading="lazy" decoding="async" class="alignnone wp-image-1994 size-full" src="https://blogs.sas.com/content/operations/files/2020/03/App-frameowrk-5.png" alt="" width="1116" height="462" srcset="https://blogs.sas.com/content/operations/files/2020/03/App-frameowrk-5.png 1116w, https://blogs.sas.com/content/operations/files/2020/03/App-frameowrk-5-300x124.png 300w, https://blogs.sas.com/content/operations/files/2020/03/App-frameowrk-5-1024x424.png 1024w" sizes="(max-width: 1116px) 100vw, 1116px" /></a></p>
<h1>Demo workflow</h1>
<p>In this section, I will describe in words the workflow of the Facility Location demo, walk you through each of the interactive components, and demonstrate how the user can do a scenario analysis as well.</p>
<p>But first, check out the video on our YouTube channel for this demo!</p>

<!-- iframe plugin v.6.0 wordpress.org/plugins/iframe/ -->
<iframe loading="lazy" width="650" height="400" src="https://www.youtube.com/embed/r7Rizt_S_vg" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" 0="allowfullscreen" scrolling="yes" class="iframe-class"></iframe>
<h2></h2>
<h2>Problem Description</h2>
<p>Now, let me give you a quick overview of the optimization problem I am trying to demonstrate using the web app. It is a facility location problem for a car manufacturing company. The company has 25 existing facilities, each of which is eligible to produce up to 5 types of cars. The problem is to optimally assign cars to be manufactured at the eligible facilities​. A variable cost is incurred per car manufactured at a facility​, while a fixed cost is a one-time operating cost incurred to keep a facility open or closed​. Each facility can manufacture at most one car. Capacity and demand are also provided in terms of numbers of cars.</p>
<p><a href="https://blogs.sas.com/content/operations/files/2020/03/blog1.png"><img loading="lazy" decoding="async" class="alignnone wp-image-1949 size-full" src="https://blogs.sas.com/content/operations/files/2020/03/blog1.png" alt="" width="1904" height="975" srcset="https://blogs.sas.com/content/operations/files/2020/03/blog1.png 1904w, https://blogs.sas.com/content/operations/files/2020/03/blog1-300x154.png 300w, https://blogs.sas.com/content/operations/files/2020/03/blog1-1024x524.png 1024w" sizes="(max-width: 1904px) 100vw, 1904px" /></a></p>
<h2>Analyze Input Data</h2>
<p>On mounting this component, a SAS Visual Analytics report created by the user on the server to analyze the input data is rendered onto the page. In this report, we can interactively see which facilities are eligible to produce a particular type of car. The bubbles on the map show the different locations of the existing facilities. By clicking on each of the bars, we can filter out the facilities for that type of car. The color of each bubble represents the variable cost for producing a car at that facility and the size of the bubble shows the fixed cost incurred for keeping the facility open. The goal is to select the smallest green bubble for each of the cars.</p>
<p><a href="https://blogs.sas.com/content/operations/files/2020/03/blog2.png"><img loading="lazy" decoding="async" class="alignnone wp-image-1950 size-full" src="https://blogs.sas.com/content/operations/files/2020/03/blog2.png" alt="" width="1905" height="972" srcset="https://blogs.sas.com/content/operations/files/2020/03/blog2.png 1905w, https://blogs.sas.com/content/operations/files/2020/03/blog2-300x153.png 300w, https://blogs.sas.com/content/operations/files/2020/03/blog2-1024x522.png 1024w" sizes="(max-width: 1905px) 100vw, 1905px" /></a></p>
<h2>Input Optimization Parameters</h2>
<p>In this component, the user is able to set a few parameters and run the optimization code based on these parameters. They can select the objective function, which can be to minimize fixed costs, variable costs, or total costs​.They may also change the demand for each type of car and/or assign a fixed facility to manufacture it using the editable table. Hitting the 'Optimize' button, a series of <a href="https://developers.google.com/web/fundamentals/primers/promises">promise-based API calls</a> is submitted to the Viya server. First, the updated table from the client-side is passed to the back-end as a JSON object and a <a href="https://go.documentation.sas.com/?docsetId=caspg&amp;docsetTarget=cas-table-upload.htm&amp;docsetVersion=3.2&amp;locale=en" rel="noopener noreferrer">table.upload</a> action is used to upload the table to the user's own caslib. Then a string of CASL statements are submitted using the <a href="https://documentation.sas.com/?docsetId=caspg&amp;docsetTarget=cas-sccasl-runcasl.htm&amp;docsetVersion=3.3&amp;locale=en" rel="noopener noreferrer">runCASL</a> action, which includes code for the optimization model. Finally, the results are retrieved from the Viya server using REST APIs and the user is redirected to the output report.</p>
<p><a href="https://blogs.sas.com/content/operations/files/2020/03/blog3.png"><img loading="lazy" decoding="async" class="alignnone wp-image-1951 size-full" src="https://blogs.sas.com/content/operations/files/2020/03/blog3.png" alt="" width="1907" height="971" srcset="https://blogs.sas.com/content/operations/files/2020/03/blog3.png 1907w, https://blogs.sas.com/content/operations/files/2020/03/blog3-300x153.png 300w, https://blogs.sas.com/content/operations/files/2020/03/blog3-1024x521.png 1024w" sizes="(max-width: 1907px) 100vw, 1907px" /></a></p>
<h3>Optimization code</h3>
<p>The MILP model is coded as a JavaScript string and passed later on into some restAF functions. Anyone with some OPTMODEL experience will easily be able to recognize the syntax in the following code block. It is written as a JavaScript function, which takes the objective type and scenario name as input from the form on the web page. This piece of code, along with a few data pre-processing and post-processing steps, is submitted purely as a string of CASL statements and the 'RunCASL' action is executed using REST APIs.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">  function optCode<span style="color: #66cc66;">&#40;</span>ObjType, appEnv, scenario<span style="color: #66cc66;">&#41;</span> <span style="color: #66cc66;">&#123;</span>
  let pgm = `
  <span style="color: #0000ff;">set</span>  PRODUCTS;
  <span style="color: #0000ff;">set</span>  FACILITIES init <span style="color: #66cc66;">&#123;</span><span style="color: #66cc66;">&#125;</span>;
  <span style="color: #0000ff;">set</span> &lt;str, str&gt; FIXED;
&nbsp;
  num demand <span style="color: #66cc66;">&#123;</span>PRODUCTS<span style="color: #66cc66;">&#125;</span>;
  num fixed_cost <span style="color: #66cc66;">&#123;</span>FACILITIES<span style="color: #66cc66;">&#125;</span>;
  num close_cost <span style="color: #66cc66;">&#123;</span>FACILITIES<span style="color: #66cc66;">&#125;</span>;
  num <span style="color: #0000ff;">x</span><span style="color: #66cc66;">&#123;</span>FACILITIES<span style="color: #66cc66;">&#125;</span>;
  num y<span style="color: #66cc66;">&#123;</span>FACILITIES<span style="color: #66cc66;">&#125;</span>;
  num var_cost <span style="color: #66cc66;">&#123;</span>PRODUCTS, FACILITIES<span style="color: #66cc66;">&#125;</span>;
  num viable_flg <span style="color: #66cc66;">&#123;</span>PRODUCTS, FACILITIES<span style="color: #66cc66;">&#125;</span>;
  num Obj_Type=$<span style="color: #66cc66;">&#123;</span>ObjType<span style="color: #66cc66;">&#125;</span>;
  num capacity <span style="color: #66cc66;">&#123;</span>FACILITIES<span style="color: #66cc66;">&#125;</span>;
  str soln_type init <span style="color: #a020f0;">'${scenario}'</span>;
&nbsp;
  read <span style="color: #000080; font-weight: bold;">data</span> demandTable <span style="color: #0000ff;">into</span> PRODUCTS=<span style="color: #66cc66;">&#91;</span>Product_Name<span style="color: #66cc66;">&#93;</span> demand;
  read <span style="color: #000080; font-weight: bold;">data</span> demandTable  <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">where</span> = <span style="color: #66cc66;">&#40;</span>facility_name ne <span style="color: #a020f0;">'None'</span><span style="color: #66cc66;">&#41;</span><span style="color: #66cc66;">&#41;</span> <span style="color: #0000ff;">into</span> FIXED=<span style="color: #66cc66;">&#91;</span>Product_Name facility_name<span style="color: #66cc66;">&#93;</span>;
  read <span style="color: #000080; font-weight: bold;">data</span> siteTable <span style="color: #0000ff;">into</span> FACILITIES=<span style="color: #66cc66;">&#91;</span>facility_name<span style="color: #66cc66;">&#93;</span> close_cost fixed_cost <span style="color: #0000ff;">x</span> y capacity;
  read <span style="color: #000080; font-weight: bold;">data</span> costTable <span style="color: #0000ff;">into</span> <span style="color: #66cc66;">&#91;</span>Product_Name facility_name<span style="color: #66cc66;">&#93;</span> var_cost viable_flg;
&nbsp;
  <span style="color: #0000ff;">var</span> Assign <span style="color: #66cc66;">&#123;</span>PRODUCTS, FACILITIES<span style="color: #66cc66;">&#125;</span> binary;
  <span style="color: #0000ff;">var</span> Units <span style="color: #66cc66;">&#123;</span>PRODUCTS, FACILITIES<span style="color: #66cc66;">&#125;</span> &gt;=<span style="color: #2e8b57; font-weight: bold;">0</span>;
  <span style="color: #0000ff;">var</span> <span style="color: #0000ff;">Close</span> <span style="color: #66cc66;">&#123;</span>FACILITIES<span style="color: #66cc66;">&#125;</span> binary;
&nbsp;
  <span style="color: #0000ff;">min</span> VarCost
    = <span style="color: #0000ff;">sum</span> <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> PRODUCTS, j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span> var_cost<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span><span style="color: #006400; font-style: italic;">*Units[i,j];</span>
&nbsp;
  <span style="color: #0000ff;">min</span> FixedCost
    = <span style="color: #0000ff;">sum</span> <span style="color: #66cc66;">&#123;</span>j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span> <span style="color: #66cc66;">&#40;</span>fixed_cost<span style="color: #66cc66;">&#91;</span>j<span style="color: #66cc66;">&#93;</span><span style="color: #006400; font-style: italic;">*(1-Close[j]) + close_cost[j]*Close[j]);</span>
&nbsp;
  <span style="color: #0000ff;">min</span> TotalCost
    = VarCost + FixedCost;
&nbsp;
  <span style="color: #006400; font-style: italic;">/* Constraints */</span>
&nbsp;
  <span style="color: #006400; font-style: italic;">/* PRODUCTS demand needs to be satisfied */</span>
  con Demand_Con <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> PRODUCTS<span style="color: #66cc66;">&#125;</span>:
    <span style="color: #0000ff;">sum</span> <span style="color: #66cc66;">&#123;</span>j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span> Units<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span> &gt;= demand<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span>;
&nbsp;
  <span style="color: #006400; font-style: italic;">/* each facility have capacity constraints */</span>
  con Capacity_Con2 <span style="color: #66cc66;">&#123;</span>j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span>:
    <span style="color: #0000ff;">sum</span> <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> PRODUCTS<span style="color: #66cc66;">&#125;</span> Units<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span> &lt;= capacity<span style="color: #66cc66;">&#91;</span>j<span style="color: #66cc66;">&#93;</span><span style="color: #006400; font-style: italic;">*(1-Close[j]);</span>
&nbsp;
  <span style="color: #006400; font-style: italic;">/* if operation i assigned to site j, then facility must not be closed at j */</span>
  con If_Used_Then_Not_Closed <span style="color: #66cc66;">&#123;</span>j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span>:
    <span style="color: #0000ff;">sum</span> <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> PRODUCTS<span style="color: #66cc66;">&#125;</span> Assign<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span> = <span style="color: #66cc66;">&#40;</span><span style="color: #2e8b57; font-weight: bold;">1</span>-<span style="color: #0000ff;">Close</span><span style="color: #66cc66;">&#91;</span>j<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
  <span style="color: #006400; font-style: italic;">/*not viable assignemnts*/</span>
  con Viable_Assignemnts <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> PRODUCTS, j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span>:
    Assign<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span> &lt;= viable_flg<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span>;
&nbsp;
  con Viable_Num_Assignemnts <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> PRODUCTS, j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span>:
    Units<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span> &lt;= capacity<span style="color: #66cc66;">&#91;</span>j<span style="color: #66cc66;">&#93;</span><span style="color: #006400; font-style: italic;">*Assign[i,j];</span>
&nbsp;
  <span style="color: #006400; font-style: italic;">/*Fixing facility - what-if analysis*/</span>
  for<span style="color: #66cc66;">&#123;</span>&lt;i,j&gt; <span style="color: #0000ff;">in</span> FIXED<span style="color: #66cc66;">&#125;</span> fix Units<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span>=<span style="color: #0000ff;">min</span><span style="color: #66cc66;">&#40;</span>demand<span style="color: #66cc66;">&#91;</span>i<span style="color: #66cc66;">&#93;</span>, capacity<span style="color: #66cc66;">&#91;</span>j<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
  <span style="color: #006400; font-style: italic;">/* solve the MILP */</span>
  <span style="color: #0000ff;">if</span> Obj_Type=<span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
    solve obj VarCost with milp;
  <span style="color: #0000ff;">end</span>;
  <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> Obj_Type=<span style="color: #2e8b57; font-weight: bold;">2</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
    solve obj FixedCost with milp;
  <span style="color: #0000ff;">end</span>;
  <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> Obj_Type=<span style="color: #2e8b57; font-weight: bold;">3</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
    solve obj TotalCost with milp;
  <span style="color: #0000ff;">end</span>;
&nbsp;
  <span style="color: #006400; font-style: italic;">/* clean up the solution */</span>
  for <span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> PRODUCTS, j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span> Assign<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span> = <span style="color: #0000ff;">round</span><span style="color: #66cc66;">&#40;</span>Assign<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
  for <span style="color: #66cc66;">&#123;</span>j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">Close</span><span style="color: #66cc66;">&#91;</span>j<span style="color: #66cc66;">&#93;</span> = <span style="color: #0000ff;">round</span><span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">Close</span><span style="color: #66cc66;">&#91;</span>j<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
&nbsp;
  num siteTotalCost <span style="color: #66cc66;">&#123;</span>j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span>=
    <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> PRODUCTS<span style="color: #66cc66;">&#125;</span> var_cost<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span> <span style="color: #006400; font-style: italic;">* Units[i,j].sol + (fixed_cost[j]*(1-Close[j].sol) + close_cost[j]*Close[j].sol);</span>
&nbsp;
  num variableCosts<span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> PRODUCTS, j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span>=
    var_cost<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span><span style="color: #006400; font-style: italic;">*Units[i,j].sol;</span>
&nbsp;
  <span style="color: #006400; font-style: italic;">/* create output */</span>
  <span style="color: #0000ff;">create</span> <span style="color: #000080; font-weight: bold;">data</span> optimal_cost <span style="color: #0000ff;">from</span>
    <span style="color: #66cc66;">&#91;</span>product site<span style="color: #66cc66;">&#93;</span>=<span style="color: #66cc66;">&#123;</span>i <span style="color: #0000ff;">in</span> PRODUCTS, j <span style="color: #0000ff;">in</span> FACILITIES: Assign<span style="color: #66cc66;">&#91;</span>i,j<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#125;</span>
    var_cost variableCosts Assign.sol Units.sol;
&nbsp;
  <span style="color: #0000ff;">create</span> <span style="color: #000080; font-weight: bold;">data</span> optimal_facilities <span style="color: #0000ff;">from</span>
    <span style="color: #66cc66;">&#91;</span>site<span style="color: #66cc66;">&#93;</span>=<span style="color: #66cc66;">&#123;</span>j <span style="color: #0000ff;">in</span> FACILITIES<span style="color: #66cc66;">&#125;</span>
    <span style="color: #0000ff;">x</span> y <span style="color: #0000ff;">Close</span>.sol siteTotalCost soln_type capacity;
  `;
  <span style="color: #0000ff;">return</span> pgm;
<span style="color: #66cc66;">&#125;</span>
&nbsp;
export default optCode;</pre></td></tr></table></div>

<p>&nbsp;</p>
<h2>Analyze Output Report</h2>
<p>Finally, using React Router I am directly routing the user to the output SAS Visual Analytics report. This report helps visualize the output of the optimization model for each scenario, by showing which facilities to close and which ones to keep open. It also shows the assignment of the products to the facilities, along with the total cost and the variable cost for each scenario. The tree map on the left shows the different products, and the size of each rectangle represents the number of units of each product that is produced. It is connected to the geographic map on the right and enables the user to filter out the facilities for each product. In the next section, I will describe how we can create new scenarios and compare them side by side.</p>
<p><a href="https://blogs.sas.com/content/operations/files/2020/03/blog4.png"><img loading="lazy" decoding="async" class="alignnone size-full wp-image-1952" src="https://blogs.sas.com/content/operations/files/2020/03/blog4.png" alt="" width="1904" height="971" srcset="https://blogs.sas.com/content/operations/files/2020/03/blog4.png 1904w, https://blogs.sas.com/content/operations/files/2020/03/blog4-300x153.png 300w, https://blogs.sas.com/content/operations/files/2020/03/blog4-1024x522.png 1024w" sizes="(max-width: 1904px) 100vw, 1904px" /></a></p>
<h2>Scenario Analysis</h2>
<p>To create a new scenario, the user needs to go back to the 'Input Optimization Parameters' component using the burger menu. A new scenario may be created and the user can either change the number of units required for a product or fix a facility for a product. For the example in the video, I increased the number of units required for Sedan from 200 to 400 and fixed its assignment to Site2. When we analyze the output solutions in the SAS Visual Analytics report, we can see that the total and variable costs for the new scenario increase. The 'Solutions Comparison' tab in the report enables the user to visualize the decisions made by each solution side-by-side. We can see that the variable cost for Sedan has increased due to the increase in number of units. The new solution also requires Site2 to be open, while the optimal solution recommended that it be closed. Similarly, the user can create many more scenarios by giving each scenario a unique name and analyze them in the output report.</p>
<p><a href="https://blogs.sas.com/content/operations/files/2020/03/blog5.png"><img loading="lazy" decoding="async" class="alignnone wp-image-1953 size-full" src="https://blogs.sas.com/content/operations/files/2020/03/blog5.png" alt="Hit ESC to exit" width="1907" height="971" srcset="https://blogs.sas.com/content/operations/files/2020/03/blog5.png 1907w, https://blogs.sas.com/content/operations/files/2020/03/blog5-300x153.png 300w, https://blogs.sas.com/content/operations/files/2020/03/blog5-1024x521.png 1024w" sizes="(max-width: 1907px) 100vw, 1907px" /></a></p>
<h1>What's Next?</h1>
<p>Now, that I've shown you how easy it is to combine various SAS products using REST APIs, you can clone my repository and try building one for yourself. The code is sufficiently commented to enable users with some front-end experience to customize it for a different optimization problem. Happy Coding!</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2020/03/11/sas-optimization-web-app-using-rest-apis-with-embedded-sas-visual-analytics-reports/">SAS&reg; Optimization Web App Using REST APIs with Embedded SAS&reg; Visual Analytics Reports</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></content:encoded>
					
		
		
			<enclosure url="https://blogs.sas.com/content/operations/files/2020/03/App-frameowrk-4-150x150.png"/>
	</item>
		<item>
		<title>Batter-pitcher matchup analysis in the 2019 Major League Baseball playoffs</title>
		<link>https://blogs.sas.com/content/operations/2019/10/24/2019-mlb-playoffs-matchup-analysis/</link>
		
		<dc:creator><![CDATA[Brandon Reese]]></dc:creator>
		<pubDate>Thu, 24 Oct 2019 18:48:28 +0000</pubDate>
				<category><![CDATA[sports analytics]]></category>
		<category><![CDATA[baseball]]></category>
		<category><![CDATA[data step]]></category>
		<category><![CDATA[operations research]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[R&D Analytics]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/operations/?p=1654</guid>

					<description><![CDATA[<p>After a marathon of a season, 162 games in each team's schedule to be precise, the stakes for Major League Baseball are higher in October, and postseason play is underway. Whether it's the renewal of an old rivalry, redemption for last year's runners up, or rooting for this season's breakout [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2019/10/24/2019-mlb-playoffs-matchup-analysis/">Batter-pitcher matchup analysis in the 2019 Major League Baseball playoffs</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>After a marathon of a season, 162 games in each team's schedule to be precise, the stakes for Major League Baseball are higher in October, and postseason play is underway. Whether it's the renewal of an old rivalry, redemption for last year's runners up, or rooting for this season's breakout rookie sensation, baseball fans have plenty to cheer for each fall. In addition to being a celebrated American pastime, baseball is a sport whose structure lends itself to many flavors of statistical analysis. </p>
<p>This article demonstrates how you can use SAS to quantify past years' batter versus pitcher statistics. You can use these historical trends to highlight the matchups to watch out for this postseason. The article also lays the groundwork for solving a <a href="http://www.baseballessential.com/news/2016/06/22/mlb-leaders-lineup-optimization/">lineup optimization problem</a>. This problem is of great interest to fantasy buffs, hobbyists, as well as actual MLB managers!</p>
<h2> The data source </h2>
<p>One abundant source of Major League Baseball data is the <a href="https://www.retrosheet.org/game.htm">event files archived by season at Retrosheet.org</a>. The code below assumes that the files for each year of interest YYYY have been downloaded and extracted into a subdirectory called YYYY. Each event file (one per team, per year) contains game event logs for all home games of a particular team in a particular season. Events recorded per game include information about starting rosters, substitutions, umpires, and game conditions. In addition, they keep a record of every play-by-play event in the game.</p>
<h2> Parsing complex event files </h2>
<p>Because of the intricacies of baseball score keeping, a <a href="https://www.retrosheet.org/eventfile.htm#5">detailed notation</a> is used to describe what happened in a baseball play. For the purpose of this blog, however, you can greatly simplify the game data by simply determining whether a particular <i>at bat</i> ended up with a <i>hit</i> or with <i>no hit</i>. Using a regular expression, you can parse this result string to determine which at bats resulted in a hit, and which did not.</p>
<p>Note that many events are neither positively nor negatively considered when computing batting average -- walks and hit-by-pitch, for example -- these events are ignored for this analysis.</p>
<p>To do this, you can parse the raw data as follows:</p>
<p>As you read through the file</p>
<ol>
<li>keep track of the current pitcher for the home and away teams (see /* 1 */ and /* 2 */ below)</li>
<ul>
<li>do this by checking for a "start" or "sub" event where the position is listed as a pitcher (position code=1)</li>
<li>store the pitcher id in the appropriate variable</li>
</ul>
<li>identify which plays result in a <i>hit</i> or <i>no hit</i> (see /* 3 */ through /* 6 */ below)</li>
<ul>
<li>do this by parsing the result string that corresponds to each play</li>
</ul>
<li>record the batter, pitcher, and outcome of each applicable play as an observation in the output data set (see /* 7 */ below)</li>
</ol>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">%macro loadYears(YEAR_START, YEAR_END, DATASET, LEAGUE=MLB, DIR=C:/data/baseball);
/** Specify N or NL to import only National League home team data files **/
%if &amp;LEAGUE = NL %then %let LEAGUE=N;
/** Specify A or AL to import only American League home team data files **/
%else %if &amp;LEAGUE = AL %then %let LEAGUE=A;
%else %let LEAGUE=*;
&nbsp;
data fileList;
   length filename $ 100;
   do YR=&amp;YEAR_START to &amp;YEAR_END;
      filename=CATS(&quot;&amp;DIR/&quot;,YR,&quot;/&quot;,YR,&quot;*.EV&amp;LEAGUE&quot;);
      output;
   end;
   drop YR;
run;
&nbsp;
data &amp;DATASET;
   length pitcher $8 batter $8 outcome 4 event $30;
   if _N_ = 1 then do;
      /** Prefixes that indicate a hit **/
      hitPrefix0 = &quot;S\d&quot;; /* a single */
      hitPrefix1 = &quot;D\d&quot;; /* a double */
      hitPrefix2 = &quot;DGR&quot;; /* a ground rule double */
      hitPrefix3 = &quot;T\d&quot;; /* a triple */
      hitPrefix4 = &quot;HR&quot;;  /* a home run */
&nbsp;
      /** Prefixes that indicate no hit **/
      noHitPrefix0 = &quot;[1-9]&quot;; /* an out in the field */
      noHitPrefix1 = &quot;FC&quot;;    /* a fielder's choice */
      noHitPrefix2 = &quot;E&quot;;     /* a fielding error */
      noHitPrefix3 = &quot;K&quot;;     /* a strikeout */
&nbsp;
      /** Build a regular expressions that check the prefixes **/
      expHit   =  &quot;/(&quot; || hitPrefix0 || &quot;)|(&quot; || hitPrefix1 || &quot;)|(&quot; || hitPrefix2 || &quot;)|(&quot; || hitPrefix3 || &quot;)|(&quot; || hitPrefix4 || &quot;)/&quot;;
      expNoHit =  &quot;/(&quot; || noHitPrefix0 || &quot;)|(&quot; || noHitPrefix1 || &quot;)|(&quot; || noHitPrefix2 || &quot;)|(&quot; || noHitPrefix3 || &quot;)/&quot;;
      retain reHit;   /* regular expression matching a hit event */
      retain reNoHit; /* regular expression matching a no-hit event */
      reHit = prxparse(expHit);
      reNoHit = prxparse(expNoHit);
&nbsp;
      /** Error checking will tell if the regular expression is invalid **/
      if missing(reHit) then do;
         putlog &quot;ERROR: Invalid expression &quot; expHit;
         stop;
      end;  
      if missing(reNoHit) then do;
         putlog &quot;ERROR: Invalid expression &quot; expNoHit;
         stop;
      end;  
   end;
&nbsp;
   set fileList;
   infile dummy fileVar=filename end=done DLM=',' DSD MISSOVER;
   do while(^done);
      format pitcherHome $10. pitcherAway $10. pitcher $10.;
&nbsp;
      /** need the current home and away pitchers to carry over between observations **/
      retain pitcherHome pitcherAway;
      input eventType $ col1 $ col2 $ col3 $ col4 $ col5 $ col6 $;
&nbsp;
      /** 
       Events of interest are given in the formats:
       start, playerID, playerName,isHomeTeam,battingOrder,positionCode
       sub, playerID, playerName,isHomeTeam,battingOrder,positionCode
       play, inningNumber, batterIsHomeTeam, batterPlayerID,ballStrikeCount,pitchResultSequence,playResult
      **/
      if eventType in (&quot;start&quot;, &quot;sub&quot;) and col3 = &quot;0&quot; and col5 = &quot;1&quot; then do;                                     /* 1 */
         /** pitcher entering for away team **/
         pitcherAway = col1;
      end;
      else if eventType in (&quot;start&quot;, &quot;sub&quot;) and col3 = &quot;1&quot; and col5 = &quot;1&quot; then do;                                /* 2 */
         /** pitcher entering for home team **/
         pitcherHome = col1;
      end;
      else if eventType=&quot;play&quot; then do;                                                                           /* 3 */
         batter=col3;
&nbsp;
         if col2=&quot;0&quot; then pitcher=pitcherHome;                                                                    /* 4 */
         else pitcher=pitcherAway;
&nbsp;
         event = col6;
         if prxmatch(reHit, event) = 1 then do                                                                    /* 5 */
            outcome = 1;
            output;
         end;
         else if prxmatch(reNoHit, event) = 1 then do;                                                            /* 6 */
            outcome = 0;
            output;
         end;
      end;
   end;
   keep batter pitcher outcome event;                                                                             /* 7 */
run;
&nbsp;
%mend loadYears;</pre></td></tr></table></div>

<h2> Aggregating the data </h2>
<p>After producing a data set of hit/no-hit outcomes, you can aggregate these data to produce one observation per batter-pitcher pair. For example, you can aggregate the number of at bats "ab" and the batting average "avg". To limit the analysis to players in the 2019 playoffs, <a href="https://raw.githubusercontent.com/brandonmreese/mlb-analysis/master/rosters.txt">here</a> is a roster data file. You can use this file to create a data set containing the 25-player active roster of each playoff team:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">data playoffRosters;
   infile &quot;rosters.txt&quot;;
   input playerId $ team $;
run;</pre></td></tr></table></div>

<p>Next, the following macro aggregates batter-pitcher matchup data for a particular list of MLB players.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">%macro dataPrep(outcomes, bvp, playerList);
%global nBatters nPitchers;
&nbsp;
/* Aggregate per batter-pitcher pair */
proc sql;
   create table &amp;bvp.Tmp as
   select batter, pitcher, sum(outcome)/count(outcome) as avg, count(outcome) as ab
   from &amp;outcomes
   group by batter, pitcher;
quit;
&nbsp;
/* Aggregate stats per batter and per pitcher. Keep only batters and pitchers in playerList. */
proc sql;
   create table battersTmp as
   select a.batter as batter,
          SUM(a.ab) as ab,
          SUM(a.avg*a.ab)/SUM(a.ab) as avg
   from &amp;bvp.Tmp as a
   join &amp;playerList as b
   on a.batter EQ b.playerid
   group by batter
   order by ab desc
   ;
quit;
proc sql;
   create table pitchersTmp as
   select a.pitcher,
          SUM(a.ab) as ab,
          SUM(a.avg*a.ab)/SUM(a.ab) as avg
   from &amp;bvp.Tmp as a
   join &amp;playerList as b
   on a.pitcher EQ b.playerid
   group by pitcher
   order by ab desc
   ;
quit;
&nbsp;
/* Join together */
proc sql;
   create table &amp;bvp as
   select a.*,
          b.ab as batter_ab,
          b.avg as batter_avg,
          c.ab as pitcher_ab,
          c.avg as pitcher_avg
   from &amp;bvp.Tmp a
   join battersTmp b
   on a.batter = b.batter
   join pitchersTmp c
   on a.pitcher = c.pitcher
   ;
   select count (distinct batter) into :nBatters
   from &amp;bvp;
   select count (distinct pitcher) into :nPitchers
   from &amp;bvp;
quit;
&nbsp;
proc sql;
   drop table battersTmp;
   drop table pitchersTmp;
   drop table &amp;bvp.Tmp;
quit;
%mend dataPrep;</pre></td></tr></table></div>

<h2> Taking a quick look into the data </h2>
<p>Now, for the analysis: you can invoke the macros</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">%let LEAGUE=MLB;
%loadYears(2010, 2018, outcomes, LEAGUE=&amp;LEAGUE);
%dataPrep(outcomes, bvp, playoffRosters);</pre></td></tr></table></div>

<p>and inspect the data</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">proc sort data=bvp;
   by descending ab;
run;
proc print data=bvp (obs=3); run;</pre></td></tr></table></div>

<p><a href="https://blogs.sas.com/content/operations/files/2019/10/top3MLB.png"><img decoding="async" src="https://blogs.sas.com/content/operations/files/2019/10/top3MLB.png" alt="" width="800" class="alignnone size-medium wp-image-1838" /></a></p>
<p>This indicates that the batter-pitcher pairs with the most at bats from 2010 to 2018 are Michael Brantley (branm003) against Justin Verlander (verlj001), Asdrubal Cabrera (cabra002) against Max Scherzer (schem001), and Nick Markakis (markn001) against Max Scherzer (schem001).</p>
<p>Next, you can use the following statements to see all the historical batting averages of the key batter-pitcher matchups. To avoid noise from tiny sample sets, you can filter out matchups with fewer than 10 at bats. In the generated heat map visualization, red corresponds to higher batting average and blue corresponds to lower batting average.  Since the National League and American League teams play each other infrequently, it is helpful to show them one at a time. Here are the National League matchups:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">%let LEAGUE=NL;
%loadYears(2010, 2018, outcomes, LEAGUE=&amp;LEAGUE);
%dataPrep(outcomes, bvp, playoffRosters);
proc sgplot data=bvp(where=(ab GE 10)) noautolegend;
   title &quot;&amp;LEAGUE Matchup Batting Averages 2010-2018&quot;;
   heatmap y=batter x=pitcher / weight=avg
      colormodel=(blue lightBlue pink red) outline x2axis;
   xaxis display=none;
   x2axis display=ALL;
   yaxis display=ALL reverse;
run;</pre></td></tr></table></div>

<p><a href="https://blogs.sas.com/content/operations/files/2019/10/heatmapNL.png"><img decoding="async" src="https://blogs.sas.com/content/operations/files/2019/10/heatmapNL.png" alt="" width="820" class="alignnone size-medium wp-image-1836" /></a></p>
<p>and the American League matchups:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="text" style="font-family:monospace;">%let LEAGUE=AL;
%loadYears(2010, 2018, outcomes, LEAGUE=&amp;LEAGUE);
%dataPrep(outcomes, bvp, playoffRosters);
proc sgplot data=bvp(where=(ab GE 10)) noautolegend;
   title &quot;&amp;LEAGUE Matchup Batting Averages 2010-2018&quot;;
   heatmap y=batter x=pitcher / weight=avg
      colormodel=(blue lightBlue pink red) outline x2axis;
   xaxis display=none;
   x2axis display=ALL;
   yaxis display=ALL reverse;
run;</pre></td></tr></table></div>

<p><a href="https://blogs.sas.com/content/operations/files/2019/10/heatmapAL.png"><img decoding="async" src="https://blogs.sas.com/content/operations/files/2019/10/heatmapAL.png" alt="" width="820" class="alignnone size-medium wp-image-1837" /></a></p>
<p>Noteworthy matchups from the heat maps include:</p>
<ul>
<li> <font color="red"> Anthony Rendon (renda001) has a .583 batting average in 12 at bats against Wade Miley (milew001) </font>
<li> <font color="blue"> Paul Goldschmidt (goldp001) has a .048 batting average in 21 at bats against Pedro Baez (baezp001) </font>
<li> <font color="red"> Alex Bregman (brega001) has a .500 batting average in 10 at bats against Blake Snell (snelb001) </font>
<li> <font color="blue"> Yuli Gurriel (gurry001) has a .136 batting average in 10 at bats against James Paxton (paxtj001) </font>
</ul>
<p>So, how have these actually panned out? Alex Bregman faced Blake Snell in game 2 of the ALDS. Bregman had 1 hit in 2 at bats including a home run off Snell! Goldschmidt never got to face Baez, as the Dodgers didn't make it out of the NLDS. Yuli Gurriel was hitless in 4 at bats against James Paxton in the ALCS. Anthony Rendon won't get to face Wade Miley since the Astros have not included Miley on the World Series roster.</p>
<h2> Coming up next </h2>
<p>Baseball managers have often criticized batter-pitcher matchup data because of their poor predictive power. This is typically due to very small sample sizes. In a future post, I will analyze this data using collaborative filtering, a machine learning technique that will help address the problem of small sample sizes. From there, I will explore ways to optimally choose a lineup of batters to face a particular pitcher. On an introductory level, this series of posts will demonstrate an end-to-end approach to lineup optimization.</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2019/10/24/2019-mlb-playoffs-matchup-analysis/">Batter-pitcher matchup analysis in the 2019 Major League Baseball playoffs</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></content:encoded>
					
		
		
			<enclosure url="https://blogs.sas.com/content/operations/files/2019/10/baseball-public-domain-unsplash-150x150.jpg"/>
	</item>
		<item>
		<title>Bringing Analytics to the Soccer Transfer Season</title>
		<link>https://blogs.sas.com/content/operations/2019/06/18/bringing-analytics-to-soccer-transfer-season/</link>
		
		<dc:creator><![CDATA[Sertalp B. Cay]]></dc:creator>
		<pubDate>Tue, 18 Jun 2019 14:58:58 +0000</pubDate>
				<category><![CDATA[mixed integer linear optimization]]></category>
		<category><![CDATA[sports analytics]]></category>
		<category><![CDATA[modeling]]></category>
		<category><![CDATA[operations research]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[python]]></category>
		<category><![CDATA[R&D Analytics]]></category>
		<category><![CDATA[sports]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/operations/?p=1655</guid>

					<description><![CDATA[<p>&#160; Major European football (soccer) leagues came to an end after an intensive year. Manchester City claimed the Premier League title after a high-intensity title race against Liverpool. It is unbelievable that Liverpool would have won 25 out of the last 27 PL titles with 97 points. Luckily, the Reds [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2019/06/18/bringing-analytics-to-soccer-transfer-season/">Bringing Analytics to the Soccer Transfer Season</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<p>&nbsp;</p>
<p>Major European football (soccer) leagues came to an end after an intensive year. Manchester City claimed the Premier League title after a high-intensity title race against Liverpool. It is unbelievable that <a href="https://twitter.com/SkySportsStatto/status/1127602210777522176">Liverpool would have won 25 out of the last 27 PL titles</a> with 97 points. Luckily, the Reds deserved and won the Champions League, and redeemed themselves after losing last year's finale.</p>
<p>Football is over on the pitch for the season, but the battle for the transfer season is beginning now. Money shapes major football leagues all over the world despite rare successes by lower-budget teams such as <a href="https://en.wikipedia.org/wiki/2015%E2%80%9316_Premier_League">Leicester City in 2016</a>. Teams across Europe are changing the outcome of their domestic leagues with massive transfer budgets. Transfer season is the time when teams are shaping their potential for the next season for sure. Sports analytics is often used to analyze teams on the pitch, but it is possible to bring it to the transfer season also. We have a chance to analyze the upcoming transfer season using mathematical optimization and the capabilities of SAS Viya.</p>
<h1>Analytics for Transfer Season</h1>
<p>In football, <a href="https://en.wikipedia.org/wiki/Transfer_(association_football)">players can move between clubs during the transfer season</a>. If they are out of contract, clubs can acquire them and sign a contract. Otherwise, the current contract needs to be terminated before any transfer. In this case, the purchasing team pays an amount called the <i>transfer fee</i>.</p>
<p>"How should we allocate our transfer budget to maximize the benefit we gain?" This is the ultimate question that every team needs to answer. (Teams often try to answer  "Which player should we get to make our fans happy?", but no one truly knows what could make fans happy.) Maximizing benefit under a limited resource is known as the <a href="https://en.wikipedia.org/wiki/Knapsack_problem">Knapsack problem</a> in combinatorial optimization. Given a set of items and their values, the Knapsack problem is to find the optimal selection of items to pack within a weight limit to maximize the total value. We can ask a similar question here: given a set of players, their values and ratings, how to choose which players to transfer to maximize total team rating within a budget limit.</p>
<p>Even though writing a detailed mathematical model of the problem is challenging, I will show how a simple model can be written to benefit from the capabilities of optimization. Before we dive any further, note that we are solving a simplified problem under the following assumptions to make things easier:</p>
<ul>
<li>We consider only the starting lineup to measure team ratings</li>
<li>Teams can transfer any player as long as their current value is paid</li>
<li>We only focus on acquiring players, not selling them</li>
<li>Teams use the same formation for the next year</li>
<li>Players can be played only at the positions they are listed in the data set</li>
</ul>
<h2>Data</h2>
<p>One of the most challenging stages of any analytical problem is to obtain clean data. At this point, we are lucky to have a great web resource: <a href="http://sofifa.com">sofifa.com</a>. SoFIFA has more data than we need for this problem. By using parallel web requests, we managed to create a database of 12,000 players sorted by their overall rating. The web scraper is available on <a href="https://github.com/sertalpbilal/soccer-transfer-problem" target="_blank" rel="noopener noreferrer">GitHub</a> and the data are available as a <a href="https://raw.githubusercontent.com/sertalpbilal/soccer-transfer-problem/master/playerdb.csv" target="_blank" rel="noopener noreferrer">CSV file</a>.</p>
<p><em>As an important side note, since these models are being run on data based on the football game FIFA, not on real player metrics, they are a better reflection of the players in the computer game, not the players in real life. However, these same concepts can be applied to real player data if you have access to it.</em></p>
<h2>Model</h2>
<p>Our aim is to maximize the sum of player ratings in the starting lineup of teams. We will solve the problem separately for each team. For each position, we filter the list of players who have a better rating than what the team currently has. Then, the increase in the total rating is used to measure the performance of the transfer for the team.</p>
<p>Let us define <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\textrm{P}" /></span><script type='math/tex'>\textrm{P}</script> as the set of all players, <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\textrm{S}" /></span><script type='math/tex'>\textrm{S}</script> as the set of team positions, and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\textrm{E}" /></span><script type='math/tex'>\textrm{E}</script> as the set of player-position pairs. The following parameters are used to define the problem:</p>
<ul>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\bar{R}_j" /></span><script type='math/tex'>\bar{R}_j</script>: Current rating of the player at position <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="j" /></span><script type='math/tex'>j</script></li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="R_i" /></span><script type='math/tex'>R_i</script>: Overall rating of player <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="i" /></span><script type='math/tex'>i</script></li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="B" /></span><script type='math/tex'>B</script>: Team budget</li>
<li><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="V_i" /></span><script type='math/tex'>V_i</script>: Transfer value of player <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="i" /></span><script type='math/tex'>i</script></li>
</ul>
<p>The main decision variable <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="t_{ij}" /></span><script type='math/tex'>t_{ij}</script> represents a binary variable, whether player <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="i" /></span><script type='math/tex'>i</script> is transferred for position <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="j" /></span><script type='math/tex'>j</script>. We also have an auxiliary variable <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r_j" /></span><script type='math/tex'>r_j</script> to define the final rating for position <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="j" /></span><script type='math/tex'>j</script> in the formation.</p>
<p>The objective function can be written as the summation of the final ratings:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\textrm{maximize} \sum\limits_{j \in \textrm{S}} r_j" /></span><script type='math/tex'>\textrm{maximize} \sum\limits_{j \in \textrm{S}} r_j</script></p>
<p>Our first constraint is the budget for the transfer. The total value of players transferred cannot exceed the team budget:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\sum\limits_{(i,j) \in \textrm{E}} V_i \cdot t_{ij} \leq B" /></span><script type='math/tex'>\sum\limits_{(i,j) \in \textrm{E}} V_i \cdot t_{ij} \leq B</script></p>
<p>The next constraint defines the final rating for each position. This constraint accounts for transfer player <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="i" /></span><script type='math/tex'>i</script> replacing the current player at position <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="j" /></span><script type='math/tex'>j</script>:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r_j = \bar{R}_j + \sum\limits_{i \in \textrm{P}: (i,j) \in \textrm{E}} (R_{i} - \bar{R}_{j}) \cdot t_{ij} \qquad \forall j \in \textrm{S}" /></span><script type='math/tex'>r_j = \bar{R}_j + \sum\limits_{i \in \textrm{P}: (i,j) \in \textrm{E}} (R_{i} - \bar{R}_{j}) \cdot t_{ij} \qquad \forall j \in \textrm{S}</script></p>
<p>The following two constraints satisfy conditions that at most one player is transferred for a given position, and the one player cannot be transferred for two different positions:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\sum\limits_{j \in \textrm{S}: (i,j) \in \textrm{E}} t_{ij} \leq 1 \qquad \forall i \in \textrm{P}" /></span><script type='math/tex'>\sum\limits_{j \in \textrm{S}: (i,j) \in \textrm{E}} t_{ij} \leq 1 \qquad \forall i \in \textrm{P}</script></p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\sum\limits_{i \in \textrm{P}: (i,j) \in \textrm{E}} t_{ij} \leq 1 \qquad \forall j \in \textrm{S}" /></span><script type='math/tex'>\sum\limits_{i \in \textrm{P}: (i,j) \in \textrm{E}} t_{ij} \leq 1 \qquad \forall j \in \textrm{S}</script></p>
<h2>Python Model</h2>
<p>We model this problem using <a href="https://github.com/sassoftware/sasoptpy"><em>sasoptpy</em></a>, an open-source Python interface of <a href="https://www.sas.com/en_us/software/optimization.html">SAS Optimization</a>.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="python" style="font-family:monospace;">m <span style="color: #66cc66;">=</span> so.<span style="color: black;">Model</span><span style="color: black;">&#40;</span>name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'optimal_team'</span><span style="color: #66cc66;">,</span> session<span style="color: #66cc66;">=</span>session<span style="color: black;">&#41;</span>
&nbsp;
rating <span style="color: #66cc66;">=</span> m.<span style="color: black;">add_variables</span><span style="color: black;">&#40;</span>POSITIONS<span style="color: #66cc66;">,</span> name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'rating'</span><span style="color: black;">&#41;</span>
transfer <span style="color: #66cc66;">=</span> m.<span style="color: black;">add_variables</span><span style="color: black;">&#40;</span>ELIG<span style="color: #66cc66;">,</span> name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'transfer'</span><span style="color: #66cc66;">,</span> vartype<span style="color: #66cc66;">=</span>so.<span style="color: black;">BIN</span><span style="color: black;">&#41;</span>
&nbsp;
m.<span style="color: black;">set_objective</span><span style="color: black;">&#40;</span>
  so.<span style="color: black;">quick_sum</span><span style="color: black;">&#40;</span>rating<span style="color: black;">&#91;</span>j<span style="color: black;">&#93;</span> <span style="color: #ff7700;font-weight:bold;">for</span> j <span style="color: #ff7700;font-weight:bold;">in</span> POSITIONS<span style="color: black;">&#41;</span><span style="color: #66cc66;">,</span> name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'total_rating'</span><span style="color: #66cc66;">,</span> sense<span style="color: #66cc66;">=</span>so.<span style="color: black;">MAX</span><span style="color: black;">&#41;</span>
&nbsp;
m.<span style="color: black;">add_constraint</span><span style="color: black;">&#40;</span>
  so.<span style="color: black;">quick_sum</span><span style="color: black;">&#40;</span>transfer<span style="color: black;">&#91;</span>i<span style="color: #66cc66;">,</span> j<span style="color: black;">&#93;</span> * value<span style="color: black;">&#91;</span>i<span style="color: black;">&#93;</span> <span style="color: #ff7700;font-weight:bold;">for</span> <span style="color: black;">&#40;</span>i<span style="color: #66cc66;">,</span> j<span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">in</span> ELIG<span style="color: black;">&#41;</span> <span style="color: #66cc66;">&lt;=</span> budget<span style="color: #66cc66;">,</span> name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'budget_con'</span><span style="color: black;">&#41;</span>
&nbsp;
m.<span style="color: black;">add_constraints</span><span style="color: black;">&#40;</span><span style="color: black;">&#40;</span>
  rating<span style="color: black;">&#91;</span>j<span style="color: black;">&#93;</span> <span style="color: #66cc66;">==</span> overall<span style="color: black;">&#91;</span>member<span style="color: black;">&#91;</span>j<span style="color: black;">&#93;</span><span style="color: black;">&#93;</span> + so.<span style="color: black;">quick_sum</span><span style="color: black;">&#40;</span>
      transfer<span style="color: black;">&#91;</span>i<span style="color: #66cc66;">,</span> j<span style="color: black;">&#93;</span> * <span style="color: black;">&#40;</span>overall<span style="color: black;">&#91;</span>i<span style="color: black;">&#93;</span> - overall<span style="color: black;">&#91;</span>member<span style="color: black;">&#91;</span>j<span style="color: black;">&#93;</span><span style="color: black;">&#93;</span><span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">for</span> <span style="color: black;">&#40;</span>i<span style="color: #66cc66;">,</span> j2<span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">in</span> ELIG <span style="color: #ff7700;font-weight:bold;">if</span> j<span style="color: #66cc66;">==</span>j2<span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">for</span> j <span style="color: #ff7700;font-weight:bold;">in</span> POSITIONS<span style="color: black;">&#41;</span><span style="color: #66cc66;">,</span> name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'transfer_con'</span><span style="color: black;">&#41;</span>
&nbsp;
m.<span style="color: black;">add_constraints</span><span style="color: black;">&#40;</span><span style="color: black;">&#40;</span>
  so.<span style="color: black;">quick_sum</span><span style="color: black;">&#40;</span>transfer<span style="color: black;">&#91;</span>i<span style="color: #66cc66;">,</span> j<span style="color: black;">&#93;</span> <span style="color: #ff7700;font-weight:bold;">for</span> <span style="color: black;">&#40;</span>i2<span style="color: #66cc66;">,</span> j<span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">in</span> ELIG <span style="color: #ff7700;font-weight:bold;">if</span> i<span style="color: #66cc66;">==</span>i2<span style="color: black;">&#41;</span> <span style="color: #66cc66;">&lt;=</span> <span style="color: #ff4500;">1</span> <span style="color: #ff7700;font-weight:bold;">for</span> i <span style="color: #ff7700;font-weight:bold;">in</span> PLAYERS<span style="color: black;">&#41;</span><span style="color: #66cc66;">,</span> name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'only_one_position'</span><span style="color: black;">&#41;</span>
&nbsp;
m.<span style="color: black;">add_constraints</span><span style="color: black;">&#40;</span><span style="color: black;">&#40;</span>
  so.<span style="color: black;">quick_sum</span><span style="color: black;">&#40;</span>transfer<span style="color: black;">&#91;</span>i<span style="color: #66cc66;">,</span> j<span style="color: black;">&#93;</span> <span style="color: #ff7700;font-weight:bold;">for</span> <span style="color: black;">&#40;</span>i<span style="color: #66cc66;">,</span> j2<span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">in</span> ELIG <span style="color: #ff7700;font-weight:bold;">if</span> j<span style="color: #66cc66;">==</span>j2<span style="color: black;">&#41;</span> <span style="color: #66cc66;">&lt;=</span> <span style="color: #ff4500;">1</span> <span style="color: #ff7700;font-weight:bold;">for</span> j <span style="color: #ff7700;font-weight:bold;">in</span> POSITIONS<span style="color: black;">&#41;</span><span style="color: #66cc66;">,</span> name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'only_one_transfer'</span><span style="color: black;">&#41;</span>
&nbsp;
m.<span style="color: black;">solve</span><span style="color: black;">&#40;</span><span style="color: black;">&#41;</span></pre></td></tr></table></div>

<p>Notice that it is very easy to model this problem using the Python interface. Our open-source optimization modeling package sasoptpy uses the <a href="https://go.documentation.sas.com/?docsetId=casactmopt&amp;docsetTarget=casactmopt_optimization_details03.htm&amp;docsetVersion=8.4&amp;locale=en" rel="noopener noreferrer">runOptmodel</a> action under the hood, as shown in <a href="https://sassoftware.github.io/sasoptpy/examples.html">examples in the documentation</a>. If you are familiar with <a href="https://go.documentation.sas.com/?docsetId=ormpug&amp;docsetTarget=ormpug_optmodel_toc.htm&amp;docsetVersion=15.1&amp;locale=en" rel="noopener noreferrer">PROC OPTMODEL</a>, you can write the SAS code and run it on SAS Viya directly.</p>
<h2>Results</h2>
<p>We have run the optimal transfer problem for the top six teams in Premier League standings: Manchester City, Liverpool, Chelsea, Tottenham, Arsenal, Manchester United. The current team and budget information are obtained from SoFIFA at the time of execution. We filtered out all the players older than 33 years old since <a href="https://content.iospress.com/articles/journal-of-sports-analytics/jsa0021">a majority of players reach their peak before 33</a> and steadily lose performance.</p>
<p>See the table below for a comparison between optimal transfers for each team. The positions of the transfers are given in the following figures below the table.</p>
<table class="tg" style="border-collapse: collapse;border-spacing: 0;border-color: #ccc">
<tbody>
<tr>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: left;border: 0px solid #ccc">Team</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;border: 0px solid #ccc">Old Rating</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;border: 0px solid #ccc">Avg</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;vertical-align: top;border: 0px solid #ccc">New Rating</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;vertical-align: top;border: 0px solid #ccc">Avg</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;vertical-align: top;border: 0px solid #ccc">Budget</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;vertical-align: top;border: 0px solid #ccc">Money Spent</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;vertical-align: top;border: 0px solid #ccc">Efficiency</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: left;vertical-align: top;border: 0px solid #ccc">Transfers</th>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;border: 0px solid #ccc">Manchester City</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;border: 0px solid #ccc">944</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;border: 0px solid #ccc">85.818</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">972</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">88.364</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€170.0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€170.0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.164706</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Giorgio Chiellini, Thiago Emiliano da Silva, Jordi Alba Ramos, C. Ronaldo<br />
dos Santos Aveiro</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;border: 0px solid #ccc">Liverpool</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;border: 0px solid #ccc">932</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;border: 0px solid #ccc">84.727</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">949</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">86.273</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€90.0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€89.5M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.189944</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Łukasz Piszczek, Giorgio Chiellini, Sergio Busquets Burgos</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Chelsea</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">925</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">84.091</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">948</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">86.182</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€95.0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€94.0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.244681</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Samir Handanovič, Giorgio Chiellini, Thiago Emiliano da Silva, Marco<br />
Parolo</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Tottenham Hotspur</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">933</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">84.818</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">949</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">86.273</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€85.0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€82.0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.195122</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Filipe Luís Kasmirski, Marco Parolo, Sergio Busquets Burgos</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Arsenal</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">905</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">82.273</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">933</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">84.818</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€92.5M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€90.0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.311111</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Lars Bender, Giorgio Chiellini, Filipe Luís Kasmirski, Fernando Luiz Rosa</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Manchester United</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">915</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">83.182</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">951</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">86.455</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€175.0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€174.0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.206897</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">César Azpilicueta Tanco, Giorgio Chiellini, Thiago Emiliano da Silva,<br />
Filipe Luís Kasmirski, Luka Modrić</td>
</tr>
</tbody>
</table>
<p>As mentioned above, we do not consider the likelihood of the transfer itself. We consider what money could buy if teams are able to get players at their current valuation.</p>
<p>Manchester City increases its total team rating from 944 to 972 by 28 points if they spend all of their current transfer budget of €170M. It is not surprising to see that with a rather limited budget of €90M, Liverpool can increase its total rating by 17 points, whereas Manchester United's total team rating can increase 36 points with their massive budget of €175M.</p>
<p>The efficiency column is calculated by dividing the change in total rating by total money spent in million euros. We expect the efficiency of the transfer to be larger when a few players have significantly lower ratings compared to the rest of the team and can be replaced with rather cheap alternatives. Arsenal has the highest efficiency and can increase its total rating 0.31 per million euros by purchasing 4 players.</p>
<p>The reason why the total rating of Liverpool does not increase as much as Arsenal's despite having close transfer budgets can be explained by the variation of the player ratings. The rating of the right back (RB) is increased 9 points (from 73 to 82) with a transfer worth of €17M for Arsenal. Liverpool's lowest rating in the current team is 80. Player values tend to increase sharply as we increase the rating:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1725" src="https://blogs.sas.com/content/operations/files/2019/06/rating_vs_value.png" alt="" width="778" height="460" srcset="https://blogs.sas.com/content/operations/files/2019/06/rating_vs_value.png 778w, https://blogs.sas.com/content/operations/files/2019/06/rating_vs_value-300x177.png 300w" sizes="(max-width: 778px) 100vw, 778px" /></p>
<p>Therefore, it is clear why some teams have an advantage in the transfer season. For these teams, it is easy to improve the team by replacing the weakest player. Consider these two extremes: Manchester City has to spend €170M to improve its total rating by 28 points, whereas Arsenal increases its total rating the same amount by spending €90M only.</p>
<p>Here's how the old and new lineups look for each team. New transfers are colored red while existing players are in blue:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1691" src="https://blogs.sas.com/content/operations/files/2019/06/Manchester-City.png" alt="" width="930" height="596" /></p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1695" src="https://blogs.sas.com/content/operations/files/2019/06/Liverpool.png" alt="" width="930" height="596" /></p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1694" src="https://blogs.sas.com/content/operations/files/2019/06/Chelsea.png" alt="" width="930" height="596" /></p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1697" src="https://blogs.sas.com/content/operations/files/2019/06/Tottenham-Hotspur.png" alt="" width="930" height="596" /></p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1693" src="https://blogs.sas.com/content/operations/files/2019/06/Arsenal.png" alt="" width="930" height="596" /></p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1696" src="https://blogs.sas.com/content/operations/files/2019/06/Manchester-United.png" alt="" width="930" height="596" /></p>
<h2>Budget Limitations</h2>
<p>In the last problem, we will have a look at how the budget is affecting the decisions. We will be varying the transfer budget of Liverpool from €0 to €200M in increments of €10M to see how it affects the outcome.</p>
<table class="tg" style="border-collapse: collapse;border-spacing: 0;border-color: #ccc">
<tbody>
<tr>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;border: 0px solid #ccc">New Rating</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;border: 0px solid #ccc">Avg.New</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;border: 0px solid #ccc">Budget</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;vertical-align: top;border: 0px solid #ccc">Money Spent</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;vertical-align: top;border: 0px solid #ccc">Efficiency</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: left;vertical-align: top;border: 0px solid #ccc">Transfers</th>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;border: 0px solid #ccc">932</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;border: 0px solid #ccc">84.727</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;border: 0px solid #ccc">€0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€0M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc"></td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;border: 0px solid #ccc">933</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;border: 0px solid #ccc">84.818</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;border: 0px solid #ccc">€10M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€7M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.142857</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Łukasz Piszczek</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">936</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">85.091</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€20M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€20M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.205128</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Łukasz Piszczek, João Miranda de Souza Filho</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">939</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">85.364</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€30M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€24M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.291667</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Thiago Emiliano da Silva</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">942</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">85.636</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€40M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€38M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.263158</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Łukasz Piszczek, Giorgio Chiellini</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">943</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">85.727</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€50M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€48M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.229167</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Lars Bender, Giorgio Chiellini</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">945</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">85.909</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€60M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€56M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.234234</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Kyle Walker, Giorgio Chiellini</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">946</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">86.000</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€70M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€62M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.227642</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">César Azpilicueta Tanco, Giorgio Chiellini</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">947</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">86.091</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€80M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€76M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.197368</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Joshua Kimmich, Giorgio Chiellini</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">949</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">86.273</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€90M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€90M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.189944</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Łukasz Piszczek, Giorgio Chiellini, Sergio Busquets Burgos</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">950</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">86.364</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€100M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€98M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.183673</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Giorgio Chiellini, Luka Modrić</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">952</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">86.545</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€110M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€107M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.186916</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Kyle Walker, Giorgio Chiellini, Sergio Busquets Burgos</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">953</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">86.636</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€120M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€116M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.181818</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Kyle Walker, Giorgio Chiellini, David Josué Jiménez Silva</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">955</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">86.818</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€130M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€129M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.178988</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">César Azpilicueta Tanco, Giorgio Chiellini, Luka Modrić</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">955</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">86.818</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€140M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€129M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.178988</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">César Azpilicueta Tanco, Giorgio Chiellini, Luka Modrić</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">957</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">87.000</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€150M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€149M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.167785</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">César Azpilicueta Tanco, Giorgio Chiellini, Fernando Luiz Rosa, Luka<br />
Modrić</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">958</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">87.091</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€160M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€160M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.163009</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">César Azpilicueta Tanco, Giorgio Chiellini, Jordi Alba Ramos, David Josué<br />
Jiménez Silva</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">959</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">87.182</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€170M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€167M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.162162</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">César Azpilicueta Tanco, Giorgio Chiellini, Jordi Alba Ramos, Luka Modrić</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">961</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">87.364</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€180M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€180M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.161111</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">César Azpilicueta Tanco, Giorgio Chiellini, Luka Modrić, Sergio Busquets<br />
Burgos</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">962</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">87.455</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€190M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">€189M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">0.159151</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">César Azpilicueta Tanco, Giorgio Chiellini, Luka Modrić, David Josué<br />
Jiménez Silva</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">962</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">87.455</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€200M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">€189M</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">0.159151</td>
<td style="font-family: Arial, sans-serif;font-size: 11px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">César Azpilicueta Tanco, Giorgio Chiellini, Luka Modrić, David Josué<br />
Jiménez Silva</td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
<p>As seen below in detail, efficiency (total rating increase per million euros) decreases as we pay more money for a relatively lower change, as expected.</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1707" src="https://blogs.sas.com/content/operations/files/2019/06/rating_vs_efficiency.png" alt="" width="730" height="429" /></p>
<p>&nbsp;</p>
<p>It seems Liverpool gets the best worth of its money if the Reds transfer Thiago Emiliano da Silva for CB position. Notice that efficiency converges to 0.16 total rating increase per million euros spent as we keep increasing the budget.</p>
<h3>Increasing the potential</h3>
<p>We have looked only at the current ratings of the players up to this point. The next problem we solve includes "potential" ratings of the new transfers. Naturally, young players have a significantly higher potential value compared to the old players. We need to replace the rating constraint as follows:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="r_j = \bar{P}_j + \sum\limits_{i \in \textrm{P}: (i,j) \in \textrm{E}} (P_{i} - \bar{P}_{j}) \cdot t_{ij} \qquad \forall j \in \textrm{S}" /></span><script type='math/tex'>r_j = \bar{P}_j + \sum\limits_{i \in \textrm{P}: (i,j) \in \textrm{E}} (P_{i} - \bar{P}_{j}) \cdot t_{ij} \qquad \forall j \in \textrm{S}</script></p>
<p>where <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="P_i" /></span><script type='math/tex'>P_i</script> is the potential rating of a player, and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\bar P_j" /></span><script type='math/tex'>\bar P_j</script> is the potential of the current player at position <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="j" /></span><script type='math/tex'>j</script> in the team.</p>
<p>For players under 25 years old, the optimal solution is to replace Henderson and Matip with Melo and de Ligt for €36M and €44M, respectively. These changes increase the potential rating by 18 points:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1736" src="https://blogs.sas.com/content/operations/files/2019/06/Liverpool-under-25.png" alt="" width="930" height="596" /></p>
<p><em>Edit</em>: An earlier version of the blog post compared potential ratings of new transfers to current ratings of the current team. After fixing the problem, results have changed slightly.</p>
<p><em>Edit #2</em>: We have updated results after fixing a filtering issue with the CSV database.</p>
<h3>Dream Team under 23</h3>
<p>Based on reader suggestions, we had a look at the optimal squad under €150M budget. Our objective is to maximize the potential rating and create a full team. I chose 4-4-2 formation for illustration purposes. The optimal squad cost €148.3M and the potential rating is 982:</p>
<p><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1736" src="https://blogs.sas.com/content/operations/files/2019/06/Dream-Team-under-23.png" alt="" width="440" height="596" /></p>
<table class="tg" style="border-collapse: collapse;border-spacing: 0;border-color: #ccc;margin: 0px auto">
<tbody>
<tr>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: left;border: 0px solid #ccc">Pos</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: left;border: 0px solid #ccc">Player</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;border: 0px solid #ccc">Rating</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;vertical-align: top;border: 0px solid #ccc">Potential</th>
<th style="font-family: Arial, sans-serif;font-size: 14px;font-weight: bold;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f0f0f0;text-align: right;vertical-align: top;border: 0px solid #ccc">Paid</th>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;border: 0px solid #ccc">GK</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;border: 0px solid #ccc">Gianluigi Donnarumma</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;border: 0px solid #ccc">83</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">94</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">33.5M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;border: 0px solid #ccc">LB</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;border: 0px solid #ccc">Thilo Kehrer</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;border: 0px solid #ccc">79</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">87</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">16.0M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">LCB</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">William Saliba</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">71</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">88</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">4.2M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">RCB</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Boubacar Kamara</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">75</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">88</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">10.5M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">RB</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Trent Alexander-Arnold</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">80</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">89</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">19.0M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">LCM</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Rodrigo Bentancur</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">78</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">90</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">18.5M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">CM</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Ricard Puig Martí</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">69</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">89</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">2.1M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">RCM</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Sandro Tonali</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">73</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">90</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">7.5M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">CAM</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Phil Foden</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">75</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">90</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">13.5M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">LS</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: left;vertical-align: top;border: 0px solid #ccc">Christian Kouamé</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">75</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">89</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">15.0M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">RS</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: left;vertical-align: top;border: 0px solid #ccc">Ezequiel Barco</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">73</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">88</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #f9f9f9;text-align: right;vertical-align: top;border: 0px solid #ccc">8.5M</td>
</tr>
<tr>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;font-weight: bold;text-align: left;vertical-align: top;border: 0px solid #ccc" colspan="2">Total</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">831</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">982</td>
<td style="font-family: Arial, sans-serif;font-size: 14px;padding: 10px 5px;overflow: hidden;border-top-width: 1px;border-bottom-width: 1px;color: #333;background-color: #fff;text-align: right;vertical-align: top;border: 0px solid #ccc">148.3M</td>
</tr>
</tbody>
</table>
<hr />
<p>I hope you enjoyed this brief analysis of potential transfers for top Premier League teams using Python and SAS Viya. I would be happy to answer if you have any questions, especially about how the model is constructed and how it is easily implemented in Python using sasoptpy. As usual, all the code for the problem is available at <a href="https://github.com/sertalpbilal/soccer-transfer-problem" target="_blank" rel="noopener noreferrer">GitHub</a>.</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2019/06/18/bringing-analytics-to-soccer-transfer-season/">Bringing Analytics to the Soccer Transfer Season</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></content:encoded>
					
		
		
			<enclosure url="https://blogs.sas.com/content/operations/files/2019/06/soccer_image1-150x150.png"/>
	</item>
		<item>
		<title>"Are You The One?" Finding the matching through SAS Constraint Programming</title>
		<link>https://blogs.sas.com/content/operations/2018/08/14/are-you-the-one/</link>
					<comments>https://blogs.sas.com/content/operations/2018/08/14/are-you-the-one/#comments</comments>
		
		<dc:creator><![CDATA[Natalia Summerville]]></dc:creator>
		<pubDate>Tue, 14 Aug 2018 10:00:19 +0000</pubDate>
				<category><![CDATA[constraint programming]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[Uncategorized]]></category>
		<category><![CDATA[are you the one]]></category>
		<category><![CDATA[operations research]]></category>
		<category><![CDATA[R&D Analytics]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/operations/?p=1478</guid>

					<description><![CDATA[<p>I bet you were not expecting a photo of 22 youngsters in swimsuits in a math blog, were you? The first time I heard about this reality TV show called "Are You The One?", my nerdiness and love for OR yelled at me: "There is so much math in this [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2018/08/14/are-you-the-one/">&quot;Are You The One?&quot; Finding the matching through SAS Constraint Programming</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<figure id="attachment_1554" aria-describedby="caption-attachment-1554" style="width: 300px" class="wp-caption alignright"><a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO-Season-6-Cast-Photo-Resized.jpg"><img loading="lazy" decoding="async" class="size-medium wp-image-1554" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO-Season-6-Cast-Photo-Resized-300x200.jpg" alt="" width="300" height="200" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO-Season-6-Cast-Photo-Resized-300x200.jpg 300w, https://blogs.sas.com/content/operations/files/2018/08/AYTO-Season-6-Cast-Photo-Resized-1024x681.jpg 1024w" sizes="(max-width: 300px) 100vw, 300px" /></a><figcaption id="caption-attachment-1554" class="wp-caption-text">Are You The One? Season 7 CR: Brian Bielmann/MTV</figcaption></figure>
<p>I bet you were not expecting a photo of 22 youngsters in swimsuits in a math blog, were you? The first time I heard about this reality TV show called "<a href="http://www.mtv.com/shows/are-you-the-one">Are You The One?</a>", my nerdiness and love for OR yelled at me: "There is so much math in this show!!!"</p>
<p>I'm sure producers did not mean it that way, but I saw mathematical optimization in it and just HAD to code it. Season 7 is premiering August 15th, and if you are as nerdy as I am, you should use my code to figure out the solution way before the all the participants do.</p>
<h2><strong>The show</strong></h2>
<p>According to Wikipedia, <span style="font-size: 14px">"Are You The One?" is a reality TV show produced by MTV, in which a group of men and women are secretly paired into male-female couples via a matchmaking algorithm. Then, while living together, the contestants try to identify all of these "perfect matches." If they succeed, the entire group shares a prize of up to $1 million.</span></p>
<p>In each episode, the participants get two new pieces of information about the coupling:</p>
<ol>
<li>They get to choose one couple to go to the "truth booth" and ask if that couple is a perfect match or not.</li>
<li>They all pair up in a "matching ceremony" and get to know how many couples they got right (not which ones though).</li>
</ol>
<p>This way, after each episode they have an increasing amount of information to find their matches. If they don't find it by episode 10, they lose.</p>
<h2><strong>The math</strong></h2>
<p>This problem can be solved through constraint programming because we're attempting to find all possible solutions (couples matches) that satisfy certain constraints (such as "Pete and Lola are NOT a couple" or "out of these given 10 couples only 2 are correct"). The more information you get as the weeks go by, the more constrained your problem is and the fewer feasible solutions you have - until you are left with only one (hopefully at most by week 10).</p>
<p>With SAS/OR Constraint Programming (<a href="http://go.documentation.sas.com/?docsetId=ormpug&amp;docsetTarget=ormpug_optmodel_toc.htm&amp;docsetVersion=14.3&amp;locale=en">PROC OPTMODEL</a> or <a href="http://go.documentation.sas.com/?docsetId=orcpug&amp;docsetTarget=orcpug_clp_toc.htm&amp;docsetVersion=14.3&amp;locale=en">PROC CLP</a>), we are able to discover this perfect matching much earlier than the participants.</p>
<p>The formulation shown in the next section uses a set of binary decision variables called Assign[b,g], with the interpretation that Assign[b,g] = 1 if boy b is assigned to girl g in the matching.  The first two sets of constraints enforce a one-to-one matching between boys and girls.  The Ceremony_Matches constraint makes sure that each solution respects the matching ceremony results.  The Truth_Booth constraint forces Assign[b,g] = 1 or 0 (depending on the truth booth outcome) for the couple (b,g) that entered the truth booth.</p>
<h2><strong>The code</strong></h2>
<p>Here is the SAS code I used to solve this problem. To run it for the next season, just copy all the code and update the "STEP 1" portion each week.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/***********************************************/</span>
<span style="color: #006400; font-style: italic;">/******* STEP 1 - UPDATE YOUR INPUTS ***********/</span>
<span style="color: #006400; font-style: italic;">/***********************************************/</span>
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input number of weeks of data */</span>
<span style="color: #0000ff;">%let</span> num_weeks = <span style="color: #2e8b57; font-weight: bold;">5</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input weekly matches (from matching ceremony) up to current week */</span>
<span style="color: #006400; font-style: italic;">/* For example, in Season 6, as of week 5, these were the couples in each ceremony */</span>
&nbsp;
<span style="color: #000080; font-weight: bold;">data</span> input_ceremony_pairs<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">keep</span>=week boys_name girls_name<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">array</span> girl_week<span style="color: #66cc66;">&#123;</span><span style="color: #0000ff; font-weight: bold;">&amp;num_weeks</span>.<span style="color: #66cc66;">&#125;</span> $;
   <span style="color: #0000ff;">input</span> boys_name $ girl_week<span style="color: #66cc66;">&#123;</span><span style="color: #006400; font-style: italic;">*};</span>
   <span style="color: #0000ff;">do</span> week = <span style="color: #2e8b57; font-weight: bold;">1</span> to <span style="color: #0000ff; font-weight: bold;">&amp;num_weeks</span>.;
      girls_name = girl_week<span style="color: #66cc66;">&#91;</span>week<span style="color: #66cc66;">&#93;</span>;
      <span style="color: #0000ff;">output</span>;
   <span style="color: #0000ff;">end</span>;
   datalines;
Anthony  Geles    Diandra  Jada     Keyana   Nicole
Clinton  Uche     Uche     Uche     Uche     Jada
Dimitri  Diandra  Nicole   Nurys    Alexis   Uche
Ethan    Jada     Jada     Alexis   Nicole   Geles
Joe      Zoe      Audrey   Zoe      Zoe      Zoe
Kareem   Alivia   Alivia   Alivia   Diandra  Alivia
Keith    Alexis   Alexis   Diandra  Nurys    Alexis
Malcolm  Nurys    Nurys    Geles    Alivia   Diandra
Michael  Keyana   Keyana   Audrey   Geles    Nurys
Shad     Audrey   Geles    Keyana   Audrey   Audrey
Tyler    Nicole   Zoe      Nicole   Jada     Keyana
;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input number of correct matches (from matching ceremony) up to current week */</span>
<span style="color: #006400; font-style: italic;">/* For example, in Season 6, as of week 5, these were the correct guesses */</span>
&nbsp;
<span style="color: #000080; font-weight: bold;">data</span> input_ceremony_correct_matches;
   <span style="color: #0000ff;">input</span> week correct_guesses;
   datalines;
<span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #2e8b57; font-weight: bold;">3</span>
<span style="color: #2e8b57; font-weight: bold;">2</span> <span style="color: #2e8b57; font-weight: bold;">1</span>
<span style="color: #2e8b57; font-weight: bold;">3</span> <span style="color: #2e8b57; font-weight: bold;">2</span>
<span style="color: #2e8b57; font-weight: bold;">4</span> <span style="color: #2e8b57; font-weight: bold;">3</span>
<span style="color: #2e8b57; font-weight: bold;">5</span> <span style="color: #2e8b57; font-weight: bold;">1</span>
;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input couples that went to truth booth and their outcome (1 if they are a match, 0 otherwise) up to current week */</span>
<span style="color: #006400; font-style: italic;">/* For example, in Season 6, as of week 5, these were the truth booths */</span>
&nbsp;
<span style="color: #000080; font-weight: bold;">data</span> input_truth_booth;
   <span style="color: #0000ff;">input</span> week boys_name $ girls_name $ outcome;
   datalines;
<span style="color: #2e8b57; font-weight: bold;">1</span>  Ethan    Keyana   <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">2</span>  Anthony  Geles    <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">3</span>  Malcolm  Nurys    <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">4</span>  Dimitri  Nicole   <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">5</span>  Clinton  Uche     <span style="color: #2e8b57; font-weight: bold;">0</span>
;
&nbsp;
<span style="color: #006400; font-style: italic;">/***********************************************/</span>
<span style="color: #006400; font-style: italic;">/****** STEP 2 - RUN WITHOUT MODIFYING CODE ****/</span>
<span style="color: #006400; font-style: italic;">/***********************************************/</span>
&nbsp;
<span style="color: #006400; font-style: italic;">/* Constraint Programming Formulation */</span>
&nbsp;
<span style="color: #000080; font-weight: bold;">proc optmodel</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* SETS */</span>
&nbsp;
   <span style="color: #0000ff;">set</span> &lt;num,str,str&gt; CEREMONY_PAIRS;
   <span style="color: #0000ff;">set</span> BOYS = setof <span style="color: #66cc66;">&#123;</span>&lt;w,b,g&gt; <span style="color: #0000ff;">in</span> CEREMONY_PAIRS<span style="color: #66cc66;">&#125;</span> b;
   <span style="color: #0000ff;">set</span> GIRLS = setof <span style="color: #66cc66;">&#123;</span>&lt;w,b,g&gt; <span style="color: #0000ff;">in</span> CEREMONY_PAIRS<span style="color: #66cc66;">&#125;</span> g;
   <span style="color: #0000ff;">set</span> WEEKS = <span style="color: #2e8b57; font-weight: bold;">1</span>..<span style="color: #0000ff; font-weight: bold;">&amp;num_weeks</span>.;
&nbsp;
   <span style="color: #0000ff;">set</span> &lt;num,str,str&gt; TRUTH_BOOTH_PAIRS;
&nbsp;
   <span style="color: #0000ff;">set</span> SOLS init <span style="color: #2e8b57; font-weight: bold;">1</span>.._NSOL_;
&nbsp;
   num correctMatches<span style="color: #66cc66;">&#123;</span>WEEKS<span style="color: #66cc66;">&#125;</span>;
   num truthBoothOutcome<span style="color: #66cc66;">&#123;</span>TRUTH_BOOTH_PAIRS<span style="color: #66cc66;">&#125;</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* READ DATA */</span>
&nbsp;
   read <span style="color: #000080; font-weight: bold;">data</span> input_ceremony_pairs
      <span style="color: #0000ff;">into</span> CEREMONY_PAIRS=<span style="color: #66cc66;">&#91;</span>week boys_name girls_name<span style="color: #66cc66;">&#93;</span>;
&nbsp;
   read <span style="color: #000080; font-weight: bold;">data</span> input_truth_booth
      <span style="color: #0000ff;">into</span> TRUTH_BOOTH_PAIRS=<span style="color: #66cc66;">&#91;</span>week boys_name girls_name<span style="color: #66cc66;">&#93;</span> truthBoothOutcome=outcome;
&nbsp;
   read <span style="color: #000080; font-weight: bold;">data</span> input_ceremony_correct_matches
      <span style="color: #0000ff;">into</span> <span style="color: #66cc66;">&#91;</span>week<span style="color: #66cc66;">&#93;</span> correctMatches=correct_guesses;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* DECISION VAR */</span>
   <span style="color: #006400; font-style: italic;">/* Assign[b,g] = 1 if boy b is assigned to girl g in the matching; 0 otherwise */</span>
   <span style="color: #0000ff;">var</span> Assign<span style="color: #66cc66;">&#123;</span>BOYS,GIRLS<span style="color: #66cc66;">&#125;</span> BINARY;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* CONSTRAINTS */</span>
   con One_Girl_Per_Boy<span style="color: #66cc66;">&#123;</span>b <span style="color: #0000ff;">in</span> BOYS<span style="color: #66cc66;">&#125;</span>: <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GIRLS<span style="color: #66cc66;">&#125;</span> Assign<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span>;
   con One_Boy_Per_Girl<span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GIRLS<span style="color: #66cc66;">&#125;</span>: <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>b <span style="color: #0000ff;">in</span> BOYS<span style="color: #66cc66;">&#125;</span> Assign<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* WEEKLY CONSTRAINTS */</span>
   con Ceremony_Matches<span style="color: #66cc66;">&#123;</span>w <span style="color: #0000ff;">in</span> WEEKS<span style="color: #66cc66;">&#125;</span>:
      <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>&lt;<span style="color: #66cc66;">&#40;</span>w<span style="color: #66cc66;">&#41;</span>,b,g&gt; <span style="color: #0000ff;">in</span> CEREMONY_PAIRS<span style="color: #66cc66;">&#125;</span> Assign<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> = correctMatches<span style="color: #66cc66;">&#91;</span>w<span style="color: #66cc66;">&#93;</span>;
   con Truth_Booth<span style="color: #66cc66;">&#123;</span>&lt;w,b,g&gt; <span style="color: #0000ff;">in</span> TRUTH_BOOTH_PAIRS<span style="color: #66cc66;">&#125;</span>:
      Assign<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> = truthBoothOutcome<span style="color: #66cc66;">&#91;</span>w,b,g<span style="color: #66cc66;">&#93;</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* SOLVE */</span>
   solve with clp / FINDALLSOLNS;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* CREATE OUTPUT */</span>
   num couplesCount<span style="color: #66cc66;">&#123;</span>BOYS, GIRLS<span style="color: #66cc66;">&#125;</span> init <span style="color: #2e8b57; font-weight: bold;">0</span>;
   for<span style="color: #66cc66;">&#123;</span>s <span style="color: #0000ff;">in</span> SOLS<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
      for<span style="color: #66cc66;">&#123;</span>b <span style="color: #0000ff;">in</span> BOYS<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
         for<span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GIRLS: Assign<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span>.sol<span style="color: #66cc66;">&#91;</span>s<span style="color: #66cc66;">&#93;</span> &gt; <span style="color: #2e8b57; font-weight: bold;">0.5</span><span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
            couplesCount<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> = couplesCount<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> + <span style="color: #2e8b57; font-weight: bold;">1</span>;
            <span style="color: #0000ff;">leave</span>;
         <span style="color: #0000ff;">end</span>;
      <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">create</span> <span style="color: #000080; font-weight: bold;">data</span> output_couples 
      <span style="color: #0000ff;">from</span> <span style="color: #66cc66;">&#91;</span>BOYS GIRLS<span style="color: #66cc66;">&#93;</span> couplesCount;
&nbsp;
   <span style="color: #0000ff;">call</span> symputx<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'Number_Solutions'</span>,_NSOL_<span style="color: #66cc66;">&#41;</span>;
&nbsp;
<span style="color: #000080; font-weight: bold;">quit</span>;</pre></td></tr></table></div>

<h2></h2>
<h2><strong>The solution</strong></h2>
<p>To visualize the solution we can use the <a href="http://go.documentation.sas.com/?docsetId=grstatproc&amp;docsetTarget=n0w12m4cn1j5c6n12ak64u1rys4w.htm&amp;docsetVersion=9.4&amp;locale=en">SAS SGPLOT</a> procedure to create a heat map that illustrates the number of feasible solutions that contain a certain couple matched.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc sort</span> <span style="color: #000080; font-weight: bold;">data</span>=output_couples;
   <span style="color: #0000ff;">by</span> boys girls;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=output_couples noautolegend;
   <span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Couples Occurrence Count in &amp;Number_Solutions. Solutions&quot;</span>;
   heatmap y=boys <span style="color: #0000ff;">x</span>=girls / weight=couplesCount 
      colormodel=<span style="color: #66cc66;">&#40;</span>white pink red<span style="color: #66cc66;">&#41;</span> outline x2axis;
   text y=Boys <span style="color: #0000ff;">x</span>=Girls text=couplesCount / textattrs=<span style="color: #66cc66;">&#40;</span>size=10pt<span style="color: #66cc66;">&#41;</span>;
   xaxis <span style="color: #0000ff;">display</span>=none;
   x2axis <span style="color: #0000ff;">display</span>=<span style="color: #66cc66;">&#40;</span>nolabel<span style="color: #66cc66;">&#41;</span>;
   yaxis <span style="color: #0000ff;">display</span>=<span style="color: #66cc66;">&#40;</span>nolabel<span style="color: #66cc66;">&#41;</span> <span style="color: #0000ff;">reverse</span>;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>

<p>&nbsp;</p>
<p>For example, if we run the code above with the given data (Season 6, Week 5) we can see this diagram:</p>
<p><a href="https://blogs.sas.com/content/operations/files/2018/07/SGPlot34.png"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1542" src="https://blogs.sas.com/content/operations/files/2018/07/SGPlot34.png" alt="" width="640" height="480" srcset="https://blogs.sas.com/content/operations/files/2018/07/SGPlot34.png 640w, https://blogs.sas.com/content/operations/files/2018/07/SGPlot34-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<p>In this output we can see, for example, that Ethan and Geles are for sure not a couple (bummer) and that Tyler and Nicole seem to be the most promising couple (so far).</p>
<h2><strong>Your turn</strong></h2>
<p>Now use the following data (Season 6, Week 8) and my optimization and heat map code. Be ready for a surprise!</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/***********************************************/</span>
<span style="color: #006400; font-style: italic;">/******* STEP 1 - UPDATE YOUR INPUTS ***********/</span>
<span style="color: #006400; font-style: italic;">/***********************************************/</span>
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input number of weeks of data */</span>
<span style="color: #0000ff;">%let</span> num_weeks = <span style="color: #2e8b57; font-weight: bold;">8</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input weekly matches (from matching ceremony) up to current week */</span>
<span style="color: #006400; font-style: italic;">/* For example, in Season 6, as of week 8, these were the couples in each ceremony */</span>
&nbsp;
<span style="color: #000080; font-weight: bold;">data</span> input_ceremony_pairs<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">keep</span>=week boys_name girls_name<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">array</span> girl_week<span style="color: #66cc66;">&#123;</span><span style="color: #0000ff; font-weight: bold;">&amp;num_weeks</span>.<span style="color: #66cc66;">&#125;</span> $;
   <span style="color: #0000ff;">input</span> boys_name $ girl_week<span style="color: #66cc66;">&#123;</span><span style="color: #006400; font-style: italic;">*};</span>
   <span style="color: #0000ff;">do</span> week = <span style="color: #2e8b57; font-weight: bold;">1</span> to <span style="color: #0000ff; font-weight: bold;">&amp;num_weeks</span>.;
      girls_name = girl_week<span style="color: #66cc66;">&#91;</span>week<span style="color: #66cc66;">&#93;</span>;
      <span style="color: #0000ff;">output</span>;
   <span style="color: #0000ff;">end</span>;
   datalines;
Anthony  Geles    Diandra  Jada     Keyana   Nicole   Keyana   Keyana   Alivia
Clinton  Uche     Uche     Uche     Uche     Jada     Geles    Geles    Geles
Dimitri  Diandra  Nicole   Nurys    Alexis   Uche     Diandra  Diandra  Diandra
Ethan    Jada     Jada     Alexis   Nicole   Geles    Jada     Zoe      Alexis
Joe      Zoe      Audrey   Zoe      Zoe      Zoe      Alexis   Uche     Jada
Kareem   Alivia   Alivia   Alivia   Diandra  Alivia   Nurys    Nurys    Nurys
Keith    Alexis   Alexis   Diandra  Nurys    Alexis   Zoe      Jada     Audrey
Malcolm  Nurys    Nurys    Geles    Alivia   Diandra  Alivia   Alexis   Uche
Michael  Keyana   Keyana   Audrey   Geles    Nurys    Uche     Audrey   Keyana
Shad     Audrey   Geles    Keyana   Audrey   Audrey   Audrey   Alivia   Zoe
Tyler    Nicole   Zoe      Nicole   Jada     Keyana   Nicole   Nicole   Nicole
;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input number of correct matches (from matching ceremony) up to current week */</span>
<span style="color: #006400; font-style: italic;">/* For example, in Season 6, as of week 8, these were the correct guesses */</span>
&nbsp;
<span style="color: #000080; font-weight: bold;">data</span> input_ceremony_correct_matches;
   <span style="color: #0000ff;">input</span> week correct_guesses;
   datalines;
<span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #2e8b57; font-weight: bold;">3</span>
<span style="color: #2e8b57; font-weight: bold;">2</span> <span style="color: #2e8b57; font-weight: bold;">1</span>
<span style="color: #2e8b57; font-weight: bold;">3</span> <span style="color: #2e8b57; font-weight: bold;">2</span>
<span style="color: #2e8b57; font-weight: bold;">4</span> <span style="color: #2e8b57; font-weight: bold;">3</span>
<span style="color: #2e8b57; font-weight: bold;">5</span> <span style="color: #2e8b57; font-weight: bold;">1</span>
<span style="color: #2e8b57; font-weight: bold;">6</span> <span style="color: #2e8b57; font-weight: bold;">4</span>
<span style="color: #2e8b57; font-weight: bold;">7</span> <span style="color: #2e8b57; font-weight: bold;">5</span>
<span style="color: #2e8b57; font-weight: bold;">8</span> <span style="color: #2e8b57; font-weight: bold;">3</span>
;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input couples that went to truth booth and their outcome (1 if they are a match, 0 otherwise) up to current week */</span>
<span style="color: #006400; font-style: italic;">/* For example, in Season 6, as of week 8, these were the truth booths */</span>
&nbsp;
<span style="color: #000080; font-weight: bold;">data</span> input_truth_booth;
   <span style="color: #0000ff;">input</span> week boys_name $ girls_name $ outcome;
   datalines;
<span style="color: #2e8b57; font-weight: bold;">1</span>  Ethan    Keyana   <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">2</span>  Anthony  Geles    <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">3</span>  Malcolm  Nurys    <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">4</span>  Dimitri  Nicole   <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">5</span>  Clinton  Uche     <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">6</span>  Keith    Alexis   <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">7</span>  Keith    Alivia   <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">8</span>  Michael  Audrey   <span style="color: #2e8b57; font-weight: bold;">0</span>
;</pre></td></tr></table></div>

<p>&nbsp;</p>
<p>FYI - The participants in Season 6 did not find the perfect matching till week 10. I bet they would have loved to have SAS Constraint Programming handy.</p>
<h2><strong>Full season summary</strong></h2>
<p>To understand better how the number of possible matches (feasible solutions) decreases with each additional data piece (constraints), you can see the following super cool animation (thanks, <a href="https://blogs.sas.com/content/author/robpratt/">Rob Pratt</a>!):<br />
<a href="https://blogs.sas.com/content/operations/files/2018/07/AreYouTheOne.gif"><img loading="lazy" decoding="async" class="aligncenter size-full wp-image-1550" src="https://blogs.sas.com/content/operations/files/2018/07/AreYouTheOne.gif" alt="" width="800" height="600" /></a><br />
Notice that the truth booths in weeks 4 and 6 were wasted because the status of the selected couple was already able to be deduced, so the number of possible solutions did not change. In a later update, I'll show how to optimally select which couple should go to the truth booth.</p>
<h2><strong>Going forward</strong></h2>
<p>So, now that you know how to use SAS Constraint Programming, let's help these poor guys find their perfect matches in Season 7. Follow this blog and see how much faster than the contestants we'll figure out the matching!</p>
<h2><strong>Season 7 Week 1 update</strong></h2>
<p>And so it begins. After the first "truth booth" and "matching ceremony" (and lots of drama), here is the first heat map.<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e1.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e1.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1560" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e1.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e1-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a><br />
Teaser from Rob:<br />
If the contestants have a choice, they should choose either Shamoy-Maria or Tomas-Morgan to go to the next truth booth, but NOT because those couples appear in the most remaining solutions.</p>
<h2><strong>Season 7 Week 2 update</strong></h2>
<p>Unfortunately the contestants did not follow Rob's suggestion. After the second truth booth and matching ceremony, we now have 287,580 solutions and actually Shamoy-Maria is now the most promising couple! Here is the heat map.<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e2.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e2.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1565" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e2.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e2-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a><br />
Update from Rob:<br />
The fate button required the contestants to pair Andrew or Cam with Asia or Nutsa for the truth booth.  Because the four pairs (Andrew-Asia, Andrew-Nutsa, Cam-Asia, Cam-Nutsa) all appeared in the same number of solutions (165,326) after week 1, these four choices were equally desirable to send to the truth booth, with respect to reducing the number of possible solutions.  After week 2, Shamoy-Maria would be the best choice for the truth booth, but again NOT because they have the highest solution count (136,848).  More details next week...</p>
<h2><strong>Season 7 Week 3 update</strong></h2>
<p>The third truth booth revealed that Shamoy and Maria are in fact a perfect match.  After the third matching ceremony, there are 37,681 solutions.<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e3.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e3.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1569" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e3.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e3-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a><br />
Optimal truth booth recommendation from Rob:<br />
Choosing either Cam-Kayla or Tevin-Kenya would yield the smallest number of resulting solutions in the worst case.</p>
<h2><strong>Optimal truth booth strategy</strong></h2>
<p>The "Couples Occurrence Count" table provides a way to determine which couple should go to the truth booth if you want to minimize the worst-case (maximum) resulting number of solutions.  If couple (b,g) goes to the truth booth, there are two possibilities:</p>
<ol>
<li>If that couple is a perfect match, then the resulting number of solutions is couplesCount[b,g].</li>
<li>If that couple is not a perfect match, then the resulting number of solutions is _NSOL_ - couplesCount[b,g].</li>
</ol>
<p>So in the worst (worse?) case, the resulting number of solutions is max(couplesCount[b,g], _NSOL_ - couplesCount[b,g]), that is, the larger of these two counts.  Because we want to reduce the number of solutions, the best strategy is to choose a couple that minimizes this quantity.  Equivalently, we want to choose a couple that comes the closest to appearing in half the solutions.  The following statements implement this idea:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   <span style="color: #006400; font-style: italic;">/* find best pair for truth booth by minimizing the maximum number of resulting solutions */</span>
   num maxSolsTruth <span style="color: #66cc66;">&#123;</span>b <span style="color: #0000ff;">in</span> BOYS, g <span style="color: #0000ff;">in</span> GIRLS<span style="color: #66cc66;">&#125;</span> = <span style="color: #0000ff;">max</span><span style="color: #66cc66;">&#40;</span>couplesCount<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span>, _NSOL_ - couplesCount<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>;
   num min_maxSolsTruth init constant<span style="color: #66cc66;">&#40;</span><span style="color: #a020f0;">'BIG'</span><span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">set</span> &lt;str,str&gt; BEST_PAIR;
   for <span style="color: #66cc66;">&#123;</span>b <span style="color: #0000ff;">in</span> BOYS, g <span style="color: #0000ff;">in</span> GIRLS<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
      <span style="color: #0000ff;">if</span> min_maxSolsTruth &gt; maxSolsTruth<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
         min_maxSolsTruth = maxSolsTruth<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span>;
         BEST_PAIR = <span style="color: #66cc66;">&#123;</span>&lt;b,g&gt;<span style="color: #66cc66;">&#125;</span>;
      <span style="color: #0000ff;">end</span>;
      <span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span> min_maxSolsTruth = maxSolsTruth<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> <span style="color: #0000ff;">then</span> <span style="color: #0000ff;">do</span>;
         BEST_PAIR = BEST_PAIR union <span style="color: #66cc66;">&#123;</span>&lt;b,g&gt;<span style="color: #66cc66;">&#125;</span>;
      <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">put</span> BEST_PAIR= min_maxSolsTruth;
   <span style="color: #0000ff;">create</span> <span style="color: #000080; font-weight: bold;">data</span> output_truth 
      <span style="color: #0000ff;">from</span> <span style="color: #66cc66;">&#91;</span>BOYS GIRLS<span style="color: #66cc66;">&#93;</span> maxSolsTruth;</pre></td></tr></table></div>

<p>After week 3, the PUT statement reports in the log:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">BEST_PAIR=<span style="color: #66cc66;">&#123;</span>&lt;<span style="color: #a020f0;">'Cam'</span>,<span style="color: #a020f0;">'Kayla'</span>&gt;,&lt;<span style="color: #a020f0;">'Tevin'</span>,<span style="color: #a020f0;">'Kenya'</span>&gt;<span style="color: #66cc66;">&#125;</span> <span style="color: #2e8b57; font-weight: bold;">21889</span></pre></td></tr></table></div>

<p>The interpretation is that by choosing either of these two couples to go to the truth booth, the resulting number of solutions will be at most 21,889, and this number is best possible in the sense that choosing any other couple could yield a larger number of solutions.
</pre>
<p>As before, it is useful to display the results in a heat map:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc sort</span> <span style="color: #000080; font-weight: bold;">data</span>=output_truth;
   <span style="color: #0000ff;">by</span> boys girls;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=output_truth noautolegend;
   <span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Maximum Number of Solutions if Couple goes to Truth Booth&quot;</span>;
   heatmap y=boys <span style="color: #0000ff;">x</span>=girls / weight=maxSolsTruth 
      colormodel=<span style="color: #66cc66;">&#40;</span>white pink red<span style="color: #66cc66;">&#41;</span> outline x2axis;
   text y=Boys <span style="color: #0000ff;">x</span>=Girls text=maxSolsTruth / textattrs=<span style="color: #66cc66;">&#40;</span>size=10pt<span style="color: #66cc66;">&#41;</span>;
   xaxis <span style="color: #0000ff;">display</span>=none;
   x2axis <span style="color: #0000ff;">display</span>=<span style="color: #66cc66;">&#40;</span>nolabel<span style="color: #66cc66;">&#41;</span>;
   yaxis <span style="color: #0000ff;">display</span>=<span style="color: #66cc66;">&#40;</span>nolabel<span style="color: #66cc66;">&#41;</span> <span style="color: #0000ff;">reverse</span>;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>

<p><a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e3_truth.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e3_truth.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1575" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e3_truth.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e3_truth-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a><br />
The lowest numbers appear in the lightest cells.</p>
<h2><strong>Season 7 Week 4 update</strong></h2>
<p>Here's the update after last night's episode. We are down to 12,860 solutions.</p>
<p><a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1577" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<p>Using Rob's optimal strategy, the contestants should send Tevin and Kenya to the booth next episode such that in the worst-case scenario, the number of solutions will be down to 7,081.</p>
<h2><strong>Avoiding a blackout</strong></h2>
<p>If the matching ceremony yields no matches (a "blackout") beyond those confirmed by the truth booth, the prize pool gets cuts in half.  So it would be good to figure out a way to avoid a blackout if possible.  After the week 4 truth booth (before the matching ceremony), here was the heat map:<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4_aftertruth.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4_aftertruth.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1581" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4_aftertruth.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4_aftertruth-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<p>To avoid a blackout, you can solve an auxiliary optimization problem that maximizes the minimum number of matches.  The formulation shown in the next code snippet uses a set of binary decision variables called IsCeremonyPair[b,g], with the interpretation that IsCeremonyPair[b,g] = 1 if boy b is paired with girl g in the matching ceremony.  The first two sets of constraints enforce a one-to-one matching between boys and girls.  The PerfectMatch constraint makes sure that any couple that is confirmed a "perfect match" by the truth booth is also paired together in the matching ceremony.  The MaxMinCon constraint linearizes the <a href="https://en.wikipedia.org/wiki/Wald%27s_maximin_model">maximin</a> objective, as in the <a href="https://blogs.sas.com/content/operations/2017/07/03/fun-with-flags-optimizing-arrangements-of-stars-with-sas/">Fun with Flags post</a> from July 2017.  The following statements build and solve this auxiliary problem:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   <span style="color: #006400; font-style: italic;">/* MATCHES_sol[s] = the set of pairs &lt;b,g&gt; for which Assign[b,g] = 1 in solution s */</span>
   <span style="color: #0000ff;">set</span> &lt;str,str&gt; MATCHES_sol <span style="color: #66cc66;">&#123;</span>SOLS<span style="color: #66cc66;">&#125;</span> init <span style="color: #66cc66;">&#123;</span><span style="color: #66cc66;">&#125;</span>;
   for<span style="color: #66cc66;">&#123;</span>s <span style="color: #0000ff;">in</span> SOLS<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
      for<span style="color: #66cc66;">&#123;</span>b <span style="color: #0000ff;">in</span> BOYS<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
         for<span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GIRLS: Assign<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span>.sol<span style="color: #66cc66;">&#91;</span>s<span style="color: #66cc66;">&#93;</span> &gt; <span style="color: #2e8b57; font-weight: bold;">0.5</span><span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">do</span>;
            MATCHES_sol<span style="color: #66cc66;">&#91;</span>s<span style="color: #66cc66;">&#93;</span> = MATCHES_sol<span style="color: #66cc66;">&#91;</span>s<span style="color: #66cc66;">&#93;</span> union <span style="color: #66cc66;">&#123;</span>&lt;b,g&gt;<span style="color: #66cc66;">&#125;</span>;
            <span style="color: #0000ff;">leave</span>;
         <span style="color: #0000ff;">end</span>;
      <span style="color: #0000ff;">end</span>;
   <span style="color: #0000ff;">end</span>;
&nbsp;
   <span style="color: #006400; font-style: italic;">/* find best ceremony pairs by maximizing the minimum number of matches (to avoid blackout) */</span>
   <span style="color: #0000ff;">var</span> IsCeremonyPair<span style="color: #66cc66;">&#123;</span>b <span style="color: #0000ff;">in</span> BOYS, g <span style="color: #0000ff;">in</span> GIRLS<span style="color: #66cc66;">&#125;</span> binary;
   con One_Girl_Per_Boy_Ceremony<span style="color: #66cc66;">&#123;</span>b <span style="color: #0000ff;">in</span> BOYS<span style="color: #66cc66;">&#125;</span>: <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GIRLS<span style="color: #66cc66;">&#125;</span> IsCeremonyPair<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span>;
   con One_Boy_Per_Girl_Ceremony<span style="color: #66cc66;">&#123;</span>g <span style="color: #0000ff;">in</span> GIRLS<span style="color: #66cc66;">&#125;</span>: <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>b <span style="color: #0000ff;">in</span> BOYS<span style="color: #66cc66;">&#125;</span> IsCeremonyPair<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span>;
   con PerfectMatch<span style="color: #66cc66;">&#123;</span>&lt;w,b,g&gt; <span style="color: #0000ff;">in</span> TRUTH_BOOTH_PAIRS: truthBoothOutcome<span style="color: #66cc66;">&#91;</span>w,b,g<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#125;</span>:
      IsCeremonyPair<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span>;
   <span style="color: #0000ff;">var</span> MaxMin &gt;= <span style="color: #2e8b57; font-weight: bold;">0</span> integer;
   <span style="color: #0000ff;">max</span> MinNumMatches = MaxMin;
   con MaxMinCon<span style="color: #66cc66;">&#123;</span>s <span style="color: #0000ff;">in</span> SOLS<span style="color: #66cc66;">&#125;</span>:
      MaxMin &lt;= <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>&lt;b,g&gt; <span style="color: #0000ff;">in</span> MATCHES_sol<span style="color: #66cc66;">&#91;</span>s<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> IsCeremonyPair<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span>;
   problem CeremonyProblem include
      IsCeremonyPair MaxMin
      MinNumMatches
      One_Girl_Per_Boy_Ceremony One_Boy_Per_Girl_Ceremony PerfectMatch MaxMinCon;
   use problem CeremonyProblem;
   solve;
   <span style="color: #0000ff;">put</span> <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">min</span><span style="color: #66cc66;">&#123;</span>s <span style="color: #0000ff;">in</span> SOLS<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>&lt;b,g&gt; <span style="color: #0000ff;">in</span> MATCHES_sol<span style="color: #66cc66;">&#91;</span>s<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> IsCeremonyPair<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#41;</span>=;
   num couplesCountCeremony <span style="color: #66cc66;">&#123;</span>b <span style="color: #0000ff;">in</span> BOYS, g <span style="color: #0000ff;">in</span> GIRLS<span style="color: #66cc66;">&#125;</span> = <span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">if</span> IsCeremonyPair<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span>.sol &gt; <span style="color: #2e8b57; font-weight: bold;">0.5</span> <span style="color: #0000ff;">then</span> couplesCount<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> <span style="color: #0000ff;">else</span> .<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">create</span> <span style="color: #000080; font-weight: bold;">data</span> output_couples 
      <span style="color: #0000ff;">from</span> <span style="color: #66cc66;">&#91;</span>BOYS GIRLS<span style="color: #66cc66;">&#93;</span> couplesCount couplesCountCeremony;</pre></td></tr></table></div>

<p>For week 4, the solver recommends an optimal matching ceremony that guarantees at least three matches and hence avoids a blackout.  You can use the following PROC SGPLOT code to display the resulting solution:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #000080; font-weight: bold;">proc sort</span> <span style="color: #000080; font-weight: bold;">data</span>=output_couples;
   <span style="color: #0000ff;">by</span> boys girls;
<span style="color: #000080; font-weight: bold;">run</span>;
&nbsp;
<span style="color: #000080; font-weight: bold;">proc sgplot</span> <span style="color: #000080; font-weight: bold;">data</span>=output_couples noautolegend;
   <span style="color: #0000ff;">title</span> <span style="color: #a020f0;">&quot;Couples Occurrence Count in &amp;Number_Solutions. Solutions&quot;</span>;
   heatmapparm y=boys <span style="color: #0000ff;">x</span>=girls colorResponse=couplesCountCeremony / 
      colormodel=<span style="color: #66cc66;">&#40;</span>white pink red<span style="color: #66cc66;">&#41;</span> outline x2axis;
   text y=Boys <span style="color: #0000ff;">x</span>=Girls text=couplesCount / textattrs=<span style="color: #66cc66;">&#40;</span>size=10pt<span style="color: #66cc66;">&#41;</span>;
   xaxis <span style="color: #0000ff;">display</span>=none;
   x2axis <span style="color: #0000ff;">display</span>=<span style="color: #66cc66;">&#40;</span>nolabel<span style="color: #66cc66;">&#41;</span>;
   yaxis <span style="color: #0000ff;">display</span>=<span style="color: #66cc66;">&#40;</span>nolabel<span style="color: #66cc66;">&#41;</span> <span style="color: #0000ff;">reverse</span>;
<span style="color: #000080; font-weight: bold;">run</span>;</pre></td></tr></table></div>

<p>Here is the resulting heat map, where the couples who are not in the matching ceremony are grayed out:<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4_ceremony_maxmin.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4_ceremony_maxmin.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1582" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4_ceremony_maxmin.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e4_ceremony_maxmin-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a><br />
It turns out that the actual matching ceremony chosen by the contestants in week 4 could have yielded a blackout.  That is, there was at least one remaining solution that contained no matches from the ceremony.</p>
<h2><strong>Season 7 Week 5 update</strong></h2>
<p>Now we're down to 1,696 solutions:<br />
<a href="https://blogs.sas.com/content/operations/files/2018/09/AYTO_s7_e5.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/09/AYTO_s7_e5.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1589" srcset="https://blogs.sas.com/content/operations/files/2018/09/AYTO_s7_e5.png 640w, https://blogs.sas.com/content/operations/files/2018/09/AYTO_s7_e5-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<p>Optimal truth booth recommendation from Rob:<br />
Cam-Kayla would yield at most <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\max(884, 1696-884) = 884" /></span><script type='math/tex'>\max(884, 1696-884) = 884</script> solutions, and they are the best choice.  Note that Tevin-Kenya appear in more solutions together but have a higher worst-case count of <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\max(1005, 1696-1005) = 1005" /></span><script type='math/tex'>\max(1005, 1696-1005) = 1005</script> solutions.</p>
<h2><strong>Season 7 Week 6 update</strong></h2>
<p>We're getting there: 574 possible solutions left. </p>
<p><a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e6.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e6.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1594" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e6.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e6-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<p>Optimal truth booth recommendation from Rob:<br />
Cam-Kayla would yield at most <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\max(285, 574-285) = 289" /></span><script type='math/tex'>\max(285, 574-285) = 289</script> solutions, and they are still the best choice.  Also, we can now conclude that Brett-Kayla are not a match, even though they never went to the truth booth.</p>
<h2><strong>Season 7 Week 7 update</strong></h2>
<p>The contestants voted to send Zak and Nutsa to the truth booth, and they were not a match, so <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="574-36=538" /></span><script type='math/tex'>574-36=538</script> possible solutions remain.</p>
<p><a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7_after_truth.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7_after_truth.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1597" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7_after_truth.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7_after_truth-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<h2><strong>Optimal matching ceremony strategy</strong></h2>
<p>We already saw one way to avoid a blackout by solving an auxiliary optimization problem to maximize the minimum number of matches.  But there might be many such possible matching ceremonies that yield the same number of matches, and (as with the truth booth strategy) we would like to minimize the number of resulting solutions in the worst case.  We can solve a different auxiliary problem to both avoid a blackout and minimize the number of solutions.  This problem involves the IsCeremonyPair[b,g] variables and related constraints from before, as well as additional binary variables IsBeamCount[s,c] with the interpretation that IsBeamCount[s,c] = 1 if and only if the beam count for solution s is c.  Because Shamoy and Maria are the only perfect match so far, we need at least two beams to avoid a blackout.  The following statements build and solve this auxiliary problem:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">   <span style="color: #006400; font-style: italic;">/* find best ceremony pairs by minimizing the maximum number of resulting solutions */</span>
   <span style="color: #006400; font-style: italic;">/* minBeamCount is minimum number of beams to avoid blackout */</span>
   num minBeamCount = <span style="color: #2e8b57; font-weight: bold;">1</span> + <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>&lt;w,b,g&gt; <span style="color: #0000ff;">in</span> TRUTH_BOOTH_PAIRS<span style="color: #66cc66;">&#125;</span> truthBoothOutcome<span style="color: #66cc66;">&#91;</span>w,b,g<span style="color: #66cc66;">&#93;</span>;
   <span style="color: #0000ff;">set</span> BEAM_COUNTS = minBeamCount..card<span style="color: #66cc66;">&#40;</span>BOYS<span style="color: #66cc66;">&#41;</span> diff <span style="color: #66cc66;">&#123;</span>card<span style="color: #66cc66;">&#40;</span>BOYS<span style="color: #66cc66;">&#41;</span> - <span style="color: #2e8b57; font-weight: bold;">1</span><span style="color: #66cc66;">&#125;</span>;
   <span style="color: #006400; font-style: italic;">/* IsBeamCount[s,c] = 1 iff sum{&lt;b,g&gt; in MATCHES_sol[s]} IsCeremonyPair[b,g] = c */</span>
   <span style="color: #0000ff;">var</span> IsBeamCount<span style="color: #66cc66;">&#123;</span>SOLS, BEAM_COUNTS<span style="color: #66cc66;">&#125;</span> binary;
   impvar NumSolsPerBeamCount<span style="color: #66cc66;">&#123;</span>c <span style="color: #0000ff;">in</span> BEAM_COUNTS<span style="color: #66cc66;">&#125;</span> = <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>s <span style="color: #0000ff;">in</span> SOLS<span style="color: #66cc66;">&#125;</span> IsBeamCount<span style="color: #66cc66;">&#91;</span>s,c<span style="color: #66cc66;">&#93;</span>;
   <span style="color: #0000ff;">var</span> MinMax &gt;= <span style="color: #2e8b57; font-weight: bold;">0</span> integer;
   <span style="color: #0000ff;">min</span> MaxNumSolutions = MinMax;
   con MinMaxCon<span style="color: #66cc66;">&#123;</span>c <span style="color: #0000ff;">in</span> BEAM_COUNTS<span style="color: #66cc66;">&#125;</span>:
      MinMax &gt;= NumSolsPerBeamCount<span style="color: #66cc66;">&#91;</span>c<span style="color: #66cc66;">&#93;</span>;
   con OneBeamCount<span style="color: #66cc66;">&#123;</span>s <span style="color: #0000ff;">in</span> SOLS<span style="color: #66cc66;">&#125;</span>:
      <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>c <span style="color: #0000ff;">in</span> BEAM_COUNTS<span style="color: #66cc66;">&#125;</span> IsBeamCount<span style="color: #66cc66;">&#91;</span>s,c<span style="color: #66cc66;">&#93;</span> = <span style="color: #2e8b57; font-weight: bold;">1</span>;
   con <span style="color: #0000ff;">Link</span><span style="color: #66cc66;">&#123;</span>s <span style="color: #0000ff;">in</span> SOLS<span style="color: #66cc66;">&#125;</span>:
      <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>&lt;b,g&gt; <span style="color: #0000ff;">in</span> MATCHES_sol<span style="color: #66cc66;">&#91;</span>s<span style="color: #66cc66;">&#93;</span><span style="color: #66cc66;">&#125;</span> IsCeremonyPair<span style="color: #66cc66;">&#91;</span>b,g<span style="color: #66cc66;">&#93;</span> = <span style="color: #0000ff;">sum</span><span style="color: #66cc66;">&#123;</span>c <span style="color: #0000ff;">in</span> BEAM_COUNTS<span style="color: #66cc66;">&#125;</span> c <span style="color: #006400; font-style: italic;">* IsBeamCount[s,c];</span>
   problem CeremonyProblem2 include
      IsCeremonyPair IsBeamCount MinMax
      MaxNumSolutions
      One_Girl_Per_Boy_Ceremony One_Boy_Per_Girl_Ceremony PerfectMatch MinMaxCon
      OneBeamCount <span style="color: #0000ff;">Link</span>;
   use problem CeremonyProblem2;
   solve;
   print NumSolsPerBeamCount;
   <span style="color: #0000ff;">create</span> <span style="color: #000080; font-weight: bold;">data</span> output_couples 
      <span style="color: #0000ff;">from</span> <span style="color: #66cc66;">&#91;</span>BOYS GIRLS<span style="color: #66cc66;">&#93;</span> couplesCount couplesCountCeremony;</pre></td></tr></table></div>

<p>For week 7, the solver recommends an optimal matching ceremony that guarantees at least two matches (and hence avoids a blackout) and yields at most 142 solutions in the worst case (three beams):<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/NumSolsPerBeamCount.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/NumSolsPerBeamCount.png" alt="" width="199" height="280" class="aligncenter size-full wp-image-1599" /></a><br />
Here is the resulting heat map, where the couples who are not in the matching ceremony are grayed out:<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7_best_ceremony.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7_best_ceremony.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1600" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7_best_ceremony.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7_best_ceremony-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a><br />
It turns out that the actual matching ceremony chosen by the contestants in week 7 (the results of which were left as a cliffhanger to be revealed in next week's episode) could yield up to 296 solutions, so the matching ceremony proposed here is better in the sense that it guarantees a much smaller worst-case number of solutions.</p>
<p>Update from Rob: The matching ceremony yielded four beams, and this implies several no-matches, four of which the contestants realized, and an inferred match (Tevin-Kenya), which the contestants did not realize and instead sent them to the truth booth.  Although the truth booth result was treated as a cliffhanger to be revealed in the next episode, we already know the result.  (In fairness to the contestants, there does not seem to be a simple explanation that Tevin and Kenya are a perfect match, without enumeration.  For example, if you fix Assign['Tevin','Kenya'] = 0, the resulting problem is MILP infeasible but LP feasible.)  There are now 128 remaining solutions, and that number will not change when the truth booth confirms the Tevin-Kenya match.<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1608" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e7-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<p>For next week, the solver recommends an optimal matching ceremony that guarantees at least three matches (and hence avoids a blackout) and yields at most 35 solutions in the worst case (five beams):<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/NumSolsPerBeamCount2.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/NumSolsPerBeamCount2.png" alt="" width="193" height="247" class="aligncenter size-full wp-image-1606" /></a><br />
Here is the resulting heat map, where the couples who are not in the matching ceremony are grayed out:<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e8_best_ceremony.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e8_best_ceremony.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1607" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e8_best_ceremony.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e8_best_ceremony-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<h2><strong>Season 7 Week 8 update</strong></h2>
<p>As we already knew would happen, the truth booth confirmed that Tevin and Kenya are a perfect match.  The matching ceremony, which could have yielded a blackout, leaves 34 solutions possible:<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e8.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e8.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1618" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e8.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e8-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<h2><strong>Season 7 Week 9 update</strong></h2>
<p>The contestants sent Cam and Samantha to the truth booth, but the results above already showed they were not a match, so that is two wasted truth booths in a row.  The solver recommends an optimal matching ceremony that guarantees at least three matches (and hence avoids a blackout) and yields at most 8 solutions in the worst case (four or five beams):<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/NumSolsPerBeamCount3.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/NumSolsPerBeamCount3.png" alt="" width="247" height="310" class="aligncenter size-full wp-image-1619" srcset="https://blogs.sas.com/content/operations/files/2018/08/NumSolsPerBeamCount3.png 247w, https://blogs.sas.com/content/operations/files/2018/08/NumSolsPerBeamCount3-239x300.png 239w" sizes="(max-width: 247px) 100vw, 247px" /></a></p>
<p>Here is the resulting heat map, where the couples who are not in the matching ceremony are grayed out:<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e9_best_ceremony.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e9_best_ceremony.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1620" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e9_best_ceremony.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e9_best_ceremony-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<p>The contestants chose a different matching ceremony, which could have yielded a blackout.  They got four beams, and now only five solutions remain:<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e9.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e9.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1634" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e9.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e9-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<p>By adding a PUT statement, you can write these solutions to the log:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;">for <span style="color: #66cc66;">&#123;</span>s <span style="color: #0000ff;">in</span> SOLS<span style="color: #66cc66;">&#125;</span> <span style="color: #0000ff;">put</span> s= MATCHES_SOL<span style="color: #66cc66;">&#91;</span>s<span style="color: #66cc66;">&#93;</span>;</pre></td></tr></table></div>

<p>Here they are:<br />
<pre class="preserve-code-formatting">
s=1 {&lt;&#039;Andrew&#039;,&#039;Cali&#039;&gt;,&lt;&#039;Brett&#039;,&#039;Nutsa&#039;&gt;,&lt;&#039;Cam&#039;,&#039;Bria&#039;&gt;,&lt;&#039;Daniel&#039;,&#039;Jasmine&#039;&gt;,&lt;&#039;Kwasi&#039;,&#039;Lauren&#039;&gt;,&lt;&#039;Lewis&#039;,&#039;Samantha&#039;&gt;,&lt;&#039;Moe&#039;,&#039;Kayla&#039;&gt;,&lt;&#039;Shamoy&#039;,&#039;Maria&#039;&gt;,&lt;&#039;Tevin&#039;,&#039;Kenya&#039;&gt;,&lt;&#039;Tomas&#039;,&#039;Asia&#039;&gt;,&lt;&#039;Zak&#039;,&#039;Morgan&#039;&gt;}
s=2 {&lt;&#039;Andrew&#039;,&#039;Cali&#039;&gt;,&lt;&#039;Brett&#039;,&#039;Samantha&#039;&gt;,&lt;&#039;Cam&#039;,&#039;Lauren&#039;&gt;,&lt;&#039;Daniel&#039;,&#039;Bria&#039;&gt;,&lt;&#039;Kwasi&#039;,&#039;Asia&#039;&gt;,&lt;&#039;Lewis&#039;,&#039;Jasmine&#039;&gt;,&lt;&#039;Moe&#039;,&#039;Kayla&#039;&gt;,&lt;&#039;Shamoy&#039;,&#039;Maria&#039;&gt;,&lt;&#039;Tevin&#039;,&#039;Kenya&#039;&gt;,&lt;&#039;Tomas&#039;,&#039;Nutsa&#039;&gt;,&lt;&#039;Zak&#039;,&#039;Morgan&#039;&gt;}
s=3 {&lt;&#039;Andrew&#039;,&#039;Cali&#039;&gt;,&lt;&#039;Brett&#039;,&#039;Samantha&#039;&gt;,&lt;&#039;Cam&#039;,&#039;Lauren&#039;&gt;,&lt;&#039;Daniel&#039;,&#039;Nutsa&#039;&gt;,&lt;&#039;Kwasi&#039;,&#039;Kayla&#039;&gt;,&lt;&#039;Lewis&#039;,&#039;Bria&#039;&gt;,&lt;&#039;Moe&#039;,&#039;Asia&#039;&gt;,&lt;&#039;Shamoy&#039;,&#039;Maria&#039;&gt;,&lt;&#039;Tevin&#039;,&#039;Kenya&#039;&gt;,&lt;&#039;Tomas&#039;,&#039;Jasmine&#039;&gt;,&lt;&#039;Zak&#039;,&#039;Morgan&#039;&gt;}
s=4 {&lt;&#039;Andrew&#039;,&#039;Samantha&#039;&gt;,&lt;&#039;Brett&#039;,&#039;Bria&#039;&gt;,&lt;&#039;Cam&#039;,&#039;Nutsa&#039;&gt;,&lt;&#039;Daniel&#039;,&#039;Cali&#039;&gt;,&lt;&#039;Kwasi&#039;,&#039;Asia&#039;&gt;,&lt;&#039;Lewis&#039;,&#039;Jasmine&#039;&gt;,&lt;&#039;Moe&#039;,&#039;Kayla&#039;&gt;,&lt;&#039;Shamoy&#039;,&#039;Maria&#039;&gt;,&lt;&#039;Tevin&#039;,&#039;Kenya&#039;&gt;,&lt;&#039;Tomas&#039;,&#039;Lauren&#039;&gt;,&lt;&#039;Zak&#039;,&#039;Morgan&#039;&gt;}
s=5 {&lt;&#039;Andrew&#039;,&#039;Samantha&#039;&gt;,&lt;&#039;Brett&#039;,&#039;Nutsa&#039;&gt;,&lt;&#039;Cam&#039;,&#039;Morgan&#039;&gt;,&lt;&#039;Daniel&#039;,&#039;Lauren&#039;&gt;,&lt;&#039;Kwasi&#039;,&#039;Asia&#039;&gt;,&lt;&#039;Lewis&#039;,&#039;Jasmine&#039;&gt;,&lt;&#039;Moe&#039;,&#039;Cali&#039;&gt;,&lt;&#039;Shamoy&#039;,&#039;Maria&#039;&gt;,&lt;&#039;Tevin&#039;,&#039;Kenya&#039;&gt;,&lt;&#039;Tomas&#039;,&#039;Bria&#039;&gt;,&lt;&#039;Zak&#039;,&#039;Kayla&#039;&gt;}
</pre></p>
<h2><strong>Season 7 Week 10 update</strong></h2>
<p>Brett and Nutsa are headed to the final truth booth.  Out of the four choices allowed by the fate button, they are the only couple whose status is unknown, so that was a good decision.  From the latest heat map, we can see that there will be either two solutions (if Brett-Nutsa is a match) or three solutions (if not) left after the truth booth results.  So it is not possible for the contestants to have logically deduced the correct matching in time for the final ceremony.  It will take some guesswork for them to win the $1 million prize.</p>
<p>The final truth booth revealed that Brett-Nutsa is a perfect match, so we are down to two solutions (s=1 and s=5 above):<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e10_after_truth.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e10_after_truth.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1636" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e10_after_truth.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e10_after_truth-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a></p>
<p>The contestants used solution s=1 for the final matching ceremony, and...<br />
<a href="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e10.png"><img loading="lazy" decoding="async" src="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e10.png" alt="" width="640" height="480" class="aligncenter size-full wp-image-1637" srcset="https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e10.png 640w, https://blogs.sas.com/content/operations/files/2018/08/AYTO_s7_e10-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /></a><br />
...they won the $1 million!  Thanks for following along with us this season.</p>
<h2><strong>Season 7 data</strong></h2>
<p>You can reproduce these results by using the following data.</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="sas" style="font-family:monospace;"><span style="color: #006400; font-style: italic;">/* Input number of weeks of data */</span>
<span style="color: #0000ff;">%let</span> num_weeks = <span style="color: #2e8b57; font-weight: bold;">10</span>;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input weekly matches (from matching ceremony) up to current week */</span>
<span style="color: #000080; font-weight: bold;">data</span> input_ceremony_pairs<span style="color: #66cc66;">&#40;</span><span style="color: #0000ff;">keep</span>=week boys_name girls_name<span style="color: #66cc66;">&#41;</span>;
   <span style="color: #0000ff;">array</span> girl_week<span style="color: #66cc66;">&#123;</span><span style="color: #0000ff; font-weight: bold;">&amp;num_weeks</span>.<span style="color: #66cc66;">&#125;</span> $;
   <span style="color: #0000ff;">input</span> boys_name $ girl_week<span style="color: #66cc66;">&#123;</span><span style="color: #006400; font-style: italic;">*};</span>
   <span style="color: #0000ff;">do</span> week = <span style="color: #2e8b57; font-weight: bold;">1</span> to <span style="color: #0000ff; font-weight: bold;">&amp;num_weeks</span>.;
      girls_name = girl_week<span style="color: #66cc66;">&#91;</span>week<span style="color: #66cc66;">&#93;</span>;
      <span style="color: #0000ff;">output</span>;
   <span style="color: #0000ff;">end</span>;
   datalines;
Andrew Lauren   Morgan   Lauren   Nutsa    Samantha Lauren   Lauren   Samantha Cali     Cali
Brett  Cali     Asia     Cali     Kayla    Nutsa    Nutsa    Nutsa    Nutsa    Bria     Nutsa
Cam    Kayla    Kayla    Kayla    Asia     Kayla    Kayla    Cali     Lauren   Morgan   Bria
Daniel Nutsa    Nutsa    Samantha Lauren   Bria     Samantha Samantha Asia     Lauren   Jasmine
Kwasi  Asia     Lauren   Jasmine  Bria     Jasmine  Asia     Asia     Jasmine  Nutsa    Lauren
Lewis  Samantha Jasmine  Asia     Kenya    Lauren   Bria     Bria     Bria     Asia     Samantha
Moe    Jasmine  Bria     Nutsa    Samantha Asia     Jasmine  Jasmine  Kayla    Kayla    Kayla
Shamoy Maria    Maria    Maria    Maria    Maria    Maria    Maria    Maria    Maria    Maria
Tevin  Kenya    Kenya    Kenya    Jasmine  Kenya    Kenya    Kenya    Kenya    Kenya    Kenya
Tomas  Morgan   Cali     Bria     Cali     Cali     Cali     Kayla    Morgan   Jasmine  Asia
Zak    Bria     Samantha Morgan   Morgan   Morgan   Morgan   Morgan   Cali     Samantha Morgan
;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input number of correct matches (from matching ceremony) up to current week */</span>
<span style="color: #000080; font-weight: bold;">data</span> input_ceremony_correct_matches;
   <span style="color: #0000ff;">input</span> week correct_guesses;
   datalines;
<span style="color: #2e8b57; font-weight: bold;">1</span> <span style="color: #2e8b57; font-weight: bold;">3</span>
<span style="color: #2e8b57; font-weight: bold;">2</span> <span style="color: #2e8b57; font-weight: bold;">3</span>
<span style="color: #2e8b57; font-weight: bold;">3</span> <span style="color: #2e8b57; font-weight: bold;">3</span>
<span style="color: #2e8b57; font-weight: bold;">4</span> <span style="color: #2e8b57; font-weight: bold;">2</span>
<span style="color: #2e8b57; font-weight: bold;">5</span> <span style="color: #2e8b57; font-weight: bold;">4</span>
<span style="color: #2e8b57; font-weight: bold;">6</span> <span style="color: #2e8b57; font-weight: bold;">4</span>
<span style="color: #2e8b57; font-weight: bold;">7</span> <span style="color: #2e8b57; font-weight: bold;">4</span>
<span style="color: #2e8b57; font-weight: bold;">8</span> <span style="color: #2e8b57; font-weight: bold;">4</span>
<span style="color: #2e8b57; font-weight: bold;">9</span> <span style="color: #2e8b57; font-weight: bold;">4</span>
<span style="color: #2e8b57; font-weight: bold;">10</span> <span style="color: #2e8b57; font-weight: bold;">11</span>
;
&nbsp;
<span style="color: #006400; font-style: italic;">/* Input couples that went to truth booth and their outcome (1 if they are a match, 0 otherwise) up to current week */</span>
<span style="color: #000080; font-weight: bold;">data</span> input_truth_booth;
   <span style="color: #0000ff;">input</span> week boys_name $ girls_name $ outcome;
   datalines;
<span style="color: #2e8b57; font-weight: bold;">1</span>  Tomas    Maria    <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">2</span>  Andrew   Asia     <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">3</span>  Shamoy   Maria    <span style="color: #2e8b57; font-weight: bold;">1</span>
<span style="color: #2e8b57; font-weight: bold;">4</span>  Brett    Kenya    <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">5</span>  Zak      Bria     <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">6</span>  Brett    Cali     <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">7</span>  Zak      Nutsa    <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">8</span>  Tevin    Kenya    <span style="color: #2e8b57; font-weight: bold;">1</span>
<span style="color: #2e8b57; font-weight: bold;">9</span>  Cam      Samantha <span style="color: #2e8b57; font-weight: bold;">0</span>
<span style="color: #2e8b57; font-weight: bold;">10</span> Brett    Nutsa    <span style="color: #2e8b57; font-weight: bold;">1</span>
;</pre></td></tr></table></div>

<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2018/08/14/are-you-the-one/">&quot;Are You The One?&quot; Finding the matching through SAS Constraint Programming</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/operations/2018/08/14/are-you-the-one/feed/</wfw:commentRss>
			<slash:comments>8</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/operations/files/2018/08/AYTO-Season-6-Cast-Photo-Resized-150x150.jpg"/>
	</item>
		<item>
		<title>Visiting all 30 Major League Baseball Stadiums - with Python and SAS® Viya®</title>
		<link>https://blogs.sas.com/content/operations/2018/06/13/visiting-all-30-major-league-baseball-stadiums-with-python-and-sas-viya/</link>
					<comments>https://blogs.sas.com/content/operations/2018/06/13/visiting-all-30-major-league-baseball-stadiums-with-python-and-sas-viya/#comments</comments>
		
		<dc:creator><![CDATA[Sertalp B. Cay]]></dc:creator>
		<pubDate>Wed, 13 Jun 2018 15:33:07 +0000</pubDate>
				<category><![CDATA[mixed integer linear optimization]]></category>
		<category><![CDATA[network optimization]]></category>
		<category><![CDATA[sports analytics]]></category>
		<category><![CDATA[baseball]]></category>
		<category><![CDATA[operations research]]></category>
		<category><![CDATA[optimization]]></category>
		<category><![CDATA[R&D Analytics]]></category>
		<category><![CDATA[SAS Viya]]></category>
		<guid isPermaLink="false">https://blogs.sas.com/content/operations/?p=1310</guid>

					<description><![CDATA[<p>Ballpark Chasers A cross-country trip is pretty much an all-American experience, and so is baseball. Traveling around the country to see all 30 Major League Baseball (MLB) stadiums is not a new idea; there's even a social network between so-called "Ballpark Chasers" where people communicate and share their journeys. Even [...]</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2018/06/13/visiting-all-30-major-league-baseball-stadiums-with-python-and-sas-viya/">Visiting all 30 Major League Baseball Stadiums - with Python and SAS® Viya®</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2>Ballpark Chasers</h2>
<figure id="attachment_1459" aria-describedby="caption-attachment-1459" style="width: 640px" class="wp-caption alignright right"><img loading="lazy" decoding="async" class="wp-image-1459 size-full" title="Oriole Park at Camden Yards by Ed Hughes" src="https://blogs.sas.com/content/operations/files/2018/06/baltimore.png" alt="" width="640" height="480" srcset="https://blogs.sas.com/content/operations/files/2018/06/baltimore.png 640w, https://blogs.sas.com/content/operations/files/2018/06/baltimore-300x225.png 300w" sizes="(max-width: 640px) 100vw, 640px" /><figcaption id="caption-attachment-1459" class="wp-caption-text">Oriole Park at Camden Yards by Ed Hughes</figcaption></figure>
<p>A cross-country trip is pretty much an all-American experience, and so is baseball. Traveling around the country to see all 30 Major League Baseball (MLB) stadiums is not a new idea; there's even a social network between so-called "<a href="http://www.ballparkchasers.com">Ballpark Chasers</a>" where people communicate and share their journeys. Even though I'm not a baseball fan myself, I find the idea of traveling around the country to visit all 30 MLB stadiums pretty interesting.</p>
<p>Since we all lack time, the natural question that might pop into your mind is, "How fast can I visit all 30 stadiums and see a MLB game in each?" This question was first asked by <a href="http://www.jstor.org/stable/25678277">Cleary et al. (2000)</a> in a mathematical context. This is where the math and baseball intersect. Finding the optimal trip is an expensive calculation. Discarding the schedule for a second and focusing only on ordering stadiums to visit results in more than <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="2.65 \times 10^{32}" /></span><script type='math/tex'>2.65 \times 10^{32}</script> different permutations. When you add the game schedule and distances between stadiums, the problem gets much bigger and more difficult quickly. See the <a href="https://en.wikipedia.org/wiki/Travelling_salesman_problem">Traveling Salesman Problem</a> if you are interested in difficult scheduling problems, which is the main source of the "Traveling Baseball Fan Problem."</p>
<h2>The Optimal Trip</h2>
<p>Before starting to talk about "The Optimal Trip" I should make some assumptions. The Optimal Trip is quite a subjective term, so I need to choose a measurable objective. My focus is to complete the schedule within the shortest time possible. Further, I assume that the superfan only uses land transportation (a car) between stadiums. Unlike some variations, I don't require the fan to return back to the origin, so the start and end cities will be different. Each stadium will be visited only once.</p>
<p>The Traveling Baseball Fan Problem (TBFP) has gained quite a bit of attention. There is a book by Ben Blatt and Eric Brewster about their 30 games in 30 days. They created an <a href="http://www.slate.com/articles/sports/sports_nut/2017/03/how_to_visit_all_30_major_league_ballparks_in_30_days.html?staz=Pirates&amp;st2z=7">online visualization</a> of such a tour for those interested; unfortunately the tool only shows schedules for the 2017 season. Their approach here is a heuristic, so the resulting solution is not guaranteed to be the "shortest possible tour." Since the problem is huge, one can only expect the true optimal solution to be obtained after optimization. Ben Blatt also wrote a <a href="https://harvardsportsanalysis.files.wordpress.com/2011/06/shortest_possible_baseball_road_trip1.pdf">mathematical optimization formulation</a> for the shortest possible baseball road trip.</p>
<p>There are different ways to model this problem. The model I am going to use to optimize the TBFP is the network-based formulation presented in a <a href="https://support.sas.com/resources/papers/proceedings14/SAS101-2014.pdf">SAS Global Forum 2014 paper</a> by Chapman, Galati, and Pratt. <a href="https://blogs.sas.com/content/author/robpratt/">Rob Pratt</a>, one of the authors, <a href="https://blogs.sas.com/content/operations/2015/04/03/the-traveling-baseball-fan-problem/">wrote about the TBFP</a> in this blog before.</p>
<h2>Ground Rules and Challenge</h2>
<p>Just to reiterate, ground rules for the optimal schedule:</p>
<ol>
<li>Use ground transportation only.</li>
<li>Use driving distances between stadiums. The driving distances are obtained via <a href="http://project-osrm.org/">OSRM</a>.</li>
<li>Stay until each game ends (assume each game lasts 3 hours).</li>
</ol>
<p>The main challenge is to gather data, model the problem, and visualize results---all within the Python environment. Moreover, my aim here is to show you that the mathematical formulation for the TBFP can easily be written with our new open-source Python package <strong>sasoptpy</strong> and can be solved using SAS Viya on the cloud. If you are interested, check our <a href="https://github.com/sassoftware/sasoptpy">Github repository</a> for the package and our <a href="https://www.sas.com/content/dam/SAS/support/en/sas-global-forum-proceedings/2018/1814-2018.pdf">SAS Global Forum 2018 paper</a> (Erickson and Cay) to learn more about it!</p>
<p>To provide variety, let's solve the problem for different time periods (2, 3, and 6 months) with two different objectives. The first objective is to finish the schedule in the shortest time possible. The second objective is to finish the schedule with spending the least amount of money. For this, I will assume $130 accommodation rate per day and $0.25 travel cost per mile. This objective was the main motivation of Rick and Mike's 1980 tour, as mentioned in the paper by <a href="http://www.jstor.org/stable/25678277">Cleary et al. (2000</a>).</p>
<h2>TBFP Model</h2>
<p>I will use the network formulation from the <a href="http://support.sas.com/resources/papers/proceedings14/SAS101-2014.pdf">aforementioned SAS Global Forum 2014 paper</a>. For this model, I define directed arcs between pairs of games, eliminate the arcs that cannot be part of a feasible solution, and optimize the given objective.</p>
<p>The decision variable in this formulation is <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="u" /></span><script type='math/tex'>u</script>, which is defined for each arc <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="(g_1, g_2)" /></span><script type='math/tex'>(g_1, g_2)</script> as</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="u[g_1, g_2] = \begin{cases}1 & \text{if the fan attends games $g_1$ and $g_2$ and no game in between} \\0 & \text{otherwise}\end{cases}" /></span><script type='math/tex'>u[g_1, g_2] = \begin{cases}1 & \text{if the fan attends games $g_1$ and $g_2$ and no game in between} \\0 & \text{otherwise}\end{cases}</script></p>
<p>Denote <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="c[g_1,g_2]" /></span><script type='math/tex'>c[g_1,g_2]</script> as the time between games <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g_1" /></span><script type='math/tex'>g_1</script> and <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g_2" /></span><script type='math/tex'>g_2</script> in days, including game duration, as follows:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt=" c[g_1,g_2] = \begin{cases} \textrm{end}[g_2] - \textrm{end}[g_1] & \textrm{if } g_1 \not = \textrm{source and } g_2 \not = \textrm{sink} \\ \textrm{end} [g_2] - \textrm{start}[g_2] & \textrm{if } g_1 = \textrm{source and } g_2 \not = \textrm{sink} \\ 0 & \textrm{otherwise} \end{cases} " /></span><script type='math/tex'> c[g_1,g_2] = \begin{cases} \textrm{end}[g_2] - \textrm{end}[g_1] & \textrm{if } g_1 \not = \textrm{source and } g_2 \not = \textrm{sink} \\ \textrm{end} [g_2] - \textrm{start}[g_2] & \textrm{if } g_1 = \textrm{source and } g_2 \not = \textrm{sink} \\ 0 & \textrm{otherwise} \end{cases} </script></p>
<p>Also denote <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="l[g]" /></span><script type='math/tex'>l[g]</script> as the location of the game <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="g" /></span><script type='math/tex'>g</script>, <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{NODES}" /></span><script type='math/tex'>\text{NODES}</script> as the list of games including dummy nodes 'source' and 'sink', <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{ARCS}" /></span><script type='math/tex'>\text{ARCS}</script> as the connections between games, and finally <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\text{STADIUMS}" /></span><script type='math/tex'>\text{STADIUMS}</script> as the list of all stadiums. Now I can write the Network Formulation as follows:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\begin{array}{rlcll} \textrm{minimize:} & \displaystyle \sum_{(g_1, g_2) \in \text{ARCS}} c[g_1,g_2] \cdot u[g_1,g_2] \\ \textrm{subject to:} & \displaystyle \sum_{(g,g_2) \in \text{ARCS}} u[g,g_2] - \sum_{(g_1,g) \in \text{ARCS}} u[g_1,g] & = & \begin{cases} 1 & \text{if } g = \text{source,} \\ -1 & \text{if } g = \text{sink,} \\ 0 & \text{otherwise}\end{cases} & & \forall g \in \text{NODES} \\ & \displaystyle \sum_{(g_1,g_2) \in \text{ARCS}: g_2 \not = \text{sink and } l[g_2] = s} u[g_1, g_2] & = & 1 & & \forall s \in \text{STADIUMS} \end{array}" /></span><script type='math/tex'>\begin{array}{rlcll} \textrm{minimize:} & \displaystyle \sum_{(g_1, g_2) \in \text{ARCS}} c[g_1,g_2] \cdot u[g_1,g_2] \\ \textrm{subject to:} & \displaystyle \sum_{(g,g_2) \in \text{ARCS}} u[g,g_2] - \sum_{(g_1,g) \in \text{ARCS}} u[g_1,g] & = & \begin{cases} 1 & \text{if } g = \text{source,} \\ -1 & \text{if } g = \text{sink,} \\ 0 & \text{otherwise}\end{cases} & & \forall g \in \text{NODES} \\ & \displaystyle \sum_{(g_1,g_2) \in \text{ARCS}: g_2 \not = \text{sink and } l[g_2] = s} u[g_1, g_2] & = & 1 & & \forall s \in \text{STADIUMS} \end{array}</script></p>
<p>The solution of this optimization problem should produce a route starting at the source, finishing at the sink, and passing through all 30 ballparks. The objective here is to minimize the total schedule time. The first set of constraints ensures that inflow and outflow are equal for regular nodes. The second set of constraints ensures that the fan visits every stadium once.</p>
<p>For the second objective, I need to replace the objective function with the following:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\textrm{minimize:} \; \displaystyle 130 \cdot \sum_{(g_1, g_2) \in \text{ARCS}} c[g_1, g_2] \cdot u[g_1, g_2] + 0.25 \cdot \sum_{(g_1, g_2) \in \text{ARCS}: g_1 \not = \text{source} \text{ and } g_2 \not = \text{sink}} d[g_1, g_2] \cdot u[g_1, g_2]" /></span><script type='math/tex'>\textrm{minimize:} \; \displaystyle 130 \cdot \sum_{(g_1, g_2) \in \text{ARCS}} c[g_1, g_2] \cdot u[g_1, g_2] + 0.25 \cdot \sum_{(g_1, g_2) \in \text{ARCS}: g_1 \not = \text{source} \text{ and } g_2 \not = \text{sink}} d[g_1, g_2] \cdot u[g_1, g_2]</script></p>
<p>where <span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="d" /></span><script type='math/tex'>d</script> is the distance between games in miles.</p>
<h3>Modeling with sasoptpy</h3>
<p>Now that I have my formulation ready, it is a breeze to write this problem using sasoptpy. Only part of the code is shown here for illustration purposes. See the <a href="https://github.com/sertalpbilal/traveling-baseball-fan-problem">Github repository</a> for all of the code, including the code used for grabbing the season schedule from the MLB website, driving distances from OpenStreetMap, and exporting results.</p>
<p>I can write the Network Formulation to solve TBFP in Python as follows:</p>

<div class="wp_syntax"><table><tr><td class="code"><pre class="python" style="font-family:monospace;"><span style="color: #483d8b;">'''
Defines the optimization problem and solves it.
&nbsp;
Parameters
----------
distance_data : pandas.DataFrame
    Distances between stadiums in miles.
driving_data : pandas.DataFrame
    The driving times between stadiums in minutes.
game_data : pandas.DataFrame
    The game schedule information for the current season.
venue_data : pandas.DataFrame
    The information regarding each 30 MLB venues.
start_date : datetime.date, optional
    The earliest start date for the schedule.
end_date : datetime.date, optional
    The latest end date for the schedule.
obj_type : integer, optional
    Objective type for the optimization problem,
    0: Minimize total schedule time, 1: Minimize total cost
'''</span>
<span style="color: #ff7700;font-weight:bold;">def</span> tbfp<span style="color: black;">&#40;</span>distance_data<span style="color: #66cc66;">,</span> driving_data<span style="color: #66cc66;">,</span> game_data<span style="color: #66cc66;">,</span> venue_data<span style="color: #66cc66;">,</span>
         start_date<span style="color: #66cc66;">=</span><span style="color: #dc143c;">datetime</span>.<span style="color: black;">date</span><span style="color: black;">&#40;</span><span style="color: #ff4500;">2018</span><span style="color: #66cc66;">,</span> <span style="color: #ff4500;">3</span><span style="color: #66cc66;">,</span> <span style="color: #ff4500;">29</span><span style="color: black;">&#41;</span><span style="color: #66cc66;">,</span>
         end_date<span style="color: #66cc66;">=</span><span style="color: #dc143c;">datetime</span>.<span style="color: black;">date</span><span style="color: black;">&#40;</span><span style="color: #ff4500;">2018</span><span style="color: #66cc66;">,</span> <span style="color: #ff4500;">10</span><span style="color: #66cc66;">,</span> <span style="color: #ff4500;">31</span><span style="color: black;">&#41;</span><span style="color: #66cc66;">,</span>
         obj_type<span style="color: #66cc66;">=</span><span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span>:
&nbsp;
    <span style="color: #808080; font-style: italic;"># Define a CAS session</span>
    cas_session <span style="color: #66cc66;">=</span> CAS<span style="color: black;">&#40;</span>your_cas_server<span style="color: #66cc66;">,</span> port<span style="color: #66cc66;">=</span>your_cas_port<span style="color: black;">&#41;</span>
    m <span style="color: #66cc66;">=</span> so.<span style="color: black;">Model</span><span style="color: black;">&#40;</span>name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'tbfp'</span><span style="color: #66cc66;">,</span> session<span style="color: #66cc66;">=</span>cas_session<span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># Define sets, parameters and pre-process data (omitted)</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># Add variables</span>
    use_arc <span style="color: #66cc66;">=</span> m.<span style="color: black;">add_variables</span><span style="color: black;">&#40;</span>ARCS<span style="color: #66cc66;">,</span> vartype<span style="color: #66cc66;">=</span>so.<span style="color: black;">BIN</span><span style="color: #66cc66;">,</span> name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'use_arc'</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># Define expressions for the objectives</span>
    total_time <span style="color: #66cc66;">=</span> so.<span style="color: black;">quick_sum</span><span style="color: black;">&#40;</span>
        cost<span style="color: black;">&#91;</span>g1<span style="color: #66cc66;">,</span>g2<span style="color: black;">&#93;</span> * use_arc<span style="color: black;">&#91;</span>g1<span style="color: #66cc66;">,</span>g2<span style="color: black;">&#93;</span> <span style="color: #ff7700;font-weight:bold;">for</span> <span style="color: black;">&#40;</span>g1<span style="color: #66cc66;">,</span> g2<span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">in</span> ARCS<span style="color: black;">&#41;</span>
    total_distance <span style="color: #66cc66;">=</span> so.<span style="color: black;">quick_sum</span><span style="color: black;">&#40;</span>
        distance<span style="color: black;">&#91;</span>location<span style="color: black;">&#91;</span>g1<span style="color: black;">&#93;</span><span style="color: #66cc66;">,</span> location<span style="color: black;">&#91;</span>g2<span style="color: black;">&#93;</span><span style="color: black;">&#93;</span> * use_arc<span style="color: black;">&#91;</span>g1<span style="color: #66cc66;">,</span> g2<span style="color: black;">&#93;</span>
        <span style="color: #ff7700;font-weight:bold;">for</span> <span style="color: black;">&#40;</span>g1<span style="color: #66cc66;">,</span> g2<span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">in</span> ARCS <span style="color: #ff7700;font-weight:bold;">if</span> g1 <span style="color: #66cc66;">!=</span> <span style="color: #483d8b;">'source'</span> <span style="color: #ff7700;font-weight:bold;">and</span> g2 <span style="color: #66cc66;">!=</span> <span style="color: #483d8b;">'sink'</span><span style="color: black;">&#41;</span>
    total_cost <span style="color: #66cc66;">=</span> total_time * <span style="color: #ff4500;">130</span> + total_distance * <span style="color: #ff4500;">0.25</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># Set objectives</span>
    <span style="color: #ff7700;font-weight:bold;">if</span> obj_type <span style="color: #66cc66;">==</span> <span style="color: #ff4500;">0</span>:
        m.<span style="color: black;">set_objective</span><span style="color: black;">&#40;</span>total_time<span style="color: #66cc66;">,</span> sense<span style="color: #66cc66;">=</span>so.<span style="color: black;">MIN</span><span style="color: black;">&#41;</span>
    <span style="color: #ff7700;font-weight:bold;">elif</span> obj_type <span style="color: #66cc66;">==</span> <span style="color: #ff4500;">1</span>:
        m.<span style="color: black;">set_objective</span><span style="color: black;">&#40;</span>total_cost<span style="color: #66cc66;">,</span> sense<span style="color: #66cc66;">=</span>so.<span style="color: black;">MIN</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># Balance constraint</span>
    m.<span style="color: black;">add_constraints</span><span style="color: black;">&#40;</span><span style="color: black;">&#40;</span>
        so.<span style="color: black;">quick_sum</span><span style="color: black;">&#40;</span>use_arc<span style="color: black;">&#91;</span>g<span style="color: #66cc66;">,</span> g2<span style="color: black;">&#93;</span> <span style="color: #ff7700;font-weight:bold;">for</span> <span style="color: black;">&#40;</span>gx<span style="color: #66cc66;">,</span>g2<span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">in</span> ARCS <span style="color: #ff7700;font-weight:bold;">if</span> gx<span style="color: #66cc66;">==</span>g<span style="color: black;">&#41;</span> -\
        so.<span style="color: black;">quick_sum</span><span style="color: black;">&#40;</span>use_arc<span style="color: black;">&#91;</span>g1<span style="color: #66cc66;">,</span> g<span style="color: black;">&#93;</span> <span style="color: #ff7700;font-weight:bold;">for</span> <span style="color: black;">&#40;</span>g1<span style="color: #66cc66;">,</span>gx<span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">in</span> ARCS <span style="color: #ff7700;font-weight:bold;">if</span> gx<span style="color: #66cc66;">==</span>g<span style="color: black;">&#41;</span>\
        <span style="color: #66cc66;">==</span> <span style="color: black;">&#40;</span><span style="color: #ff4500;">1</span> <span style="color: #ff7700;font-weight:bold;">if</span> g <span style="color: #66cc66;">==</span> <span style="color: #483d8b;">'source'</span> <span style="color: #ff7700;font-weight:bold;">else</span> <span style="color: black;">&#40;</span>-<span style="color: #ff4500;">1</span> <span style="color: #ff7700;font-weight:bold;">if</span> g <span style="color: #66cc66;">==</span> <span style="color: #483d8b;">'sink'</span> <span style="color: #ff7700;font-weight:bold;">else</span> <span style="color: #ff4500;">0</span><span style="color: black;">&#41;</span> <span style="color: black;">&#41;</span>
        <span style="color: #ff7700;font-weight:bold;">for</span> g <span style="color: #ff7700;font-weight:bold;">in</span> NODES<span style="color: black;">&#41;</span><span style="color: #66cc66;">,</span>
        name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'balance'</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># Visit once constraint</span>
    visit_once <span style="color: #66cc66;">=</span> so.<span style="color: black;">ConstraintGroup</span><span style="color: black;">&#40;</span><span style="color: black;">&#40;</span>
        so.<span style="color: black;">quick_sum</span><span style="color: black;">&#40;</span>
            use_arc<span style="color: black;">&#91;</span>g1<span style="color: #66cc66;">,</span>g2<span style="color: black;">&#93;</span>
            <span style="color: #ff7700;font-weight:bold;">for</span> <span style="color: black;">&#40;</span>g1<span style="color: #66cc66;">,</span>g2<span style="color: black;">&#41;</span> <span style="color: #ff7700;font-weight:bold;">in</span> ARCS <span style="color: #ff7700;font-weight:bold;">if</span> g2 <span style="color: #66cc66;">!=</span> <span style="color: #483d8b;">'sink'</span> <span style="color: #ff7700;font-weight:bold;">and</span> location<span style="color: black;">&#91;</span>g2<span style="color: black;">&#93;</span> <span style="color: #66cc66;">==</span> s<span style="color: black;">&#41;</span> <span style="color: #66cc66;">==</span> <span style="color: #ff4500;">1</span>
        <span style="color: #ff7700;font-weight:bold;">for</span> s <span style="color: #ff7700;font-weight:bold;">in</span> STADIUMS<span style="color: black;">&#41;</span><span style="color: #66cc66;">,</span> name<span style="color: #66cc66;">=</span><span style="color: #483d8b;">'visit_once'</span><span style="color: black;">&#41;</span>
    m.<span style="color: black;">include</span><span style="color: black;">&#40;</span>visit_once<span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># Send the problem to SAS Viya solvers and solve the problem</span>
    m.<span style="color: black;">solve</span><span style="color: black;">&#40;</span>milp<span style="color: #66cc66;">=</span><span style="color: black;">&#123;</span><span style="color: #483d8b;">'concurrent'</span>: <span style="color: #008000;">True</span><span style="color: black;">&#125;</span><span style="color: black;">&#41;</span>
&nbsp;
    <span style="color: #808080; font-style: italic;"># Post-process results (omitted)</span></pre></td></tr></table></div>

<h2>Experiments</h2>
<p>I ran this problem for the following twelve settings.</p>
<table style="padding: 0px;border-collapse: collapse;border-spacing: 0px;margin-bottom: 16px;background-color: #fafafa">
<thead>
<tr>
<th style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px;font-weight: bold">ID</th>
<th style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px;font-weight: bold">Period</th>
<th style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px;font-weight: bold">Objective (Min)</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">1</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">03/29 - 06/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Time</td>
</tr>
<tr style="background-color: #f2f2f2">
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">2</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">03/29 - 06/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Cost</td>
</tr>
<tr>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">3</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">06/01 - 08/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Time</td>
</tr>
<tr style="background-color: #f2f2f2">
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">4</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">06/01 - 08/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Cost</td>
</tr>
<tr>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">5</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">08/01 - 10/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Time</td>
</tr>
<tr style="background-color: #f2f2f2">
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">6</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">08/01 - 10/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Cost</td>
</tr>
<tr>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">7</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">03/29 - 07/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Time</td>
</tr>
<tr style="background-color: #f2f2f2">
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">8</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">03/29 - 07/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Cost</td>
</tr>
<tr>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">9</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">07/01 - 10/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Time</td>
</tr>
<tr style="background-color: #f2f2f2">
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">10</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">07/01 - 10/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Cost</td>
</tr>
<tr>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">11</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">03/29 - 10/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Time</td>
</tr>
<tr style="background-color: #f2f2f2">
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">12</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">03/29 - 10/01</td>
<td style="text-align: center;border: 1px solid #e6e6e6;margin: 0px;padding: 6px 13px">Cost</td>
</tr>
</tbody>
</table>
<p>The last two settings (11 and 12) cover the entire 2018 MLB season from March 29th to October 1st. Therefore, these problems should give the optimal solutions for the best time and best cost objectives, respectively. My aim is to show how the problem size and solution time grow when the problem period is larger.</p>
<h2>Visualization</h2>
<p>The ultimate benefit of working within the Python environment is ability to use open-source packages for many tasks. I have used the <a href="https://bokeh.pydata.org/en/latest/">Bokeh package</a> for plots and <a href="http://python-visualization.github.io/folium/">Folium</a> for generating travel maps below. Bokeh is capable of web-ready interactive plots, which makes the visualization engaging for the user. Folium uses the Leaflet.js Javascript library to generate interactive maps based on OpenStreetMap maps. For interaction between the Bokeh scatter plots and the Leaflet maps, I have used a custom Javascript function provided by the Bokeh CustomJS class. You can see details of how the visualization part works in the <a href="http://nbviewer.jupyter.org/github/sertalpbilal/traveling-baseball-fan-problem/blob/master/TBFP.ipynb">Jupyter notebook</a>.</p>
<h2>Results</h2>
<p>The optimal solution I obtained from the 11th experiment gives the best schedule time for the 2018 season, which is just over 24 days (24 days and 3 hours). The solution starts with Diamondbacks @ Giants in AT&amp;T Park, San Francisco on the 5th of June and ends with Royals @ Mariners in Safeco Field, Seattle on the 29th of June. This is the global best solution among the scheduled games this season. Maps and itineraries of selected solutions are shown below. Click on any of the plots and tables to see the <a href="http://nbviewer.jupyter.org/github/sertalpbilal/traveling-baseball-fan-problem/blob/master/TBFP.ipynb">Jupyter notebook</a>. All times in these schedules are in EDT.</p>
<p><a href="http://nbviewer.jupyter.org/github/sertalpbilal/traveling-baseball-fan-problem/blob/master/TBFP.ipynb#plots"><img loading="lazy" decoding="async" class="aligncenter wp-image-1451 size-full" src="https://blogs.sas.com/content/operations/files/2018/06/map11.png" alt="" width="914" height="557" srcset="https://blogs.sas.com/content/operations/files/2018/06/map11.png 914w, https://blogs.sas.com/content/operations/files/2018/06/map11-300x183.png 300w" sizes="(max-width: 914px) 100vw, 914px" /></a></p>
<p><a href="http://nbviewer.jupyter.org/github/sertalpbilal/traveling-baseball-fan-problem/blob/master/TBFP.ipynb#plots"><img loading="lazy" decoding="async" class="aligncenter wp-image-1453 size-full" src="https://blogs.sas.com/content/operations/files/2018/06/sched11.png" alt="" width="607" height="843" srcset="https://blogs.sas.com/content/operations/files/2018/06/sched11.png 607w, https://blogs.sas.com/content/operations/files/2018/06/sched11-216x300.png 216w" sizes="(max-width: 607px) 100vw, 607px" /></a></p>
<p>As a 22,528-mile trip, this schedule costs roughly $8,767. By changing the objective, it is possible to obtain a better cost, however the schedule takes longer significantly longer. Solution 12 is only 11,914 miles. It's 10,614 miles shorter and $2,149 cheaper compared to Solution 11 but takes 4 days longer.</p>
<p>Among the schedules you can still try at the writing of this post, Solution 9 gives the shortest schedule time (24 days 3 hours) and Solution 10 gives the best cost ($6,899). The latter schedule starts with Tigers @ Angels in Angel Stadium, Anaheim on the 6th of August, and ends with Orioles @ Mariners in Safeco Field, Seattle on the 4th of September. Note that, this solution is the longest schedule among all solutions I have with a little over 29 days.</p>
<p><a href="http://nbviewer.jupyter.org/github/sertalpbilal/traveling-baseball-fan-problem/blob/master/TBFP.ipynb#plots"><img loading="lazy" decoding="async" class="aligncenter wp-image-1464 size-full" src="https://blogs.sas.com/content/operations/files/2018/06/map10.png" alt="" width="916" height="552" srcset="https://blogs.sas.com/content/operations/files/2018/06/map10.png 916w, https://blogs.sas.com/content/operations/files/2018/06/map10-300x181.png 300w" sizes="(max-width: 916px) 100vw, 916px" /></a> <a href="http://nbviewer.jupyter.org/github/sertalpbilal/traveling-baseball-fan-problem/blob/master/TBFP.ipynb#plots"><img loading="lazy" decoding="async" class="aligncenter wp-image-1465 size-full" src="https://blogs.sas.com/content/operations/files/2018/06/sched10.png" alt="" width="599" height="838" srcset="https://blogs.sas.com/content/operations/files/2018/06/sched10.png 599w, https://blogs.sas.com/content/operations/files/2018/06/sched10-214x300.png 214w" sizes="(max-width: 599px) 100vw, 599px" /></a></p>
<p>Here's a list of all solutions:</p>
<p><img loading="lazy" decoding="async" class="aligncenter wp-image-1450 size-full" src="https://blogs.sas.com/content/operations/files/2018/06/all_solutions.png" alt="" width="723" height="340" srcset="https://blogs.sas.com/content/operations/files/2018/06/all_solutions.png 723w, https://blogs.sas.com/content/operations/files/2018/06/all_solutions-300x141.png 300w" sizes="(max-width: 723px) 100vw, 723px" /></p>
<p>The objective and the time period of the formulation heavily affect the solution time. Minimizing the cost takes longer due to unique optimal solutions. Moreover, increasing the time period from 3 months to 6 months nearly quadruples the solution time.</p>
<p>Ultimately, the best trip is up to you. You can define another objective of your choice, whether it be minimizing the cost, minimizing the schedule time, avoiding the risky connections (minimum time between games minus the driving time), minimizing the total driving time, or even maximizing the landmarks you have visited along the way. Whatever objective you choose, you can use <em>sasoptpy</em> and use the powerful SAS Viya mixed integer linear optimization solver to generate a trip that is perfect for you! Working in Python allows you to integrate packages you are familiar with and makes everything smoother. Do not forget to check my <a href="http://nbviewer.jupyter.org/github/sertalpbilal/traveling-baseball-fan-problem/blob/master/TBFP.ipynb">Jupyter notebook</a> and see the <a href="https://github.com/sertalpbilal/traveling-baseball-fan-problem/blob/master/tbf.py">Python files</a>.</p>
<h2>Moving Further</h2>
<p>You can further improve this model based on your desire. Here I list a few ideas:</p>
<p>If you would like to see an away and a home game for every team exactly once, then you can add the following constraint</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\displaystyle \sum_{(g, g_1) \in \text{ARCS}: \text{away}[g]= t \text{ and } \text{away}[g_1] \not = t} u[g,g_1]+\sum_{(g_1, g) \in \text{ARCS}: \text{away}[g]= t \text{ and } \text{away}[g_1] \not = t} u[g_1,g] = 1 \qquad \forall t \in \text{TEAMS}" /></span><script type='math/tex'>\displaystyle \sum_{(g, g_1) \in \text{ARCS}: \text{away}[g]= t \text{ and } \text{away}[g_1] \not = t} u[g,g_1]+\sum_{(g_1, g) \in \text{ARCS}: \text{away}[g]= t \text{ and } \text{away}[g_1] \not = t} u[g_1,g] = 1 \qquad \forall t \in \text{TEAMS}</script></p>
<p>You can try to avoid risky connections you have in the schedule by replacing the objective with</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\displaystyle \text{maximize: } \sum_{(g_1,g_2) \in \text{ARCS}} ( \text{start}[g_2] - \text{end}[g_1] - c[g_1,g_2])\cdot u[g_1, g_2]" /></span><script type='math/tex'>\displaystyle \text{maximize: } \sum_{(g_1,g_2) \in \text{ARCS}} ( \text{start}[g_2] - \text{end}[g_1] - c[g_1,g_2])\cdot u[g_1, g_2]</script></p>
<p>To prevent schedules that are too long, you should add a limit to the total schedule length, for example, 25 days:</p>
<p><span class='MathJax_Preview'><img src='' style='vertical-align: middle; border: none; ' class='tex' alt="\displaystyle \sum_{(g, \text{sink}) \in \text{ARCS}} \text{end}[g]\cdot u[g, \text{sink}] - \sum_{(\text{source}, g) \in \text{ARCS}} \text{start}[g]\cdot u[\text{source}, g] \leq 25" /></span><script type='math/tex'>\displaystyle \sum_{(g, \text{sink}) \in \text{ARCS}} \text{end}[g]\cdot u[g, \text{sink}] - \sum_{(\text{source}, g) \in \text{ARCS}} \text{start}[g]\cdot u[\text{source}, g] \leq 25</script></p>
<p>Share how you define the perfect schedule below if you have more ideas!</p>
<p>The post <a rel="nofollow" href="https://blogs.sas.com/content/operations/2018/06/13/visiting-all-30-major-league-baseball-stadiums-with-python-and-sas-viya/">Visiting all 30 Major League Baseball Stadiums - with Python and SAS® Viya®</a> appeared first on <a rel="nofollow" href="https://blogs.sas.com/content/operations">Operations Research with SAS</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blogs.sas.com/content/operations/2018/06/13/visiting-all-30-major-league-baseball-stadiums-with-python-and-sas-viya/feed/</wfw:commentRss>
			<slash:comments>5</slash:comments>
		
		
			<enclosure url="https://blogs.sas.com/content/operations/files/2018/06/baltimore-150x150.png"/>
	</item>
	</channel>
</rss>