In this notebook we create a simple SEA model of two rooms divided by a concrete wall.
We start by importing some of the modules that are needed.
In [1]:
import numpy as np
import pandas as pd
pd.set_option('float_format', '{:.2e}'.format)
import matplotlib
%matplotlib inline
To create a SEA model we begin by creating an instance of System.
In [2]:
from seapy import System
from acoustics.signal import OctaveBand
f = OctaveBand(fstart=20.0, fstop=4000.0, fraction=1)
In [3]:
system1 = System(f)
We are only interested in a limited frequency range, e.g. the third octave bands ranging from 1000 to 4000 Hz.
The rooms are filled with air, so we add air as material.
In [4]:
air = system1.add_material('air',
'MaterialGas',
density = 1.296,
temperature = 293.0,
bulk = 1.01e5,
loss_factor=0.05)
In [5]:
concrete = system1.add_material('concrete',
'MaterialSolid',
young=3.0e10,
poisson=0.15,
density=2.3e3,
loss_factor=0.02)
We don't know the shear modulus of concrete, so let's calculate it. With the function modulus we can calculate for an isotropic material any elastic modulus given two other ones.
In [6]:
from seapy.materials.materialsolid import modulus
concrete.shear = modulus('shear', young=3.0e10, poisson=0.15)
Just to be sure, we can list the properties of the concrete.
In [7]:
concrete.info(['density',
'poisson',
'young',
'shear',])
Out[7]:
Now we add the two rooms.
In [8]:
room1 = system1.add_component('room1',
'Component3DAcoustical',
material='air',
length=4.0,
height=2.5,
width=5.0)
In [9]:
room2 = system1.add_component('room2',
'Component3DAcoustical',
material='air',
length=5.0,
height=2.5,
width=5.0)
Given the material type and the volume we can for example calculate the mass of the air in the room
In [10]:
room1.mass
Out[10]:
or plot the modal density of the subsystem representing longitudinal waves
In [11]:
fig = room1.subsystem_long.plot("modal_density", yscale='log')
We now add the concrete wall.
In [12]:
wall = system1.add_component('wall',
'Component2DPlate',
material='concrete',
length=3.0,
width=2.5,
height=0.05)
Let's have a look at the modal densities of the subsystems.
In [13]:
system1.info(system1.subsystems, 'modal_density')
Out[13]:
The modal density of the subsystem representing bending waves in the wall seems to remain constant.
It's also possible to inspect objects further. For example, as was shown with the mass of the room, it is possible to request e.g. multiple parameters.
In [14]:
wall.subsystem_bend.info(['soundspeed_group',
'soundspeed_phase',
'modal_density',
'average_frequency_spacing',
'power_input',
'dlf',
'tlf',])
Out[14]:
Shown is now a table, but what is returned is in fact a pandas DataFrame. Pandas is a data analysis toolkit and offers powerful tools to analyse data and to export data to e.g. spreadsheet formats like Excel.
The rooms and the wall form a junction and connect along a surface.
In [15]:
junction1 = system1.add_junction('junction1', 'Junction', shape='Surface', components=['room1',
'room2',
'wall'])
Now, when we call junction1.update_couplings it tries to determine all the couplings between the subsystems of the components that were added.
In [16]:
junction1.update_couplings()
We can now for example see the coupling loss factors of all the couplings that were added.
In [17]:
system1.info(system1.couplings, 'clf')
Out[17]:
Now that both the coupling loss factors and damping loss factors are known we can also list the total loss factor
In [18]:
system1.info(system1.subsystems, 'tlf')
Out[18]:
The coupling loss factor of the coupling between the rooms is based on the non-resonant transmission coefficient.
In [19]:
system1.get_object('room1_SubsystemLong_room2_SubsystemLong').info(['tau', 'sound_reduction_index'])
Out[19]:
In [20]:
system1.get_object('wall_SubsystemBend_room1_SubsystemLong').info(['critical_frequency'])
Out[20]:
We have defined the subsystems and couplings. What's left is to add an excitation to the system.
In [21]:
excitation1 = room1.subsystem_long.add_excitation('excitation1',
'ExcitationPointVolume',
velocity=0.001,
radius=0.05)
The input power $P$ depends on the volume velocity $U$ of the source and the real part of the radiation impedance, i.e. the radiation resistance $R$.
In [22]:
excitation1.info(['resistance'])
Out[22]:
The resistance increases with frequency and therefore the radiated power increases similary.
In [23]:
fig = excitation1.plot('power_level')
Now we can solve the system.
In [24]:
system1.solve()
Out[24]:
We can have a look at the modal energy
In [25]:
system1.info(system1.subsystems, 'modal_energy')
Out[25]:
but those values are generally hard to interpret. Instead, we could just request the sound pressure levels in the rooms
In [26]:
system1.info(['room1', 'room2'], 'pressure_level')
Out[26]:
or plot them.
In [27]:
fig = system1.plot(['room1', 'room2'], 'pressure_level')
Let's consider the sound pressure level difference between the two rooms.
In [28]:
(room1.info(['pressure_level']) - room2.info(['pressure_level']))
Out[28]:
In [29]:
fig = system1.get_object('room1_SubsystemLong_room2_SubsystemLong').plot('sound_reduction_index')
Obviously, we can also look at the modal energies
In [30]:
system1.info(system1.subsystems, 'modal_energy')
Out[30]:
or see the level contributions of the individual subsystems.
In [31]:
system1.info(system1.subsystems, 'velocity_level')
Out[31]:
In [32]:
system1.info(system1.subsystems, 'pressure_level')
Out[32]:
All the objects in SeaPy remember to which other objects they're connected. For example, we can list the subsystems in a component.
In [33]:
for obj in wall.linked_subsystems:
print(obj.name)
As soon as a model gets a bit bigger it can be hard to track which objects are connected. One way to help with keeping an overview is by drawing graphs.
In [34]:
import networkx as nx
The following graph shows the relation between components and subsystems.
In [35]:
G = system1.path_analysis.graph(['components', 'subsystems'])
nx.draw_networkx(G)
We can also show for example subsystems and couplings.
In [36]:
G = system1.path_analysis.graph(['subsystems', 'couplings'])
fig = nx.draw_networkx(G)
In [37]:
from seapy.tools import graph_couplings
G = graph_couplings(system1)
fig = nx.draw_networkx(G)
By creating graphs of subsystems and couplings it is also straightforward to check whether subsystems are in anyway connected
In [38]:
system1.path_analysis.has_path('room1_SubsystemLong', 'room2_SubsystemLong')
Out[38]:
and to determine the possible paths between any two subsystems.
In [39]:
for path in system1.path_analysis.paths('room1_SubsystemLong', 'room2_SubsystemLong'):
print(path)
We can also calculate the level difference due to a transmission path.
In [40]:
for path in system1.path_analysis.paths('room1_SubsystemLong', 'room2_SubsystemLong'):
print(path.level_difference)
In [41]:
list(system1.path_analysis.paths('room1_SubsystemLong', 'room2_SubsystemLong'))[0].level_difference
Out[41]:
SEA models can be saved as YAML.
In [42]:
system1.save("model.yaml")
YAML is a human-readable file format. Models can be implemented or edited in the YAML file if desired.
In [43]:
!head -n 20 model.yaml
Loading is done using the load method.
In [44]:
system2 = System.load("model.yaml")
To verify whether the models are similar we check the modal energy.
In [45]:
system1.info(system1.subsystems, 'modal_energy')
Out[45]:
In [46]:
system2.info(system2.subsystems, 'modal_energy')
Out[46]:
That looks correctly. To be really sure we just calculate the modal energies again in the second model, to verify that other parameters have also been restored.
In [47]:
system2.solve()
system2.info(system2.subsystems, 'modal_energy')
Out[47]:
Same results.
In [48]:
from IPython.display import IFrame
IFrame("https://seapy.readthedocs.io/en/latest/", width=800, height=600)
Out[48]: