It's better to be roughly right than precisely wrong

Often in modelling and simulation projects, there is a temptation to add more complexity to the solution to a problem than the original question demands - whether it's an overly fine mesh on a detailed geometry in a Finite Element Analysis, or a financial model that captures precise cash flows of all the various sub-components of a business.
The problem with this detail is that it is very difficult to model correctly. As a model becomes more complex there is more opportunity for error. We are often better off with a simple Excel model than a detailed all-singing-all-dancing 1-2-1 representation of a real system, for the very simple reason that simplicity is more robust to error.
When using a complicated model, the minimum standard the modeller should enforce is to develop a simple version of the model for sense-checking the results. This "hand calc" model (which doesn't need to be an actual hand calculation - a simple spreadsheet will suffice) will prevent results from being reported which are wildly off target.
One approach to keeping complexity in check is to relentlessly focus on the question. "What is the question, or set of questions, which we are trying to answer here?" From this, you can invert, and ask: "what can we safely exclude from the model?" The answers to this latter question often yield a clear direction on modelling scope and can serve to control complexity and its cascading consequences.
Yes, the simple approach yields less precise results and they won't handle the dastardly dynamic detail, but simple models are easy to build, easy to interpret, easy to explain, easy to understand and easy to see when they do not work. They are more right, if less precisely right.
Complex models, on the other hand, provide that warm fuzzy feeling everything has been considered - but with that complexity comes a drawback: they are far more likely to contain errors. To confound things they may provide a false sense of confidence in results. They are precise, but often not right, and it's better to be roughly right, than precisely wrong.
And now for something (not quite) completely different
Me: Using python and simpy write me a simulation of a fleet of mine trucks. I want to know, given a fleet size, how much material (i.e. production throughput) is mined and dumped at a particular location.
ChatGPT:
import simpy
import random
class MineTruck:
def __init__(self, env, capacity, mining_rate, dumping_rate, location):
self.env = env
self.capacity = capacity
self.mining_rate = mining_rate
self.dumping_rate = dumping_rate
self.location = location
self.material = 0
self.action = env.process(self.run())
def run(self):
while True:
# move to mining location
yield self.env.timeout(random.randint(1, 5))
# mine material
mining_duration = self.capacity / self.mining_rate
yield self.env.timeout(mining_duration)
self.material = self.capacity
# move to dumping location
yield self.env.timeout(random.randint(1, 5))
# dump material
dumping_duration = self.material / self.dumping_rate
yield self.env.timeout(dumping_duration)
self.material = 0
def mine_simulation(num_trucks, mining_rate, dumping_rate, sim_time):
env = simpy.Environment()
mining_location = simpy.Resource(env, capacity=1)
dumping_location = simpy.Resource(env, capacity=1)
trucks = [MineTruck(env, capacity=50, mining_rate=mining_rate, dumping_rate=dumping_rate, location=mining_location) for i in range(num_trucks)]
total_mined = 0
total_dumped = 0
for t in range(sim_time):
# request mining location
with mining_location.request() as req:
yield req
# mine material
mined = sum([truck.material for truck in trucks])
total_mined += mined
# release mining location
for truck in trucks:
truck.material = 0
# request dumping location
with dumping_location.request() as req:
yield req
# dump material
dumped = sum([truck.capacity for truck in trucks])
total_dumped += dumped
# move time forward
yield env.timeout(1)
print(f"Total material mined: {total_mined}")
print(f"Total material dumped: {total_dumped}")
env = simpy.Environment()
env.process(mine_simulation(num_trucks=5, mining_rate=5, dumping_rate=5, sim_time=1000))
env.run()
Not a bad effort!
28th Feb update:
I tested the above code, and it didn't work. But it looks like ChatGPT only made one mistake - it called env = simpy.Environment() explicitly in the mine_simulation function rather than passing it in as an argument. Fixed code below:
import simpy
import random
class MineTruck:
def __init__(self, env, capacity, mining_rate, dumping_rate, location):
self.env = env
self.capacity = capacity
self.mining_rate = mining_rate
self.dumping_rate = dumping_rate
self.location = location
self.material = 0
self.action = env.process(self.run())
def run(self):
while True:
# move to mining location
yield self.env.timeout(random.randint(1, 5))
# mine material
mining_duration = self.capacity / self.mining_rate
yield self.env.timeout(mining_duration)
self.material = self.capacity
# move to dumping location
yield self.env.timeout(random.randint(1, 5))
# dump material
dumping_duration = self.material / self.dumping_rate
yield self.env.timeout(dumping_duration)
self.material = 0
def mine_simulation(num_trucks, mining_rate, dumping_rate, sim_time, simpy_env):
env = simpy_env
mining_location = simpy.Resource(env, capacity=1)
dumping_location = simpy.Resource(env, capacity=1)
trucks = [MineTruck(env, capacity=50, mining_rate=mining_rate, dumping_rate=dumping_rate, location=mining_location) for i in range(num_trucks)]
total_mined = 0
total_dumped = 0
for t in range(sim_time):
# request mining location
with mining_location.request() as req:
yield req
# mine material
mined = sum([truck.material for truck in trucks])
total_mined += mined
# release mining location
for truck in trucks:
truck.material = 0
# request dumping location
with dumping_location.request() as req:
yield req
# dump material
dumped = sum([truck.capacity for truck in trucks])
total_dumped += dumped
# move time forward
yield env.timeout(1)
print(f"Total material mined: {total_mined}")
print(f"Total material dumped: {total_dumped}")
env = simpy.Environment()
env.process(mine_simulation(num_trucks=5, mining_rate=5, dumping_rate=5, sim_time=1000, simpy_env=env))
env.run()
Yields the following output:
Total material mined: 15850
Total material dumped: 250000
The approach Chat-GPT took was a little strange. In fact, it doesn't make any sense at all! It took me a little bit of staring to understand exactly what it was doing, for some reason it is requesting the mining and dumping locations while iterating over the "sim_time", when these resource requests should probably live in the mine truck run method. The iteration in the mine simulation function is really used as a measuring tool. Alas, it was impressive that something relatively complex only required one modification to run without error, and a little more time modifying would result in a simulation that does make sense.
Subscribe to my newsletter
Read articles from Harry Munro directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by

Harry Munro
Harry Munro
I design, build and test things virtually before decisions are made in the real world.