TeamAtomECS / AtomECS

Cold atom simulation code
GNU General Public License v3.0
46 stars 11 forks source link

Using the ramp crate #89

Closed BrianBostwick closed 1 year ago

BrianBostwick commented 1 year ago

Hi @ElliotB256, Tiffany informed me that you might not be receiving my emails. I was hoping we could talk about using the ramp.rs for the evaporative cooling. I have been unable to get it to work correctly, so I wrote my own, but I am concerned that how I am ramping is causing some severe slowing of my simulations. If you could email me at blb42@cam.ac.uk, I would appreciate it.

ElliotB256 commented 1 year ago

Hi Brian!

I am indeed having some issues with my old nexus account. It seems like the email forwarding is unreliable, - some days it works, some days (e.g. today!) it does not. But it also doesn't give any delivery failure notice which is concerning :/

Let's have the discussion here because then it provides something that others can search in the future :)

What have you done so far in trying to use the ramp crate? In general, you can ramp any component provided you implement the Lerp trait for it. This trait means that a component can be linearly interpolated from a value A to a value B by a given amount. A minimal example can be seen for the made up ALerpComp component in the unit test. There's also an automatic derive implementation you can use for simple types, which will enumerate through the fields on the struct and linearly interpolate each of them. That makes it much easier, so you can write e.g. #[derive(Lerp)] on your own components and that does all of the work for you. For example:

#[derive(Serialize, Clone, Copy, Lerp)]
pub struct QuadrupoleField3D {
    /// Gradient of the quadrupole field, in units of Tesla/m
    pub gradient: f64,
    /// A unit vector pointing along the symmetry axis of the 3D quadrupole field.
    pub direction: Vector3<f64>,
}

However, in order to make your components ramp in the simulation, you will need to add a system to your simulation that is responsible for ramping that particular component type. For example, from the unit test for the ramp module:

            .with(VelocityVerletIntegratePositionSystem, "integrator", &[])
            .with(
                RampUpdateSystem::<ALerpComp>::default(),
                "update_lerp_comp",
                &["integrator"],
            )
            .build();

This is adding a system that is responsible for ramping ALerpComp components, and arranging it to run after the integrator system in this example.

Hope this helps, show me your code and let's see why ramps aren't working. I would guess the system hasn't been added that is responsible for ramping the components? The documentation could probably be clearer here.

BrianBostwick commented 1 year ago

Hey Elliot,

Sounds good, we can just continue here.

Yes, I have looked through the #tests for the ramp but have been unable to translate that into the proper syntax for my simulation. To start, I do not understand how to add the RampUpdateSystem using the new simulation builder, do I need to write a plugin for Ramp?

This is my current Atomics simulation: (without ramp) https://github.com/BrianBostwick/evaperative_cooling/blob/main/src/main.rs

The goal is to have a ramp on the laser power. So, do I need to create my own version of ALerpCom? Or is this the gaussian.power?

I tried something like this,

sim_builder.dispatcher_builder.with(
        RampUpdateSystem::<GaussianBeam>::default(),
        "update_comp",
        &[],
    );

But I don't think this is correct.

This is my attempt at using the ramp with a test simulation: https://github.com/BrianBostwick/ramp_test/blob/main/src/main.rs

ElliotB256 commented 1 year ago

GaussianBeam implements Lerp already, so you only need to add the RampUpdateSystem as you have and add the values the GaussianBeam component should take at each different time. The convention in rust is to use the new function if it exists, e.g. Ramp::new(keyframes) to create your ramp component.

What you have done looks correct. To check, do you have a Step resource in your simulation?

world.insert(Step { n: 0 });

Another note is that you are defining values of the gaussian beam at t=0 and t=1, but your timestep is 1e-6 and you are only running 500 iterations, so you will only get a tiny fraction of the way towards the second keyframe value. https://github.com/BrianBostwick/ramp_test/blob/main/src/main.rs

BrianBostwick commented 1 year ago

Hi Elliot,

So I got a working example for a ramp of the laser power; thank you for the suggestions! I had a question; why do you only need to give the ramp GaussianBeam and not a specific Entity, say gaussain_beam = GaussianBeam{...}? Also, in my example, I could not pass the same ramp and frames to more than one beam without repeating the code; I tried using the & to ref. But it did not work.

Here is the repo with the working example: https://github.com/BrianBostwick/ramp_test/blob/main/src/main.rs

I think it would be nice to add it to the examples in AtomECS; would you agree? After I add more comments.

ElliotB256 commented 1 year ago

why do you only need to give the ramp GaussianBeam and not a specific Entity

The Ramp<T> component is attached to the entity you want to ramp - it will ramp the component of type T on that entity. So you could define Ramp<Position> and attach that to an entity to linearly interpolate it's Position, for instance.

Also, in my example, I could not pass the same ramp and frames to more than one beam without repeating the code; I tried using the & to ref. But it did not work.

This is because Ramp<T> doesn't implement the Copy or Clone traits. Remember that components are structs, and must be stored in the component storage memory. Copy would be automatically used by the compiler if it detects a copy must be made (for instance, because you are intending to store a copy of your initial struct at multiple separate locations in memory). If it implemented clone, you could use gaussian_beam.clone() to create a clone of the component on demand, which you could assign to the other entities. It seems like an oversight - try adding #[derive(Copy, Clone)] to https://github.com/TeamAtomECS/AtomECS/blob/master/src/ramp.rs#L25 and it might fix this for you.

I think it would be nice to add it to the examples in AtomECS; would you agree? After I add more comments.

Definitely! It would be great to see some ramp examples added, I think a lot of people are confused the first time they see it.

BrianBostwick commented 1 year ago

I see; thank you!

This is because Ramp<T> doesn't implement the Copy or Clone traits. Remember that components are structs, and must be stored in the component storage memory. Copy would be automatically used by the compiler if it detects a copy must be made (for instance, because you are intending to store a copy of your initial struct at multiple separate locations in memory). If it implemented clone, you could use gaussian_beam.clone() to create a clone of the component on demand, which you could assign to the other entities. It seems like an oversight - try adding #[derive(Copy, Clone)] to https://github.com/TeamAtomECS/AtomECS/blob/master/src/ramp.rs#L25 and it might fix this for you.

Okay, this makes sense, but it did not support the Copy trait. I was able to use ramp.clone(), but I don't fully understand the distinction. It has to do with how the component is stored in memory and its complexity of it. Is that right?

Definitely! It would be great to see some ramp examples added; I think a lot of people are confused the first time they see it.

Should I push it then once I have added some comments?

ElliotB256 commented 1 year ago

Yes please! (Apologies, I thought I had replied from my phone but it appears to not have posted it!)