Here's how we can formulate this problem and the corresponding Gurobi code:

**Decision Variables:**

*  `x`: Number of multiple-choice questions Nolan answers.
*  `y`: Number of short-answer questions Nolan answers.

**Objective Function:**

Maximize Nolan's score: `2x + 5y`

**Constraints:**

* **Total Questions:** `x + y <= 30`
* **Minimum Multiple Choice:** `x >= 15`
* **Minimum Short Answer:** `y >= 10`
* **Maximum Multiple Choice:** `x <= 20`
* **Maximum Short Answer:** `y <= 20`
* **Non-negativity:** `x, y >= 0` (implicitly handled by Gurobi for integer variables)
* **Integer Constraint:** `x, y` must be integers


```python
import gurobipy as gp

# Create a new model
model = gp.Model("PhysicsContest")

# Create variables
x = model.addVar(vtype=gp.GRB.INTEGER, name="multiple_choice")
y = model.addVar(vtype=gp.GRB.INTEGER, name="short_answer")

# Set objective function
model.setObjective(2*x + 5*y, gp.GRB.MAXIMIZE)

# Add constraints
model.addConstr(x + y <= 30, "total_questions")
model.addConstr(x >= 15, "min_multiple_choice")
model.addConstr(y >= 10, "min_short_answer")
model.addConstr(x <= 20, "max_multiple_choice")
model.addConstr(y <= 20, "max_short_answer")


# Optimize model
model.optimize()

# Print results
if model.status == gp.GRB.OPTIMAL:
    print(f"Optimal Score: {model.objVal}")
    print(f"Multiple Choice Questions: {x.x}")
    print(f"Short Answer Questions: {y.x}")
else:
    print("Infeasible or unbounded")

```
