Part of interaction between codes in AMUSE is based on exchanging data between the community codes or exchanging data between these codes and AMUSE. As you might have noticed in the pervious tutorial topic, every code provides access to particle collections or grids. The data of these collections or grids live inside the code, while the data of collections created in the script live inside the python process.
Background: All data storage of particle collections (or grids) is implemented by different storage classes. AMUSE supports storage classes that simply store the data in python lists and numpy arrays. AMUSE also supports storage classes that send messages to the codes to perform the actual storage and retrieval. At the script level the interface to these classes is all the same, so in normal use they behave the same. The performance of the different storage classes will vary, for a code storage the data may be sent over an internet connection causing slower reaction times. Smart usage of channels and caching data in memory sets will increase performance.
from amuse.lab import *
It is easy to make two collections with the same particles, we only have to copy the collection
particles1 = Particles(4) particles2 = particles1.copy_to_memory() print particles1 print particles2
key - ==================== 3252067210124956219 10026886985908834103 7273773629161635396 10546081611040806737 ==================== key - ==================== 3252067210124956219 10026886985908834103 7273773629161635396 10546081611040806737 ====================
The particles in the collection have the same keys and are considered the same particles in AMUSE, although they are not identical.
print particles1 == particles2 print particles1 is particles2
Setting the mass of the particles in one collection will not influence the particles in the second collection.
particles1.mass = [5,6,7,8] | units.MSun particles1.radius = [1,2,3,4] | units.RSun print particles2
key - ==================== 3252067210124956219 10026886985908834103 7273773629161635396 10546081611040806737 ====================
You could however easily copy the data over with an attribute assignment
particles2.mass = particles1.mass print particles2
key mass - MSun ==================== =========== 3252067210124956219 5.000e+00 10026886985908834103 6.000e+00 7273773629161635396 7.000e+00 10546081611040806737 8.000e+00 ==================== ===========
However this will fail or be incorrect if one of the sets changed in before the copy action
particles2.remove_particle(particles2) particles2.mass = particles1.mass print particles2
key mass - MSun ==================== =========== 3252067210124956219 5.000e+00 10026886985908834103 6.000e+00 10546081611040806737 7.000e+00 ==================== ===========
As you can see the mass of the last particle in particles2 is simply set to the mass of the third particle in particles1. These particles are no long the same particle as we removed the third particle from particles2. We just copied the masses based on the position of the particle in the collection and not based on the identity of the particle. In complex scripts where particles are removed and added due to physical processes this will cause incorrect results.
print particles1 == particles2 print particles1.mass print particles2.mass
False 7.0 MSun 7.0 MSun
AMUSE provides channels to track the particle identities and optimize the transport of attribute values between collections. Channels are save to use when adding or removing particles. Channels are uni-directional, you’ll need two to be able to do bi-derectional information exchange.
channel_from_1_to_2 = particles1.new_channel_to(particles2) channel_from_1_to_2.copy_attribute("mass") print particles1 print particles2
key mass radius - MSun RSun ==================== =========== =========== 3252067210124956219 5.000e+00 1.000e+00 10026886985908834103 6.000e+00 2.000e+00 7273773629161635396 7.000e+00 3.000e+00 10546081611040806737 8.000e+00 4.000e+00 ==================== =========== =========== key mass - MSun ==================== =========== 3252067210124956219 5.000e+00 10026886985908834103 6.000e+00 10546081611040806737 8.000e+00 ==================== ===========
As you can see the particles with the same key now also have the same mass. Channels are always defined between exactly 2 collections and will only copy data of the overlapping particles in both collections. In the abouve case data of 3 particles was copied.
Channels can copy an attribute from one set to another and give the copy a new name. This is useful, as some codes define particles with attributes having the same name but a script my assign a different meaning to these names. A stellar evolution code will define the star radius as just that, the star radius, but a stellar dynamics code might interpret the star radius as the star interaction radius (which will be factors larger).
channel_from_1_to_2.copy_attribute("mass", "core_mass") print particles2
key core_mass mass - MSun MSun ==================== =========== =========== 3252067210124956219 5.000e+00 5.000e+00 10026886985908834103 6.000e+00 6.000e+00 10546081611040806737 8.000e+00 8.000e+00 ==================== =========== ===========
Channels can be used to copy multiple attributes in one go, this can optimize data transport between codes.
channel_from_1_to_2.copy_attributes(["mass", "radius"]) print particles2
key core_mass mass radius - MSun MSun RSun ==================== =========== =========== =========== 3252067210124956219 5.000e+00 5.000e+00 1.000e+00 10026886985908834103 6.000e+00 6.000e+00 2.000e+00 10546081611040806737 8.000e+00 8.000e+00 4.000e+00 ==================== =========== =========== ===========