Skip to main content

Your First Simulation


The following tutorial page will guide you through the necessary steps to create and run a simple simulation within the Treble SDK. We begin by initialising a new conda environment, installing jupyter to support the provided example notebook, and installing the SDK within the environment in the command line. You may use any virtual environment structure, or none at all (though we strongly recommend using one to avoid dependency conflicts).

conda create -n first-simulation python=3.10.16
conda install jupyter
conda install ipykernel

cd /path/to/install/location
pip install treble_tsdk-<VERSION>-py3-none-any.whl

While you may run the following notebook directly from the environment by calling jupyter notebook from the command line, we recommend using Visual Studio Code and its Python extension for ease-of-use.

The following documentation is presented as Python code running inside a Jupyter Notebook. To run it yourself you can copy/type each individual cell or directly download the full notebook, including all required files.
info

After opening the notebook in VSCode, press the "Select Kernel" button and choose "Python Environments". Choose the python environment where the SDK is installed - in our case, this will be the environment first-simulation.

Necessary Components


To successfully set up and run a simulation in the SDK, you will need to follow these general steps

  1. Import the SDK and provide a set of valid credentials, and import any additional packages you may desire (e.g., matplotlib, scipy, etc.).
  2. Create or retrieve a project.
  3. Create or retrieve a (valid) model.
  4. Assign materials to all the layers of the model.
  5. Define a (valid) source or set of sources.
  6. Define a (valid) receiver or set of receivers.
  7. Create a simulation definition with the model, materials, sources, and receivers.
  8. Add the simulation to the project.
  9. Run the simulation.
  10. Download and work with the results.

Initialising the Notebook


In the first cell of the example notebook, we see the initialisation calls required to interact with the SDK and a few optional packages. The first time you import the SDK within an environment will take a few extra seconds - don't worry! this step will be much faster the next time you run the notebook in the environment.

import random

# The tsdk_namespace provides convenient access to most SDK classes/variables.
from treble_tsdk.tsdk import TSDK, TSDKCredentials
# Here we import the tsdk_namespace as 'treble'.
from treble_tsdk import treble
# The display_data module provides functions that can be used to display many SDK datastructures as trees or tables.
# Here we import the display_data module as 'dd'.
from treble_tsdk import display_data as dd

# at this step, you will need your credentials
tsdk = TSDK(TSDKCredentials.from_file('path/to/credentials'))
# if you have configured the path to your credentials automatically, you may simply call
#tsdk = TSDK()

# check for updates
import treble_tsdk
print(f'Current version: {treble_tsdk.__version__}')

Create a project

Projects are used to organise simulations in the SDK, and are available to all users within your organisation. Since you may have access to other's projects within your organisation, let's check to see what projects you have created yourself. If this is your first time interacting with the SDK, the first table generated by this cell will be blank, and the second table will show your new project.

# Check what projects were created by your credentials
projects = tsdk.list_my_projects()
dd.as_table(projects)

# create a project with a given name, and check that it has been added to your list of projects
tsdk.create_project("My Unique Project Name")
projects = tsdk.list_my_projects()
# now that you have created a project, this table will show the My Unique Project Name project
dd.as_table(projects)

# retrieve the project by name: if you have previously create the project My Unique Project Name, you can skip directly to get_by_name.
project = tsdk.get_by_name("My Unique Project Name")
info

The SDK requires that every project has a unique name - if you try to create the project "My Unique Project Name", and someone in your organisation has already created a project with that name, you will be asked to select a different name.

Create a model

There are many ways to create models within the SDK, but for this tutorial, we will stick with the simplest possible model: a shoebox room. The next code block creates a simple shoebox model and adds a few pieces of furniture to it. It then adds the room and its contents to the project, allowing you to retrieve the precise model in the future and use it in simulations.

# Create a shoebox room
room = treble.GeometryDefinitionGenerator.create_shoebox_room(
width_x=3,
depth_y=6,
height_z=2,
join_wall_layers=True,
)

# get furniture components
sofa = tsdk.geometry_component_library.query(group="sofa")[1]
chair = tsdk.geometry_component_library.query(group="chair")[1]
table = tsdk.geometry_component_library.query(group="table")[0]

# define positions for the furniture and add the objects to the room
sofa_pos = treble.Vector3d(1.5, 5.5, 0)
room.add_geometry_component("my_comfy_sofa", sofa, treble.Transform3d(sofa_pos, treble.Rotation(0, 0, 0)))

chair1_pos = sofa_pos + treble.Vector3d(-1, -3.2, 0)
chair2_pos = sofa_pos + treble.Vector3d(0, -3.5, 0)
chair3_pos = sofa_pos + treble.Vector3d(0, -2.7, 0)

