I'm trying to optimize a flow distributor in a tank such that the velocity and temperature distribution across any cross-section is relatively uniform. There are many parameters I can adjust to the maximum cross-sectional uniformity, such as the number of inlet pipes, their position, orientation, and direction. I know that I can create a number of different geometries and test each one individually, but this is very time consuming. I'd like to be able to write a program that can iteratively test several cases at once (in parallel), and adaptively choose a new set of geometries to test based upon the previous results. How can I best do this?
What you want to do is shape optimization using gradient based methods. It essentially means that you need to calculate the gradient of the objective function w.r.t. to your model parameters.
For a small number of parameters you can use FD but for large number of parameters you need to look into adjoint methods. If you are using a commercial code or someone else's code that cannot solve the adjoint equations then FD is your only option.
Look into basic basic shape optimization books.
Edit: For structural FE problems you can check out the book by Choi and Kim I and II
If you parameterize your geometry construction part appropriately, this is a problem of black box optimization with mixed discrete and continuous parameters.
DAKOTA http://dakota.sandia.gov/ and NOMAD http://www.gerad.ca/NOMAD/Project/Home.html are two useful packages that allow you to choose automatically best parameter selections. (DAKOTA has better application support, but NOMAD probably has the better optimizers.)
To vary the geometry, introduce a discrete or continuous parameter for each control with which you want to influence the geometry, and automatize the construction of the geometry from the collection of controls. Note that derivative-free methods are quite slow in high dimensions, so keep the number of parameters reasonably small.
After finishing exploring the space with one of the above packages, you may refine the analysis by doing a more accurate optimization in which all discrete parameters and all continuous parameters are fixed for which you can't get an analytic derivative. But you may increase the number of continuous shape parameters with respect to which you can compute analytic derivatives, as a gradient-based optimizer (such as IPOPT https://projects.coin-or.org/Ipopt ) can efficiently handle far bigger problems.
If you don't know how to get the derivative but the dependence is smooth, you may consider using an automatic differentiation program, or coding your continuous problem in AMPL, in which case the solver interface will take care of the derivatives.
For the basics on shape optimization see, e.g., Haftka, R.T. and Grandhi, R.V., tructural shape optimization--A survey, Computer Methods in Applied Mechanics and Engineering 57 (1986), 91-106. (Trust the description about the modeling; but don't use the solvers they recommend, as optimization technology has much improved since that time.)
As far as parameterizing the geometry is in question (as Geoff pointed out a no trivial one) - I can sincerely recommend Brenda Kulfan - Universal Parametric Geometry Representation Method, J. Aircraft, Vol.45, No.1,2008.
Described approach is applicable in aerodynamic optimization of aircrafts.
There is also adjoint space optimization, which seems to be a lot faster than standard parametric optimization in CFD. Recently it has had a large increase in popularity within the CFD community in general, and in OpenFOAM especially. We are organizing a workshop on OpenFOAM currently, and we received a lot of abstract submissions regarding this method. If you are interested, check this out, for other info, just google "adjoint space shape optimization in CFD".
If you could use OpenFOAM, there is a Python based library that is used for just such thing, to manipulate large amount of cases and change their parameters called PyFoam. For a simple geometry, you could define a mesh as a simple blockMesh and iterate over whatever you like. For a simple case, this is a question of writing a few loops in Python. Here's how the script looks like if you change the "inlet" boundary condition velocity. Simple mesh geometry changes would be a few more lines of code...