Closed momchil-flex closed 6 months ago
we’d need to come up with an algorithm for setting the run time based on various properties of the RunTimeSpec / Simulation. Maybe we can just work that detail out before breaking ground on any feature?
as for the API i think it would make sense to change
Simulation.run_time : Union[float, RunTimeSpec]
and then add a @property
Simulation._run_time
that computes run time based on the Simulation.run_time
+ other information (such asSimulation.size
, Simulation.medium
, Simulation.sources
).
then it should be a matter of replacing the backend to use _run_time
instead of run_time
?
Alternatively, instead of either float
or RunTimeSpec
, we could add Simulation.run_time_spec
and do some kind of handling for if both run_time
and run_time_spec
are defined.
Yep, I think the first API option you describe sounds good.
Regarding the formula, it should be something related to what we discussed in the adjoint issue. Something like
source_decay_time = C / source_time.fwidth
propagation_time = propagation_length / C_0 / highest_material_index_at_source_time_freq0
propagation_time *= highest_q
run_time = source_decay_time + propagation_time
I think @lucas-flexcompute mentioned he's doing something similar, could you comment any feedback or tips?
Yes, I have something similar, but I default the highest Q to 100 and leave the refractive index at 1. The propagation length I get from the largest bounding box dimension.
fwidth = max(fmax - fmin, 0.1 * frequencies.mean())
source_time = 10 / fwidth
q_factor = 100
propagation_lenght = (bounds[1] - bounds[0]).max()
propagation_time = propagation_lenght / C_0 * q_factor
run_time = source_time + propagation_time
Thanks @lucas-flexcompute and this works pretty well?
I'm wondering about the idea behind fwidth = max(fmax - fmin, 0.1 * frequencies.mean())
, are the fmax
and fmin
from the monitors or the sources? what about frequencies
?
Working towards some kind of spec that we can capture the free parameters of this formula with (when added to a Simulation). Right now it seems the spec would mainly contain
class RunTimeSpec(Tidy3DBaseModel):
q_factor : pd.PositiveFloat = 100
fwidth_factor1(?) : 0.1 (?)
fwidth_factor2(?) : 10 (?)
and then in Simulation.run_time
, we'd do something like
@property
def _run_time(self):
run_time_spec = self.run_time_spec
# either this
fwidth = max(fmax - fmin, run_time_spec.fwidth_factor * frequencies.mean())
source_time = run_time_spec.fwidth_factor2 / fwidth
# or this
source_time = self.run_time_of_longest_source(...)
propagation_lenght = (self.bounds[1] - bounds[0]).max()
propagation_time = propagation_lenght / C_0 * run_time_spec.q_factor
return source_time + propagation_time
Do you have any thoughts on whether we should use your formula for source time or try to grab it from the Simulation sources? (note, I guess we can't reliably use the source_time.fwidth
, we might also need to take offset
into account.)
Those values and formulas for fwidth
were taken from the smatrix plugin, to match the Gaussian pulse used there. Those seem to be reasonable defaults for common integrated devices. In the general case, I think grabbing from the source would be a better idea. In fact, each time source could provide it's own implementation of source.end_time()
to be used here.
The estimation usually works with a good margin (shutoff around 30% of the total run time) for passive non-resonant components. It does not work for high Q resonators, but that's expected.
I see, so perhaps with that in mind, this could be a modification of the _run_time
property?
@property
def _run_time(self):
"""The run time of the simulation (if `run_time` is not supplied)`."""
run_time_spec = self.run_time_spec
# contribution from the time of of the source pulses
source_times = [src.source_time.end_time() for src in self.sources]
if not source_times:
raise ...
source_time_max = np.max(source_times)
source_time = run_time_spec.source_factor * source_time_max
# contribution from field decay out of the simulation
propagation_length = np.max((np.array(self.bounds[1]) - np.array(self.bounds[0])))
maximum_n = self.maximum_refractive_index
propagation_time = run_time_spec.q_factor * maximum_n * propagation_length / C_0
return source_time + propagation_time
Note:
run_time_spec.source_factor
that roughly controls how much we want to wait for the source to decay.q_factor
)?It's looking good to me. In my opinion, if it's easy to get the maximum index, it's better to use it. I don't because I don't want photonforge to depend on any internals of Tidy3D that are not strictly required.
Cool. Regarding maximum index, I'm just slightly worried that including that AND Q factor might lead to some overestimation. I assume the two things are kind of linked so not sure. Also would a default Q of 100 be overkill?
I don't think 100 is overkill. That's equivalent to a very bad resonator, which I chose because it helps if there are unintended resonances in the structure (think of an MMI with multiple back-reflections or small cavities that appear in adjoint optimizations). I wouldn't consider anything below 500 a real resonator in optics (RF/MW are another story).
Is your feature request related to a problem? Please describe. It is not always clear how to set a good simulation
run_time
.We could do something like this: allow
run_time
to accept a float or a new objectRunTimeSpec
, with something likeFor e.g. PIC simulations, the user can do something like
run_time=RunTimeSpec()
and the default values should just work for most simulations. For other things (e.g. metalens), it still gives them an option to get a good run time estimate with much less effort than currently.