room.add_geometry_component("my_chair1", chair, treble.Transform3d(chair1_pos, treble.Rotation(-135, 0, 0)))
room.add_geometry_component("my_chair2", chair, treble.Transform3d(chair2_pos, treble.Rotation(-90, 0, 0)))
room.add_geometry_component("my_chair3", chair, treble.Transform3d(chair3_pos, treble.Rotation(60, 0, 0)))

table_pos = treble.Vector3d(1, 4, 0)
room.add_geometry_component("my_table", table, treble.Transform3d(table_pos, treble.Rotation(0, 0, 0)))

# plot the room
room.plot()

# add the room to the project
model = project.add_model("example room", room)

#check that the model has been added to the project
dd.display(project.get_models())

Assign materials

The fastest way to create a material assignment for our model would be to use the next code block, which iterates through all layers in the model and randomly chooses one of the default materials included in the Treble Material Library. This approach, while fast, also may result in implausible materials associated with the model's layers, e.g., assigning an acoustic panel to a table object.

# Get a list with all materials associated with the organization
all_materials = tsdk.material_library.get()

# Remove materials that are user-generated
database_materials = [material for material in all_materials if material["organizationId"] == None]

# Grab a random material for every layer
material_assignment = [treble.MaterialAssignment(layer, random.choice(database_materials)) for layer in model.layer_names]

# print the random assignment
for l in material_assignment:
print(l.material_name)

Instead, we will proceed to the next code block, which creates a loose dictionary that maps certain types of materials to the layers we know exist in the model. The first part of this code block prints the layer names for inspection - if you have changed the furniture objects at a previous stage, you will need to update the layer names with the variable layer_to_search to reflect the new model properties.

# first check what the layer names are
for layer in model.layer_names:
print(layer)

# Create a dictionary associating layer names with acceptable material names
layer_to_search = {
"shoebox_walls": "gypsum", #any gypsum material is acceptable
"shoebox_floor": "carpet", #any carpeting is acceptable
"shoebox_ceiling": "gypsum",
"Furniture/Couch C": "85", #grab a flat 85% absorption for the couch
"Furniture/Dining table": "wood", # any wood or wooden furnitures is acceptable
"Furniture/Chair A": "wood",
}

# create an empty material assignment before the loop
material_assignment = []

# grab a random material from the default materials that comes closer to a realistic assignment
for layer in model.layer_names:
if layer in layer_to_search:
search_string = layer_to_search[layer]
matches = [
m for m in database_materials
if search_string.lower() in m.name.lower()
]
if matches:
material_assignment.append(
treble.MaterialAssignment(layer, random.choice(matches))
)

# show the material assignment
dd.display(material_assignment)

Create sources and receivers

For this simple simulation, we will create two omni sources and two mono receivers within the model. The SDK contains further options for source and receiver types, which you may explore in more depth as you become familiar with the software.

# create a list of sources and receivers
source = [treble.Source(0.25,0.5,1.5,treble.SourceType.omni,"source_1"), treble.Source(2.25,2,1.3,treble.SourceType.omni,"source_2")]
receivers = [treble.Receiver(1,1,1.2,treble.ReceiverType.mono,"receiver_1"), treble.Receiver(1,5,1.2,treble.ReceiverType.mono,"receiver_2")]

Create the simulation definition

Now that you have created a model, a material assignment, and sources and receivers, you may finally define your simulation. A simulation definition allows you to specify what type of simulation you wish to run - GA, DG, or a hybrid of the two. In this code block, we will create a GA simulation which will stop running when the energy from the sources decays by 40 dB from their initial levels.

sim_def = treble.SimulationDefinition(
name="Simulation_1", # unique name of the simulation
simulation_type=treble.SimulationType.ga, # the type of simulation
model=model, # the model we created in an earlier step
energy_decay_threshold=40, # simulation termination criteria - the simulation stops running after -40 dB of energy decay
receiver_list=receivers,
source_list=source,
material_assignment=material_assignment
)

# double check that all the receivers and sources fall within the room
sim_def.remove_invalid_receivers()
sim_def.remove_invalid_sources()

# plot the simulation before adding it to the project
sim_def.plot()

# create a simulation from the definition
simulation = project.add_simulation(sim_def)

Run the simulation

Running the simulation is accomplished with a single call. For convenience, you may track the progress of the simulation using the as_live_progress method, which will automatically update the details of the calculation.

simulation.start()
simulation.as_live_progress()

Congratulations! You have run your first simulation in the Treble SDK!

Download the Results

To see and interact with your results, you must download them from the cloud to a local directory. In this case, we will download the results to a subdirectory named after the simulation. Once you retrieve the results object, you may begin to interact with it directly, either plotting results globally or exploring things with more granularity.

results_object = simulation.download_results(f'results/{simulation.name}')

# begin to explore the results
results_object.plot()

# isolate the data from a single impulse response
this_result = results_object.get_mono_ir(source=simulation.sources[0],receiver=simulation.receivers[1])
this_ir = this_result.data
this_tvec = this_result.time

this_result.plot()