Problem 1300

Summary: The surface normal is incorrect
Product: Geant4 Reporter: gum
Component: geometry/navigationAssignee: John Apostolakis <John.Apostolakis>
Status: RESOLVED FIXED    
Severity: critical CC: Oliver.Merle
Priority: P5    
Version: 9.5   
Hardware: All   
OS: All   
Attachments: simple application showing the problem

Description gum 2012-04-02 21:27:53 CEST
Created attachment 155 [details]
simple application showing the problem

In a relatively simple geometry, a G4Box rotated around global-y, the surface normal presented to G4OpBoundaryProcess is wrong. Consequently, optical photon tracking does not work. If I am correct, this is a serious bug.

I attach a minimal geometry definition where I place a WLS bar inside the world with a rotation around Y. (The rotated positioning is all important here to exhibit the problem).

The WLS bar is clad with a reflector on three sides. The exit window (without the cladding) is at +x in its local coordinate system.

When the optical photon hits this exit surface, the "local normal" and "global point" (as printed in G4OpBoundaryProcess::DoIt) are correct, but the "local point" and "global normal" are both wrong!

To confirm this, please untar the attachment and run the executable without arguments.

Due to this error the OpBoundaryProcess claims  *** TotalInternalReflection *** while the ray continues.

You also need to add the four G4cout in the G4OpBoundaryProcess where the points and normals have been calculated:

......
      G4ThreeVector theGlobalPoint = pPostStepPoint->GetPosition();

      G4Navigator* theNavigator =
                   G4TransportationManager::GetTransportationManager()->
                                            GetNavigatorForTracking();

      G4ThreeVector theLocalPoint = theNavigator->
                                    GetGlobalToLocalTransform().
                                    TransformPoint(theGlobalPoint);

      G4cout << "theLocalPoint:  " << theLocalPoint  << G4endl;
      G4cout << "theGlobalPoint: " << theGlobalPoint << G4endl;
......
      G4cout << "theLocalNormal: " << theLocalNormal << G4endl;
      theGlobalNormal = theNavigator->GetLocalToGlobalTransform().
                                      TransformAxis(theLocalNormal);
      G4cout << "theGlobalNormal: " << theGlobalNormal << G4endl;

(Unfortunately, I can't change the 'Component' of this bug-report to something more appropriate than 'general'
Comment 1 Oliver Merle 2012-05-28 23:19:28 CEST
>> When the optical photon hits this exit surface, the "local normal" and "global
>> point" (as printed in G4OpBoundaryProcess::DoIt) are correct, but the "local
>> point" and "global normal" are both wrong!

If I understand the code correctly, the value of theLocalNormal is NOT correct. It should be expressed in the coordinate system of the *final* volume. In your example the final volume is the world volume, but theLocalNormal is given in the coordinate system of the WLS solid.

I'm not a Geant4 expert, but I think I understand at least a part of the problem:

I guess the reason is that the exit-normal (in G4Navigator) is stored in the coordinate system of what you might call the "grandmother" (fGrandMotherExitNormal), but the final volume is not *always* the grandmother. In your case its the mother of the grandmother. This means GetLocalExitNormal returns the *correct* normal in the *wrong* coordinate system.

In case of a photon exiting a child volume without entering another one (volume into mother) the code expects the final volume to be the "grandmother". But if the mother and its child share the same exit-surface, this logic fails because the photon is relocated into its grandgrandmother and the top-transform is not in the same coordinate system as fGrandMotherExitNormal.

I've changed the normal transformation in G4OpBoundaryProcess.cc to:

if(theNavigator->EnteredDaughterVolume()) {
  theGlobalNormal = theNavigator->GetLocalToGlobalTransform().
                      TransformAxis(theLocalNormal);
} else {
  const G4NavigationHistory * hist = pPreStepPoint->GetTouchable()->GetHistory();
  if(hist->GetDepth()>0) {
    theGlobalNormal = hist->GetTransform(hist->GetDepth()-1).Inverse().
                        TransformAxis(theLocalNormal);
  } else {
    theGlobalNormal=theLocalNormal;
  }
}

what seems to do the trick. I just use the touchable history of the preStepPoint to recover the grandmothers transformation. This doesn't change the fact that GetLocalExitNormal is returning the normal in the wrong CS. 


Kind Regards,
Oliver
Comment 2 Oliver Merle 2012-06-06 13:30:31 CEST
After applying the above correction in G4Navigator::LocateGlobalPointAndSetup, the bug appears to be fixed:

in G4Navigator::LocateGlobalPointAndSetup ...

at the beginning of the function, add:
//-----------------------------------------
static G4AffineTransform prev_transform;
G4bool transform_normal = fWasLimitedByGeometry && (fHistory.GetDepth()>0);
if(transform_normal) prev_transform = fHistory.GetTransform(fHistory.GetDepth()-1);
//-----------------------------------------

at the end of the function (directly before the return statement), add:
//-----------------------------------------
if( (!fEnteredDaughter) && transform_normal) {
  const G4AffineTransform & top_transform = fHistory.GetTopTransform();
  if(top_transform!=prev_transform)
    fGrandMotherExitNormal = fHistory.GetTopTransform().TransformAxis(prev_transform.Inverse().TransformAxis(fGrandMotherExitNormal));
}
//-----------------------------------------

This should correct the cached normal - and only when it is necessary. Maybe it would be better/faster to combine the matrices instead of applying the two transforms one after another.


As a side note:
You might want to revisit the following function:

G4ThreeVector G4Navigator::
GetLocalExitNormalAndCheck(const G4ThreeVector& ExpectedBoundaryPointGlobal,
                                 G4bool*        pValid)
{
  G4ThreeVector ExpectedBoundaryPointLocal;

  // Check Current point against expected 'local' value
  //
  if ( fLastTriedStepComputation ) 
  {
     const G4AffineTransform& GlobalToLocal= GetGlobalToLocalTransform(); 
     ExpectedBoundaryPointLocal =
       GlobalToLocal.TransformPoint( ExpectedBoundaryPointGlobal ); 
  }

  return GetLocalExitNormal( pValid); 
}


As you can see it effectively does the same as just calling GetLocalExitNormal(pValid);

So either something went wrong here or the function is redundant and just introduces overhead.

Regards,
Oliver
Comment 3 John Apostolakis 2012-08-21 12:57:56 CEST
I agree with the diagnosis of Oliver - indeed the 'Grandmother' surface normal as kept in G4Navigator refers to the origin of the ComputeStep ( typically the 'PreStep'  point ).   So is not transformed correctly when the final location's transformations are used - which is the case after LocateGlobalPoint is called in the PostStepDoIt of G4Transportation.

I have prepared a revised version of G4Navigator.hh and G4Navigator.cc which transform the surface normal to the global coordinate system during ComputeStep, when the correct transformation is available. 

It adds a new method GetGlobalExitNormal() which uses this in the relevant cases (when a volume is exited) and uses other methods to calculate the normal in the remaining cases.

// ********************************************************************
// GetGlobalExitNormal
//
// Obtains the Normal vector to a surface (in global coordinates)
// pointing out of previous volume and into current volume
// ********************************************************************
//
G4ThreeVector 
G4Navigator::GetGlobalExitNormal(const G4ThreeVector& IntersectPointGlobal,
                                       G4bool*        pValidNormal) 

I am willing to share this version for extra testing.

( I am trying also to check it against Oliver's suggestion, but have not yet succeeded. ) 

Best regards,
John Apostolakis
Comment 4 Gabriele Cosmo 2012-10-25 18:13:22 CEST
Fix has been introduced and will be included in the next release 9.6 and patch release 9.5.p02.