Running simulations through the cloud#

This notebook is a tutorial of the API used for submitting simulations to our servers.

[1]:
import tidy3d as td
import tidy3d.web as web

# set logging level to ERROR because we'll only running simulations to demonstrate the web API, we dont care about the results.
td.config.logging_level = "ERROR"

Setup#

Let’s set up a simple simulation to get started.

[2]:
# whether to print output in web functions, note: if False (default) they will all run silently
verbose = True

# set up parameters of simulation
dl = 0.05
pml = td.PML()
sim_size = [4, 4, 4]
freq0 = 3e14
fwidth = 1e13
run_time = 1 / fwidth

# create structure
dielectric = td.Medium.from_nk(n=2, k=0, freq=freq0)
square = td.Structure(
    geometry=td.Box(center=[0, 0, 0], size=[1.5, 1.5, 1.5]), medium=dielectric
)

# create source
source = td.UniformCurrentSource(
    center=(-1.5, 0, 0),
    size=(0, 0.4, 0.4),
    source_time=td.GaussianPulse(freq0=freq0, fwidth=fwidth),
    polarization="Ex",
)

# create monitor
monitor = td.FieldMonitor(
    fields=["Ex", "Ey", "Ez"],
    center=(0, 0, 0),
    size=(td.inf, td.inf, 0),
    freqs=[freq0],
    name="field",
)

# Initialize simulation
sim = td.Simulation(
    size=sim_size,
    grid_spec=td.GridSpec.uniform(dl),
    structures=[square],
    sources=[source],
    monitors=[monitor],
    run_time=run_time,
    boundary_spec=td.BoundarySpec.all_sides(boundary=pml),
)

Running simulation manually#

For the most control, you can run the simulation through the Tidy3D web API. Each simulation running on the server is identified by a task_id, which must be specified in the web API. Let’s walk through submitting a simulation this way.

[3]:
# upload the simulation to our servers
task_id = web.upload(sim, task_name="webAPI", verbose=verbose)

# start the simulation running
web.start(task_id)

# monitor the simulation, dont move on to next line until completed.
web.monitor(task_id, verbose=verbose)

# download the results and load into a simulation data object for plotting, post processing etc.
sim_data = web.load(task_id, path="data/sim.hdf5", verbose=verbose)

[19:01:55] Created task 'webAPI' with task_id                      webapi.py:188
           'fdve-5720b5c2-99b0-4c3a-a6f1-692c41001bf7v1'.                       
[19:01:56] status = queued                                         webapi.py:361
[19:02:05] status = preprocess                                     webapi.py:355
[19:02:09] Maximum FlexCredit cost: 0.025. Use                     webapi.py:341
           'web.real_cost(task_id)' to get the billed FlexCredit                
           cost after a simulation run.                                         
           starting up solver                                      webapi.py:377
           running solver                                          webapi.py:386
           To cancel the simulation, use 'web.abort(task_id)' or   webapi.py:387
           'web.delete(task_id)' or abort/delete the task in the                
           web UI. Terminating the Python script will not stop the              
           job running on the cloud.                                            
[19:02:15] status = postprocess                                    webapi.py:419
[19:02:19] status = success                                        webapi.py:426
[19:02:20] loading SimulationData from data/sim.hdf5               webapi.py:590

While we broke down each of the individual steps above, one can also perform the entire process in one line by calling the web.run() function as follows.

sim_data = web.run(sim, task_name='webAPI', path='data/sim.hdf5')

(We won’t run it again in this notebook to save time).

Sometimes this is more convenient, but other times it can be helpful to have the steps broken down one by one, for example if the simulation is long, you may want to web.start and then exit the session and load the results at a later time using web.load.

Job Container#

The web.Job interface provides a more convenient way to manage single simuations, mainly because it eliminates the need for keeping track of the task_id and original Simulation.

We can get the cost estimate of running the task before actually running it. This prevents us from accidentally running large jobs that we set up by mistake. The estimated cost is the maximum cost corresponding to running all the time steps.

