To solve this problem, we need to define the decision variables, the objective function, and the constraints. 

Let's denote:
- \(M\) as the amount invested in company M,
- \(P\) as the amount invested in company P.

The objective is to maximize earnings from these investments. The earnings from each investment can be calculated by multiplying the amount invested by the respective return rate. Thus, the total earnings (\(E\)) can be represented as:
\[ E = 0.09M + 0.12P \]

Given constraints are:
1. The doctor has $100,000 to invest in total: \( M + P \leq 100,000 \)
2. He wants to invest at least three times as much money in company M than in company P: \( M \geq 3P \)
3. He can invest at most $80,000 in company M: \( M \leq 80,000 \)

We also have non-negativity constraints since investments cannot be negative: \( M \geq 0, P \geq 0 \)

To find the optimal investment amounts using Gurobi in Python, we'll define these variables and constraints within a model.

```python
from gurobipy import *

# Create a new model
m = Model("Vaccine_Investment")

# Define the decision variables
M = m.addVar(lb=0, name="Investment_in_M")
P = m.addVar(lb=0, name="Investment_in_P")

# Define the objective function: Maximize earnings
m.setObjective(0.09*M + 0.12*P, GRB.MAXIMIZE)

# Add constraints
m.addConstr(M + P <= 100000, name="Total_Investment")
m.addConstr(M >= 3*P, name="Minimum_M_to_P_Ratio")
m.addConstr(M <= 80000, name="Maximum_Investment_in_M")

# Optimize the model
m.optimize()

# Print the results
if m.status == GRB.OPTIMAL:
    print(f"Optimal Investment in Company M: ${M.x:.2f}")
    print(f"Optimal Investment in Company P: ${P.x:.2f}")
    print(f"Maximum Earnings: ${m.objVal:.2f}")
else:
    print("Model is infeasible")
```