Background
The inspiration for this post comes from this Chief Delphi thread asking what the maximum possible Offensive Power Rating (OPR) was in 2019. The easy answer is that there is no upper bound on 2019 OPRs because there was no upper bound for the score due to penalties. Oftentimes innocuous questions can lead down very intriguing paths though, and it got me interested enough to do a more detailed analysis of the question of what the maximum unpenalized OPR in 2019 would have been. Since the maximum unpenalized score for a 2019 match was 154, a first pass guess would say the max OPR would also be 154 (assuming a team scores 154 in all their matches and all other scores are 0). However, just like negative OPRs are theoretically possible, so too are OPRs greater than the max score.
OPR Theory
For a refresher on how OPR is calculated, check out Eugene’s great blog post from a few years ago, I’ll be skimming details here, so if you get lost try referencing back to that post. To summarize, the least squares solution to an overdetermined set of linear equations (or what is commonly called OPR around here) has the following closed form solution:

Where x is OPR, A is the schedule matrix, and b is the score matrix. From here on in, I’m going to refer to this:

As the multiplication matrix, since it multiplies with the score matrix to obtain the OPR matrix.
The schedule A is static, so the multiplication matrix is really just a static function of the schedule. Since all entries of b are bounded above (by the max score) and below (by 0), the logic to maximize an entry of x is simple: For each half-match (column), if the row corresponding to that entry of x in the multiplication matrix is positive, set that half match’s score equal to the max score. Otherwise, set that half-matches score equal to 0. I know that’s a lot to take in all at once, so here’s an example to illustrate.
Case Study
Let’s take a look at 5511 at the North Carolina State Championship. I’ve copied all of the entries in the row of the multiplication matrix corresponding to 5511 into the below table:
| match | blue | red |
| 1 | 0.085 | -0.007 |
| 2 | 0.004 | -0.010 |
| 3 | -0.010 | -0.002 |
| 4 | -0.014 | -0.005 |
| 5 | 0.004 | -0.010 |
| 6 | 0.003 | -0.010 |
| 7 | -0.012 | -0.012 |
| 8 | -0.003 | -0.010 |
| 9 | -0.003 | 0.004 |
| 10 | -0.009 | -0.013 |
| 11 | 0.085 | -0.006 |
| 12 | 0.004 | -0.010 |
| 13 | -0.013 | 0.083 |
| 14 | -0.007 | -0.011 |
| 15 | -0.002 | 0.003 |
| 16 | -0.011 | -0.002 |
| 17 | 0.002 | -0.005 |
| 18 | -0.011 | -0.003 |
| 19 | -0.003 | -0.005 |
| 20 | -0.010 | -0.012 |
| 21 | -0.003 | -0.009 |
| 22 | 0.083 | -0.003 |
| 23 | -0.002 | -0.013 |
| 24 | -0.014 | 0.010 |
| 25 | -0.010 | -0.013 |
| 26 | 0.000 | -0.011 |
| 27 | 0.083 | -0.003 |
| 28 | -0.010 | -0.002 |
| 29 | -0.002 | -0.002 |
| 30 | -0.006 | -0.007 |
| 31 | -0.004 | -0.001 |
| 32 | 0.081 | -0.011 |
| 33 | -0.003 | -0.013 |
| 34 | -0.010 | -0.003 |
| 35 | -0.011 | 0.004 |
| 36 | -0.012 | -0.009 |
| 37 | 0.083 | 0.012 |
| 38 | -0.013 | -0.003 |
| 39 | -0.002 | -0.009 |
| 40 | 0.081 | -0.013 |
| 41 | -0.003 | -0.010 |
| 42 | 0.004 | -0.005 |
| 43 | -0.003 | -0.014 |
| 44 | -0.014 | -0.003 |
| 45 | -0.002 | -0.010 |
| 46 | -0.013 | -0.003 |
| 47 | 0.003 | 0.086 |
| 48 | -0.006 | -0.003 |
| 49 | -0.002 | -0.004 |
| 50 | -0.002 | 0.084 |
| 51 | -0.010 | -0.006 |
| 52 | 0.006 | -0.012 |
| 53 | -0.014 | -0.004 |
| 54 | -0.009 | -0.014 |
| 55 | -0.009 | -0.011 |
| 56 | 0.005 | -0.003 |
| 57 | -0.005 | 0.003 |
| 58 | -0.011 | 0.083 |
| 59 | -0.005 | 0.004 |
| 60 | -0.010 | 0.082 |
| 61 | -0.003 | -0.012 |
| 62 | -0.005 | -0.002 |
| 63 | -0.011 | -0.004 |
| 64 | -0.002 | -0.012 |
You can interpret these numbers as the “impact” of each half-match to 5511’s OPR. So you can see that by far the most important half-matches for 5511 are ones like blue 1, blue 11, and red 13, which not coincidentally are the half-matches 5511 is in. These are the obvious first-order effects of this methodology, if you only look at these, it is very similar to just looking at average scores for teams.
You’ll also see that the majority of the remaining half-matches have negative values. This indicates that, generally, it reflects better on 5511 if half-matches they aren’t in have low scores because that means their partners likely weren’t contributing as much when they were partnered with 5511. We’ll call these effects second-order.
What we are most interested in right now though, are the half-matches that don’t contain 5511, but still have slight positive importance to them such as blue 2, blue 5, blue 6, and red 9. These positive values are generally due to third-order effects. Just like it’s a good sign for you if your partners do poorly without you, it’s also a good sign if their partners do well without them.
This pattern continues forever, odd degrees of separation between teams have positive impact to your team, and even degrees of separation have negative impact. But the further out you go, the more watered down these effects are. Interestingly, 538 recently discussed this same effect for their NBA RAPTOR calculation methodology (jump to point 3 in the section titled RAPTOR On-Off) where they choose to stop at the third order effects or two degrees of separation.
Alright, so now that we have that out of the way, let’s use those half-match impacts to get 5511 a stupid-high OPR. Just go through and make the score 154 if it has positive impact for 5511, and 0 if it has negative impact for them. Here are those scores:
| match | blue | red |
| 1 | 154 | 0 |
| 2 | 154 | 0 |
| 3 | 0 | 0 |
| 4 | 0 | 0 |
| 5 | 154 | 0 |
| 6 | 154 | 0 |
| 7 | 0 | 0 |
| 8 | 0 | 0 |
| 9 | 0 | 154 |
| 10 | 0 | 0 |
| 11 | 154 | 0 |
| 12 | 154 | 0 |
| 13 | 0 | 154 |
| 14 | 0 | 0 |
| 15 | 0 | 154 |
| 16 | 0 | 0 |
| 17 | 154 | 0 |
| 18 | 0 | 0 |
| 19 | 0 | 0 |
| 20 | 0 | 0 |
| 21 | 0 | 0 |
| 22 | 154 | 0 |
| 23 | 0 | 0 |
| 24 | 0 | 154 |
| 25 | 0 | 0 |
| 26 | 154 | 0 |
| 27 | 154 | 0 |
| 28 | 0 | 0 |
| 29 | 0 | 0 |
| 30 | 0 | 0 |
| 31 | 0 | 0 |
| 32 | 154 | 0 |
| 33 | 0 | 0 |
| 34 | 0 | 0 |
| 35 | 0 | 154 |
| 36 | 0 | 0 |
| 37 | 154 | 154 |
| 38 | 0 | 0 |
| 39 | 0 | 0 |
| 40 | 154 | 0 |
| 41 | 0 | 0 |
| 42 | 154 | 0 |
| 43 | 0 | 0 |
| 44 | 0 | 0 |
| 45 | 0 | 0 |
| 46 | 0 | 0 |
| 47 | 154 | 154 |
| 48 | 0 | 0 |
| 49 | 0 | 0 |
| 50 | 0 | 154 |
| 51 | 0 | 0 |
| 52 | 154 | 0 |
| 53 | 0 | 0 |
| 54 | 0 | 0 |
| 55 | 0 | 0 |
| 56 | 154 | 0 |
| 57 | 0 | 154 |
| 58 | 0 | 154 |
| 59 | 0 | 154 |
| 60 | 0 | 154 |
| 61 | 0 | 0 |
| 62 | 0 | 0 |
| 63 | 0 | 0 |
| 64 | 0 | 0 |
Altogether, this gets 5511 up to an OPR of 165.8! That’s astoundingly high, but it isn’t even the highest possible at this event. Since each team’s schedule is slightly different, each team’s max OPR is also slightly different. So the highest possible OPR at NC Champs would actually have been 186.8 for team 7890. We can then find the highest possible OPR of the 2019 season by doing this same analysis at every event. Unfortunately, no sane person would do that much work.
Full 2019 Results
But at least I did :). Here is the max and min OPRs possible for every in season event along with some other details about those events.
| event | matches per team | teams | matches | max OPR | min OPR |
| ausp | 8 | 62 | 83 | 269.4 | -218.0 |
| flor | 9 | 64 | 96 | 264.2 | -212.9 |
| flwp | 9 | 64 | 96 | 260.1 | -208.7 |
| scmb | 9 | 64 | 96 | 258.3 | -207.0 |
| mndu | 9 | 63 | 95 | 258.0 | -206.7 |
| mnmi | 9 | 63 | 95 | 257.6 | -206.3 |
| cada | 9 | 59 | 89 | 257.1 | -205.8 |
| carv | 10 | 68 | 114 | 256.4 | -205.1 |
| arli | 9 | 58 | 87 | 254.8 | -203.5 |
| casj | 9 | 59 | 89 | 254.6 | -203.2 |
| okok | 9 | 62 | 93 | 253.9 | -202.5 |
| hop | 10 | 68 | 114 | 253.7 | -202.4 |
| cars | 10 | 68 | 114 | 253.5 | -202.2 |
| mndu2 | 9 | 60 | 90 | 252.6 | -201.3 |
| dal | 10 | 68 | 114 | 251.5 | -200.2 |
| lake | 9 | 59 | 89 | 250.5 | -199.2 |
| roe | 10 | 67 | 112 | 250.2 | -198.8 |
| ohcl | 9 | 60 | 90 | 250.2 | -198.8 |
| tes | 10 | 68 | 114 | 250.1 | -198.8 |
| tur | 10 | 66 | 110 | 249.9 | -198.5 |
| new | 10 | 67 | 112 | 249.6 | -198.3 |
| cala | 9 | 56 | 84 | 249.4 | -198.1 |
| ohmv | 9 | 60 | 90 | 249.2 | -197.9 |
| mnmi2 | 9 | 60 | 90 | 248.3 | -197.0 |
| arc | 10 | 68 | 114 | 248.3 | -197.0 |
| gal | 10 | 67 | 112 | 248.0 | -196.7 |
| dar | 10 | 68 | 114 | 247.9 | -196.6 |
| cur | 10 | 68 | 114 | 247.7 | -196.4 |
| ilch | 9 | 53 | 80 | 245.7 | -194.3 |
| nyny | 9 | 51 | 77 | 239.4 | -188.1 |
| wila | 10 | 54 | 90 | 232.9 | -181.6 |
| alhu | 10 | 55 | 92 | 230.6 | -179.3 |
| azfl | 10 | 53 | 89 | 227.0 | -175.7 |
| ndgf | 10 | 52 | 87 | 226.5 | -175.2 |
| wimi | 10 | 54 | 90 | 225.8 | -174.5 |
| code | 10 | 53 | 89 | 225.7 | -174.4 |
| pncmp | 12 | 64 | 128 | 225.4 | -174.1 |
| tnkn | 10 | 51 | 85 | 225.3 | -174.0 |
| ftcmp | 12 | 64 | 128 | 222.1 | -170.8 |
| necmp | 12 | 64 | 128 | 221.0 | -169.7 |
| cafr | 10 | 49 | 82 | 220.8 | -169.5 |
| mosl | 10 | 48 | 80 | 220.4 | -169.0 |
| utwv | 10 | 49 | 82 | 220.3 | -168.9 |
| nyli2 | 10 | 48 | 80 | 219.9 | -168.6 |
| caoc | 10 | 49 | 82 | 219.0 | -167.6 |
| nysu | 10 | 46 | 77 | 218.5 | -167.1 |
| nyro | 10 | 48 | 80 | 217.8 | -166.4 |
| mrcmp | 12 | 60 | 120 | 215.8 | -164.5 |
| paca | 10 | 45 | 75 | 214.7 | -163.4 |
| iacf | 10 | 48 | 80 | 214.5 | -163.2 |
| chcmp | 12 | 58 | 116 | 211.6 | -160.3 |
| casd | 10 | 47 | 79 | 211.3 | -160.0 |
| mxmo | 10 | 43 | 72 | 210.1 | -158.8 |
| iscmp | 11 | 45 | 83 | 209.4 | -158.1 |
| nvlv | 11 | 44 | 81 | 198.9 | -147.6 |
| qcmo | 11 | 42 | 77 | 197.9 | -146.5 |
| casf | 11 | 43 | 79 | 196.6 | -145.3 |
| abca | 10 | 37 | 62 | 196.4 | -145.0 |
| gacmp | 12 | 45 | 90 | 196.0 | -144.7 |
| ilpe | 11 | 40 | 74 | 194.5 | -143.2 |
| nhgrs | 12 | 36 | 72 | 193.6 | -142.2 |
| ausc | 11 | 40 | 74 | 192.5 | -141.2 |
| txpas | 12 | 32 | 64 | 191.7 | -140.3 |
| txgre | 12 | 42 | 84 | 191.5 | -140.2 |
| oncmp2 | 12 | 40 | 80 | 190.8 | -139.4 |
| cave | 12 | 42 | 84 | 189.7 | -138.4 |
| wayak | 12 | 31 | 62 | 189.7 | -138.4 |
| txdel | 12 | 40 | 80 | 189.6 | -138.2 |
| misjo | 12 | 40 | 80 | 188.9 | -137.6 |
| micmp2 | 12 | 40 | 80 | 188.8 | -137.4 |
| azpx | 12 | 42 | 84 | 188.2 | -136.9 |
| qcqc | 11 | 35 | 65 | 188.2 | -136.9 |
| milan | 12 | 40 | 80 | 188.0 | -136.7 |
| mikng | 12 | 40 | 80 | 188.0 | -136.7 |
| inmis | 12 | 42 | 84 | 188.0 | -136.7 |
| mishe | 12 | 40 | 80 | 187.8 | -136.5 |
| mitvc | 12 | 40 | 80 | 187.7 | -136.3 |
| mifor | 12 | 40 | 80 | 187.6 | -136.3 |
| mimil | 12 | 40 | 80 | 187.4 | -136.1 |
| mitry | 12 | 41 | 82 | 187.3 | -136.0 |
| ctwat | 12 | 41 | 82 | 187.2 | -135.9 |
| mibel | 12 | 40 | 80 | 187.0 | -135.6 |
| milsu | 12 | 41 | 82 | 186.9 | -135.6 |
| nccmp | 12 | 32 | 64 | 186.8 | -135.4 |
| onosh | 12 | 40 | 80 | 186.8 | -135.4 |
| misou | 12 | 40 | 80 | 186.6 | -135.3 |
| micmp4 | 12 | 40 | 80 | 186.4 | -135.0 |
| marea | 12 | 38 | 76 | 186.2 | -134.9 |
| micen | 12 | 40 | 80 | 186.2 | -134.9 |
| miken | 12 | 41 | 82 | 186.2 | -134.9 |
| micmp3 | 12 | 40 | 80 | 186.2 | -134.9 |
| mimar | 12 | 40 | 80 | 186.0 | -134.7 |
| inwla | 12 | 39 | 78 | 186.0 | -134.7 |
| milin | 12 | 41 | 82 | 186.0 | -134.7 |
| milak | 12 | 39 | 78 | 185.8 | -134.5 |
| mxto | 11 | 36 | 66 | 185.8 | -134.4 |
| midet | 12 | 39 | 78 | 185.8 | -134.4 |
| miliv | 12 | 40 | 80 | 185.7 | -134.4 |
| onlon | 12 | 40 | 80 | 185.5 | -134.2 |
| onwin | 12 | 40 | 80 | 185.4 | -134.0 |
| micmp1 | 12 | 40 | 80 | 185.2 | -133.9 |
| mxcm | 11 | 27 | 50 | 185.0 | -133.6 |
| isde4 | 11 | 36 | 66 | 184.9 | -133.6 |
| onto3 | 12 | 32 | 64 | 184.9 | -133.6 |
| txdls | 12 | 39 | 78 | 184.9 | -133.5 |
| caln | 12 | 36 | 72 | 184.6 | -133.3 |
| nhdur | 12 | 40 | 80 | 184.6 | -133.3 |
| macma | 12 | 39 | 78 | 184.6 | -133.2 |
| mimus | 12 | 39 | 78 | 184.5 | -133.2 |
| oncmp1 | 12 | 40 | 80 | 184.2 | -132.9 |
| miwmi | 12 | 39 | 78 | 184.2 | -132.8 |
| mimid | 12 | 39 | 78 | 184.2 | -132.8 |
| cthar | 12 | 38 | 76 | 184.1 | -132.8 |
| bcvi | 11 | 35 | 65 | 184.0 | -132.7 |
| gagai | 12 | 38 | 76 | 184.0 | -132.6 |
| vagle | 12 | 37 | 74 | 183.9 | -132.5 |
| migul | 12 | 38 | 76 | 183.8 | -132.5 |
| melew | 12 | 32 | 64 | 183.7 | -132.3 |
| wasno | 12 | 38 | 76 | 183.6 | -132.3 |
| gafor | 12 | 30 | 60 | 183.2 | -131.9 |
| mdoxo | 12 | 39 | 78 | 182.8 | -131.5 |
| mike2 | 12 | 39 | 78 | 182.8 | -131.5 |
| mijac | 12 | 40 | 80 | 182.8 | -131.4 |
| vahay | 12 | 38 | 76 | 182.3 | -131.0 |
| mabri | 12 | 38 | 76 | 182.3 | -130.9 |
| mialp | 12 | 39 | 78 | 181.9 | -130.6 |
| txaus | 12 | 36 | 72 | 181.9 | -130.5 |
| gadal | 12 | 38 | 76 | 181.8 | -130.4 |
| vapor | 12 | 37 | 74 | 181.6 | -130.3 |
| waamv | 12 | 38 | 76 | 181.6 | -130.3 |
| mdowi | 12 | 38 | 76 | 181.6 | -130.2 |
| njfla | 12 | 37 | 74 | 181.5 | -130.2 |
| tuis | 11 | 34 | 63 | 181.5 | -130.1 |
| txama | 12 | 29 | 58 | 181.3 | -129.9 |
| nhsnh | 12 | 37 | 74 | 181.2 | -129.9 |
| njski | 12 | 37 | 74 | 181.0 | -129.6 |
| orwil | 12 | 38 | 76 | 180.4 | -129.1 |
| mawne | 12 | 30 | 60 | 180.3 | -128.9 |
| isde2 | 11 | 34 | 63 | 180.2 | -128.8 |
| onto1 | 12 | 30 | 60 | 179.9 | -128.6 |
| caav | 12 | 37 | 74 | 179.7 | -128.3 |
| nyli | 12 | 37 | 74 | 179.1 | -127.8 |
| onnyo | 12 | 36 | 72 | 179.1 | -127.8 |
| mdbet | 12 | 36 | 72 | 179.0 | -127.7 |
| mokc3 | 12 | 36 | 72 | 179.0 | -127.6 |
| mial2 | 12 | 36 | 72 | 178.5 | -127.2 |
| paphi | 12 | 31 | 62 | 178.5 | -127.2 |
| ksla | 12 | 36 | 72 | 178.5 | -127.1 |
| txsan | 12 | 36 | 72 | 178.5 | -127.1 |
| ncwak | 12 | 36 | 72 | 178.4 | -127.1 |
| mokc | 12 | 36 | 72 | 178.3 | -127.0 |
| nytr | 12 | 36 | 72 | 178.2 | -126.9 |
| ncgui | 12 | 36 | 72 | 178.2 | -126.8 |
| camb | 12 | 36 | 72 | 178.2 | -126.8 |
| orore | 12 | 36 | 72 | 178.2 | -126.8 |
| waspo | 12 | 29 | 58 | 177.9 | -126.6 |
| paben | 12 | 31 | 62 | 177.3 | -125.9 |
| ncpem | 12 | 36 | 72 | 177.2 | -125.9 |
| gaalb | 12 | 30 | 60 | 177.0 | -125.7 |
| isde3 | 11 | 31 | 57 | 176.7 | -125.4 |
| ingre | 12 | 35 | 70 | 176.3 | -125.0 |
| orlak | 12 | 36 | 72 | 176.3 | -125.0 |
| tuis2 | 11 | 32 | 59 | 176.2 | -124.9 |
| cadm | 12 | 35 | 70 | 175.9 | -124.6 |
| migib | 12 | 35 | 70 | 175.9 | -124.6 |
| miket | 12 | 35 | 70 | 175.9 | -124.6 |
| mabos | 12 | 28 | 56 | 175.6 | -124.3 |
| gacol | 12 | 35 | 70 | 175.6 | -124.3 |
| txpla | 12 | 35 | 70 | 175.5 | -124.1 |
| njbri | 12 | 35 | 70 | 174.9 | -123.6 |
| njtab | 12 | 35 | 70 | 174.9 | -123.5 |
| nyut | 13 | 31 | 68 | 174.8 | -123.5 |
| pahat | 12 | 35 | 70 | 174.6 | -123.3 |
| waahs | 12 | 34 | 68 | 174.5 | -123.2 |
| txcha | 12 | 35 | 70 | 174.2 | -122.9 |
| wamou | 12 | 34 | 68 | 174.0 | -122.6 |
| rismi | 12 | 29 | 58 | 173.8 | -122.5 |
| onham | 12 | 34 | 68 | 173.6 | -122.3 |
| vabla | 12 | 34 | 68 | 173.3 | -122.0 |
| hiho | 12 | 34 | 68 | 173.0 | -121.7 |
| isde1 | 11 | 31 | 57 | 172.8 | -121.5 |
| pawch | 12 | 32 | 64 | 172.0 | -120.7 |
| incmp | 12 | 32 | 64 | 171.4 | -120.1 |
| onnob | 12 | 33 | 66 | 171.4 | -120.0 |
| ncash | 12 | 32 | 64 | 171.3 | -120.0 |
| onbar | 12 | 26 | 52 | 171.0 | -119.6 |
| idbo | 12 | 27 | 54 | 170.9 | -119.5 |
| onwat | 12 | 32 | 64 | 169.2 | -117.8 |
| txelp | 12 | 25 | 50 | 167.0 | -115.7 |
So, I believe the max possible OPR in 2019 was 269.4, which could only have been achieved by 6508 at South Pacific.
Here I’ve graphed Max OPR against all three of matches per team, total matches, and total teams:



Conclusions
There’s plenty of other interesting info in here. Firstly, all 2019 events had a max OPR greater than 154. This bodes well for my hypothesis that all but a select few schedules will have some team capable of achieving a max OPR greater than the max score (although a proof or counterexample would be needed to rule definitively on this).
Another cool tidbit is that the minimum possible normalized OPR is always 1/3-(normalized max OPR). This means that, since all events have a normalized max OPR greater than or equal to 1.0, all events also must have a normalized min OPR which is less than or equal to -2/3.
Finally, if you want to predict the max OPR without this excessive effort, then it seems the best linear predictor is team count (R^2 = 0.89). As you can see though there is no straightforward formula to determine precisely what it is as there is clear schedule dependence. Here is a rough exponential function that bounds the 2019 data points above:

It’s formula is (max OPR) = (max score) *(1+e^((teams at event – 63)/26)). I think for the future though, providing the scheduler stays the same and we don’t get more than 70 teams at an event, we can comfortably say that there will probably never be an opportunity for a team to achieve an unpenalized OPR greater than 2.5X the max score.