[4]:
# initializes job, puts task on server (but doesnt run it)
job = web.Job(simulation=sim, task_name="job", verbose=verbose)

# estimate the maximum cost
estimated_cost = web.estimate_cost(job.task_id)
[19:02:21] Created task 'job' with task_id                         webapi.py:188
           'fdve-2243db88-e53e-4332-b9e6-74ef3b4c9830v1'.                       
The estimated maximum cost is 0.025 Flex Credits.

While Job has methods with names identical to the web API functions above, which give some more granular control, it is often most convenient to call .run() so we will do that now.

[5]:
# start job, monitor, and load results
sim_data = job.run(path="data/sim.hdf5")
[19:02:27] status = queued                                         webapi.py:361
[19:02:45] status = preprocess                                     webapi.py:355
[19:02:49] Maximum FlexCredit cost: 0.025. Use                     webapi.py:341
           'web.real_cost(task_id)' to get the billed FlexCredit                
           cost after a simulation run.                                         
           starting up solver                                      webapi.py:377
           running solver                                          webapi.py:386
           To cancel the simulation, use 'web.abort(task_id)' or   webapi.py:387
           'web.delete(task_id)' or abort/delete the task in the                
           web UI. Terminating the Python script will not stop the              
           job running on the cloud.                                            
[19:02:55] status = postprocess                                    webapi.py:419
[19:02:59] status = success                                        webapi.py:426
[19:03:00] loading SimulationData from data/sim.hdf5               webapi.py:590

Another convenient thing about Job objects is that they can be saved and loaded just like other Tidy3d components.

This is convenient if you want to save and load up the results of a job that has already finished, without needing to know the task_id.

[6]:
# saves the job metadata to a single file
job.to_file("data/job.json")

# can exit session, break here, or continue in new session.

# load the job metadata from file
job_loaded = web.Job.from_file("data/job.json")

# download the data from the server and load it into a SimulationData object.
sim_data = job_loaded.load(path="data/sim.hdf5")

[19:03:01] loading SimulationData from data/sim.hdf5               webapi.py:590

Batch Processing#

Commonly one needs to submit a batch of Simulations. One way to approach this using the web API is to upload, start, monitor, and load a series of tasks one by one, but this is clumsy and you can lose your data if the session gets interrupted.

A better way is to use the built-in Batch object. The batch object is like a Job, but stores task metadata for a series of simulations.

[7]:
# make a dictionary of  {task name : simulation} for demonstration
sims = {f"sim_{i}": sim for i in range(3)}

# initialize a batch and run them all
batch = web.Batch(simulations=sims, verbose=verbose)

# run the batch and store all of the data in the `data/` dir.
batch_results = batch.run(path_dir="data")

           Created task 'sim_0' with task_id                       webapi.py:188
           'fdve-50d7405e-8036-4672-b481-0d533ef532e0v1'.                       
           Created task 'sim_1' with task_id                       webapi.py:188
           'fdve-3499351d-d144-4ae2-8414-3676713cfe96v1'.                       
[19:03:02] Created task 'sim_2' with task_id                       webapi.py:188
           'fdve-d91b0bed-e70a-49c4-b959-85fbcaeef1c5v1'.                       
[19:03:03] Started working on Batch.                            container.py:475
[19:03:17] Maximum FlexCredit cost: 0.075 for the whole batch.  container.py:479
           Use 'Batch.real_cost()' to get the billed FlexCredit                 
           cost after the Batch has completed.                                  
[19:03:35] Batch complete.                                      container.py:522

When the batch is completed, the output is not a SimulationData but rather a BatchData. The data within this BatchData object can either be indexed directly batch_results[task_name] or can be looped through batch_results.items() to get the SimulationData for each task.

This was chosen to reduce the memory strain from loading all SimulationData objects at once.

Alternatively, the batch can be looped through (several times) using the .items() method, similar to a dictionary.

[8]:
# grab the sum of intensities in the simulation one by one (to save memory)
intensities = {}
for task_name, sim_data in batch_results.items():
    intensity = sim_data.get_intensity("field").sel(f=freq0)
    sum_intensity = float(intensity.sum(("x", "y")).values)
    intensities[task_name] = sum_intensity

