Unexpected absorbed SR photon distribution in Synrad+

Dear @maarton,

May I ask you a few questions about the old and new reflection models implemented in Synrad+?

1. Old model:

How did you derive the two equations for the perturbed surface normal?

thetaOffset=atan(sigmaRatio*tan(PI*(rnd()-0.5)));
phiOffset=atan(sigmaRatio*tan(PI*(rnd()-0.5)));

Do you have any references to cite these equations?

2. New model:

Can I use the scattering angle fit parameters listed in your PhD thesis, see Table 2.4, as an approximation for the new reflection model in my custom-built code? Or should I repeat your study and re-fit the distributions in order to get the correct fit parameters for the reflection angles?

I would really appreciate any help you can provide.

Thanks and best regards,
Andrii

Hello Andrii,

I used the phrase “The old model, ported from an earlier code, without any physical model to back it up” because that’s the unfortunate case - I tried to find a source for this equation but didn’t, as it was ported from an in-house ray tracing code. That’s why the new reflection model was developed in the first place, because even though the old gives valid-looking results, it’s not acceptable to have a simulator with a key component not backed up by verified paper(s).

Sure, my Phd thesis is public, feel free to use those approximations. As you can see in Fig. 2.32 and Fig.2.36 right side, for very low grazing angles (~1 deg), the out-of-plane angle approximation is not that good. I paste at the end the C++ routines if they help.

  1. Just one question: In your graphs, and in your messages, you refer often to blue dashed curves, but I don’t see any. Is it because it’s always perfectly overlapping the green curve?

Thanks, Marton

Old reflection model surface perturbation:

std::tuple<Vector3d, Vector3d, Vector3d>
ParticleTracer::PerturbateSurface(const SimulationFacet &collidedFacet,
                            const double &sigmaRatio) {

    double rnd1 = randomGenerator.rnd();
    double rnd2 = randomGenerator.rnd(); // for debug
    // Saturate(rnd1, 0.01, 0.99); //Otherwise thetaOffset would go to +/-
    // infinity Saturate(rnd2, 0.01, 0.99); //Otherwise phiOffset would go to +/-
    // infinity
    double thetaOffset = atan(sigmaRatio * tan(PI * (rnd1 - 0.5)));
    double phiOffset = atan(sigmaRatio * tan(PI * (rnd2 - 0.5)));

    // Make a local copy
    Vector3d nU_facet = collidedFacet.sh.nU;
    Vector3d nV_facet = collidedFacet.sh.nV;
    Vector3d N_facet = collidedFacet.sh.N;

    // Random rotation around N (to discard U orientation thus make scattering
    // isotropic)
    double randomAngle = randomGenerator.rnd() * 2 * PI;
    nU_facet = Rotate(nU_facet, Vector3d(0, 0, 0), N_facet, randomAngle);
    nV_facet = Rotate(nV_facet, Vector3d(0, 0, 0), N_facet, randomAngle);

    // Bending surface base vectors
    Vector3d nU_rotated =
            Rotate(nU_facet, Vector3d(0, 0, 0), nV_facet, thetaOffset);
    Vector3d N_rotated =
            Rotate(N_facet, Vector3d(0, 0, 0), nV_facet, thetaOffset);
    nU_rotated = Rotate(nU_rotated, Vector3d(0, 0, 0), nU_facet,
                        phiOffset); // check if correct
    Vector3d nV_rotated =
            Rotate(nV_facet, Vector3d(0, 0, 0), nU_facet, phiOffset);
    N_rotated = Rotate(N_rotated, Vector3d(0, 0, 0), nU_facet, phiOffset);

    return std::make_tuple(nU_rotated, nV_rotated, N_rotated);
}

New reflection:

void ParticleTracer::PerformBounce_new(SimulationFacet &collidedFacet,
                                 const int &reflType, const double &inTheta,
                                 const double &inPhi) {

    double outTheta, outPhi; // perform bounce without scattering, will perturbate
    // these angles later if it's a rough surface
    if (collidedFacet.sh.reflectType == REFLECTION_DIFFUSE) {
        outTheta = acos(sqrt(randomGenerator.rnd()));
        outPhi = randomGenerator.rnd() * 2.0 * PI;
    } else if (collidedFacet.sh.reflectType == REFLECTION_SPECULAR) {
        outTheta = PI - inTheta;
        outPhi = inPhi;
    } else { // material reflection, might have backscattering
        switch (reflType) {
            case REFL_FORWARD: // forward scattering
                outTheta = PI - inTheta;
                outPhi = inPhi;
                break;
            case REFL_DIFFUSE: // diffuse scattering
                outTheta = acos(sqrt(randomGenerator.rnd()));
                outPhi = randomGenerator.rnd() * 2.0 * PI;
                break;
            case REFL_BACK: // back scattering
                outTheta = PI - inTheta;
                outPhi = PI + inPhi;
                break;
        } // end switch (transparent pass treated at the Intersect() routine
    }   // end material reflection

    if (collidedFacet.sh.doScattering) {
        double incidentAngle = abs(inTheta);
        if (incidentAngle > PI / 2)
            incidentAngle = PI - incidentAngle; // coming from the normal side
        double y = cos(incidentAngle);
        double wavelength =
                3E8 * 6.626E-34 / (energy * 1.6E-19); // energy[eV] to wavelength[m]
        double specularReflProbability =
                exp(-Sqr(4 * PI * collidedFacet.sh.rmsRoughness * y /
                         wavelength)); // Debye-Wallers factor, See "Measurements of
        // x-ray scattering..." by Dugan, Sonnad, Cimino,
        // Ishibashi, Scafers, eq.2
        bool specularReflection = randomGenerator.rnd() < specularReflProbability;
        if (!specularReflection) {
            // Smooth surface reflection performed, now let's perturbate the angles
            // Using Gaussian approximated distributions of eq.14. of the above
            // article
            double onePerTau =
                    collidedFacet.sh.rmsRoughness / collidedFacet.sh.autoCorrLength;

            // Old acceptance-rejection algorithm
            /*
            size_t nbTries = 0; double outThetaPerturbated;
            do {
                double dTheta = Gaussian(2.9264*onePerTau); //Grazing angle
            perturbation, depends only on roughness, must assure it doesn't go against
            the surface outThetaPerturbated = outTheta + dTheta; nbTries++; } while
            (((outTheta < PI / 2) != (outThetaPerturbated < PI / 2)) && nbTries <
            10);*/

            // New truncated Gaussian algorithm, see N. Chopin: Fast simulation of
            // truncated Gaussian distributions, DOI: 10.1007/s11222-009-9168-1

            double lowerBound = 0.0;
            double upperBound = PI / 2;
            if (outTheta > (PI / 2)) { // Limits: PI/2 .. PI instead of 0..PI/2
                lowerBound += PI / 2;
                upperBound += PI / 2;
            }
            double outThetaPerturbated = truncatedGaussian.GetGaussian(
                    outTheta, 2.9264 * onePerTau, lowerBound, upperBound);

            double dPhi = randomGenerator.Gaussian(
                    (2.80657 * pow(incidentAngle, -1.00238) -
                     1.00293 * pow(incidentAngle, 1.22425)) *
                    onePerTau); // Out-of-plane angle perturbation, depends on roughness
            // and incident angle
            outTheta = outThetaPerturbated;
            outPhi += dPhi;
        }
    }

    particle.direction = PolarToCartesian(collidedFacet.sh.nU, collidedFacet.sh.nV,
                                 collidedFacet.sh.N, outTheta, outPhi, false);

    RecordHit(HIT_REF);
    lastHitFacet = &collidedFacet;
    if (collidedFacet.sh.countRefl) {
        RecordHitOnTexture(&collidedFacet);
    }
}

The full file is here: https://gitlab.cern.ch/molflow_synrad/synrad/-/blob/master_cli/src/Simulation/Particle.cpp?ref_type=heads

1 Like

Dear @maarton,

Thank you so much! I will let you know when the results for the new model in Geant4 are ready so we can compare them against previous numbers.

  1. Just one question: In your graphs, and in your messages, you refer often to blue dashed curves, but I don’t see any. Is it because it’s always perfectly overlapping the green curve?

Yes, the green and blue histograms overlap.

Cheers,
Andrii

Dear @maarton ,

I apologize for being annoying :slight_smile: I want to make my code reliable. So, currently, I’m working on implementing the new Synrad+ model into my custom-built Geant4 code. All previous options worked well, so I hope this new option goes smoothly, too.

I have one question regarding the reflection probability for the new model. In your step-by-step instruction from above, it seems the reflection probability depends only on photon energy and incident angle. And you use roughness for reflected angle change (Debye-Waller factor – specular reflection, if not, then perturb the reflected direction). So, according to this, the absorption probability is independent of roughness. Is it true, or do I miss something? Running Synrad+ with old and new models gives the same absorbed photon distribution, so I wonder what is still missing.

I sincerely appreciate any help you can provide.
Best regards,
Andrii

Yes, I confirm.

In this file:
rough_vs_shiny.syn7z (77.2 KB)

Facets 19 and 20 are exactly the same, except one is extremely rough (sigma=5000nm), the other is shiny (sigma=50nm):


image

Plotting the absorbed spectrum, it’s the same for both.

In the old model, however, roughness does play a role in reflectivity, as the surface is perturbed before the incident angle is calculated.

The five curves show the absorbed photons as a function of roughness (in legend):

image

If you’d like, I can check your file to see why you got the same distributions with the old model (maybe you were in the roughness range which doesn’t change the reflection significantly

Cheers, Marton

Dear @maarton ,

Thank you for your reply.

Perhaps I was not clear and did not fully explain the issue. I do not have problems with the old model results from Synrad+. For the old model, everything behaves as I expected and is in good agreement with my custom-built Geant4 simulation. However, the new model in Synrad+ behaves strangely. It gives different absorption probability for different roughness, while according to the algorithm, there should not be any difference between ON (sigma = 50 nm, T = 10000 nm) and OFF for the “Rough surface scattering” option. Right?

Fig. 1: Absorbed SR photon energy spectrum. Old vs New Reflection model for Rough surface scattering = ON (sigma = 50 nm, T = 10000 nm), facets #19 and #20

Fig. 2: Absorbed SR photon energy spectrum. Old vs New Reflection model for Rough surface scattering = OFF, facets #19 and #20

To turn ON and OFF the new model I use “Global Settings”, “Use new reflection model” = ON/OFF, and press “Apply above settings” with resetting the simulation; see below.

Here is the Synrad+ files:
synrad_files.zip (9.5 KB)

Therefore, I would really appreciate it if you could help me understand why the new model gives the same results as the old model for Rough surface scattering = ON (sigma = 50 nm, T = 10000 nm). Maybe I miss something?

Thank you and best regards,
Andrii

Hello Andrii,

I see something not as expected when switching between old and new reflection models. For some reason you have to reapply the roughness parameters to facets 19 and 20 to actually switch modes. I guess something’s off with the conversion between the single-value roughness ratio and the bi-value roughness/correlation length.

I’ll have a look and get back.

1 Like

I identified the bug. After switching between old and new reflection models, “change” a parameter of any facet, which will force a reloading:

refl_change

Notice how I write “1” to mimic a parameter change.

So basically the reflection model change was not shared with the simulation units, unless a model change forced it. I’ll release a fix soon, thanks for finding this.

1 Like

Dear @maarton, Thank you!
Cheers,
Andrii

v1.4.34 should now fix this:

Cheers, Marton

Dear @maarton,

Thank you so much for preparing this new version of Synrad+. I have downloaded and tried it. Now, everything is in good agreement with my expectations and my custom-built Geant4 simulation results.

Best regards,
Andrii

Fig. 1: Absorbed SR photon energy spectrum. Old vs New Reflection model for Rough surface scattering = ON (sigma = 50 nm, T = 10000 nm), facets #19 and #20

Fig. 2: Absorbed SR photon energy spectrum. Old vs New Reflection model for Rough surface scattering = OFF, facets #19 and #20

Fig.3: Comparison of the absorbed SR photon spectrums for different simulation codes and models.

1 Like