Reducing Memory Size of Objective Jacobian Calculation
During optimization, one of the most memory-intensive steps is the calculation of the Jacobian
of the cost function. This memory cost comes from attempting to calculate the entire Jacobian
matrix in one vectorized operation. However, this can be tuned between high memory usage but quick (default)
and low memory usage but slower with the jac_chunk_size keyword argument. By default, where this matters
is when creating the overall ObjectiveFunction to be used in the optimization (where by default deriv_mode="batched"). The Jacobian is a
matrix of shape [obj.dim_f x obj.dim_x], and the calculation of the Jacobian is vectorized over
the columns (the obj.dim_x dimension), where obj is the ObjectiveFunction object. Passing in the jac_chunk_size attribute allows one to split up
the vectorized computation into chunks of jac_chunk_size columns at a time, allowing one to compute the Jacobian
in a slightly slower, but more memory-efficient manner. The memory usage of the Jacobian calculation is
memory usage = m0 + m1*jac_chunk_size: the smaller the chunk size, the less memory the Jacobian calculation
will require (with some baseline memory usage). The time to compute the Jacobian is roughly t=t0 +t1/jac_chunk_size
with some baseline time, so the larger the jac_chunk_size, the faster the calculation takes,
at the cost of requiring more memory. A jac_chunk_size of 1 corresponds to the least memory intensive,
but slowest method of calculating the Jacobian. If jac_chunk_size="auto", it will default to a size
that should make the calculation fit in memory based on a heuristic estimate of the Jacobian memory usage.
If deriv_mode="blocked" is specified when the ObjectiveFunction is created, then the Jacobian will
be calculated individually for each of the sub-objectives inside of the ObjectiveFunction, and in that case
the jac_chunk_size of the individual _Objective objects inside of the ObjectiveFunction will be used.
For example, if obj1 = QuasisymmetryTripleProduct(eq, jac_chunk_size=100), obj2 = MeanCurvature(eq, jac_chunk_size=2000)
and obj = ObjectiveFunction((obj1, obj2), deriv_mode="blocked"), then the Jacobian will be calculated with a
jac_chunk_size=100 for the quasisymmetry part and a jac_chunk_size=2000 for the curvature part, then the full Jacobian
will be formed as a blocked matrix with the individual Jacobians of these two objectives.