print(intensities)

[19:03:37] loading SimulationData from                             webapi.py:590
           data/fdve-50d7405e-8036-4672-b481-0d533ef532e0v1.hdf5                
/tmp/ipykernel_20147/711851585.py:5: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
  sum_intensity = float(intensity.sum(("x", "y")).values)
           loading SimulationData from                             webapi.py:590
           data/fdve-3499351d-d144-4ae2-8414-3676713cfe96v1.hdf5                
/tmp/ipykernel_20147/711851585.py:5: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
  sum_intensity = float(intensity.sum(("x", "y")).values)
[19:03:38] loading SimulationData from                             webapi.py:590
           data/fdve-d91b0bed-e70a-49c4-b959-85fbcaeef1c5v1.hdf5                
{'sim_0': 6377911.0, 'sim_1': 6377911.0, 'sim_2': 6377911.0}
/tmp/ipykernel_20147/711851585.py:5: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
  sum_intensity = float(intensity.sum(("x", "y")).values)

Asynchronous Batching#

Finally, one can make use of the asyncio package to perform asynchronous processing of several simulations.

For this purpose, a web.run_async function is provided, which works like the regular web.run but accepts a dictionary of simulations.

Here is the previous example repeated using this feature.

[9]:
batch_results = web.run_async(simulations=sims, verbose=verbose)

           Created task 'sim_0' with task_id                       webapi.py:188
           'fdve-9bf7553e-55a5-4eb2-b1d5-928be47720b8v1'.                       
[19:03:39] Created task 'sim_1' with task_id                       webapi.py:188
           'fdve-5f3b0fba-94d3-437e-9fdd-bf32327952d0v1'.                       
           Created task 'sim_2' with task_id                       webapi.py:188
           'fdve-ecd95f55-28e6-421e-9cb1-24e979c4e73av1'.                       
[19:03:40] Started working on Batch.                            container.py:475
[19:03:54] Maximum FlexCredit cost: 0.075 for the whole batch.  container.py:479
           Use 'Batch.real_cost()' to get the billed FlexCredit                 
           cost after the Batch has completed.                                  
[19:04:17] Batch complete.                                      container.py:522
[10]:
# grab the sum of intensities in the simulation one by one (to save memory)
intensities = {}
for task_name, sim_data in batch_results.items():
    intensity = sim_data.get_intensity("field").sel(f=freq0)
    sum_intensity = float(intensity.sum(("x", "y")).values)
    intensities[task_name] = sum_intensity

print(intensities)

[19:04:18] loading SimulationData from                             webapi.py:590
           ./fdve-9bf7553e-55a5-4eb2-b1d5-928be47720b8v1.hdf5                   
/tmp/ipykernel_20147/711851585.py:5: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
  sum_intensity = float(intensity.sum(("x", "y")).values)
[19:04:19] loading SimulationData from                             webapi.py:590
           ./fdve-5f3b0fba-94d3-437e-9fdd-bf32327952d0v1.hdf5                   
/tmp/ipykernel_20147/711851585.py:5: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
  sum_intensity = float(intensity.sum(("x", "y")).values)
           loading SimulationData from                             webapi.py:590
           ./fdve-ecd95f55-28e6-421e-9cb1-24e979c4e73av1.hdf5                   
{'sim_0': 6377911.0, 'sim_1': 6377911.0, 'sim_2': 6377911.0}
/tmp/ipykernel_20147/711851585.py:5: DeprecationWarning: Conversion of an array with ndim > 0 to a scalar is deprecated, and will error in future. Ensure you extract a single element from your array before performing this operation. (Deprecated NumPy 1.25.)
  sum_intensity = float(intensity.sum(("x", "y")).values)

After going through this tutorial, you have learned how to run simulations with Tidy3D via the web API. If you are new to Tidy3D or the finite-difference time-domain (FDTD) method, we highly recommend going through our FDTD101 tutorials and example library before starting your own simulation adventure.

[ ]: