-
Notifications
You must be signed in to change notification settings - Fork 106
EM Field Enhancement: local coordinates, dynamic placements and mapped fields #1012
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
srmarcballestero
wants to merge
19
commits into
OpenGATE:master
Choose a base branch
from
srmarcballestero:feature/field_enhancement
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
65eec5f
Include new C++ classes in opengate_lib for field management. This:
srmarcballestero 3242f72
Enhance field management on the Python side by adding support for fie…
srmarcballestero 10e399f
[pre-commit.ci] Automatic python and c++ formatting
pre-commit-ci[bot] d660b8e
Enhance support for electromagnetic fields (E or E+B) , and implement…
srmarcballestero 88e9aef
Bind G4SextupoleMagField and add support for it on the Python side
srmarcballestero 66dda13
Bind G4SextupoleMagField and add support for it on the Python side
srmarcballestero 5a22998
Fixes:
srmarcballestero 75ad5dd
Fix: robustness of `refresh_transforms`
srmarcballestero 8f81e1a
Enahnce existing tests and add new ones
srmarcballestero f018a8f
Document changes so far
srmarcballestero f14365f
Refactor internal GATE field management: one single base class conta…
srmarcballestero 77c5628
Implement grid-based mapped electric and magnetic fields
srmarcballestero a2d9252
Implement grid-based mapped electric and magnetic fields
srmarcballestero 728a6f7
Implement mapped combined electric and magnetic fields
srmarcballestero dc8d640
Refactor tests and add new ones
srmarcballestero 7af1d25
Documentation for mapped fields + fix related build warnings
srmarcballestero e49853f
Fix test description for MappedElectricField vs UniformElectricField
srmarcballestero e448223
Add round-trip serialization tests for MappedMagneticField, MappedEle…
srmarcballestero 0a5457f
Merge branch 'OpenGATE:master' into feature/field_enhancement
srmarcballestero File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,24 @@ | ||
| /* -------------------------------------------------- | ||
| Copyright (C): OpenGATE Collaboration | ||
| This software is distributed under the terms | ||
| of the GNU Lesser General Public Licence (LGPL) | ||
| See LICENSE.md for further details | ||
| -------------------------------------------------- */ | ||
|
|
||
| #include <pybind11/pybind11.h> | ||
|
|
||
| namespace py = pybind11; | ||
|
|
||
| #include "G4MagneticField.hh" | ||
| #include "G4SextupoleMagField.hh" | ||
|
|
||
| void init_G4SextupoleMagField(py::module &m) { | ||
|
|
||
| py::class_<G4SextupoleMagField, G4MagneticField, | ||
| std::unique_ptr<G4SextupoleMagField, py::nodelete>>( | ||
| m, "G4SextupoleMagField") | ||
|
|
||
| .def(py::init<G4double>()) | ||
|
|
||
| ; | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
28 changes: 28 additions & 0 deletions
28
core/opengate_core/opengate_lib/GateElectroMagneticField.cpp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| /* -------------------------------------------------- | ||
| Copyright (C): OpenGATE Collaboration | ||
| This software is distributed under the terms | ||
| of the GNU Lesser General Public Licence (LGPL) | ||
| See LICENSE.md for further details | ||
| -------------------------------------------------- */ | ||
|
|
||
| #include "GateElectroMagneticField.h" | ||
|
|
||
| GateElectroMagneticField::GateElectroMagneticField( | ||
| G4ElectroMagneticField *inner, const G4VSolid *solid, | ||
| std::vector<G4ThreeVector> translations, | ||
| std::vector<G4RotationMatrix> rotations, double deltaChordMM) | ||
| : G4ElectroMagneticField(), | ||
| GateFieldBase(solid, std::move(translations), std::move(rotations), | ||
| deltaChordMM), | ||
| m_inner(inner) {} | ||
|
|
||
| void GateElectroMagneticField::GetFieldValue(const G4double Point[4], | ||
| G4double *BEfield) const { | ||
| // localFieldFunc is a lambda that captures 'this' and calls | ||
| // m_inner->GetFieldValue | ||
| auto localFieldFunc = [this](const G4double pos[4], G4double *f) { | ||
| m_inner->GetFieldValue(pos, f); | ||
| }; | ||
|
|
||
| applyTransforms(Point, BEfield, 6, localFieldFunc); | ||
| } |
38 changes: 38 additions & 0 deletions
38
core/opengate_core/opengate_lib/GateElectroMagneticField.h
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| /* -------------------------------------------------- | ||
| Copyright (C): OpenGATE Collaboration | ||
| This software is distributed under the terms | ||
| of the GNU Lesser General Public Licence (LGPL) | ||
| See LICENSE.md for further details | ||
| -------------------------------------------------- */ | ||
|
|
||
| #ifndef GateElectroMagneticField_h | ||
| #define GateElectroMagneticField_h | ||
|
|
||
| #include "G4ElectroMagneticField.hh" | ||
| #include "GateFieldBase.h" | ||
| #include <vector> | ||
|
|
||
| class G4VSolid; | ||
|
|
||
| // GATE wrapper for G4ElectroMagneticField. | ||
| class GateElectroMagneticField : public G4ElectroMagneticField, | ||
| public GateFieldBase { | ||
| public: | ||
| // constructor | ||
| GateElectroMagneticField(G4ElectroMagneticField *inner, const G4VSolid *solid, | ||
| std::vector<G4ThreeVector> translations, | ||
| std::vector<G4RotationMatrix> rotations, | ||
| double deltaChordMM); | ||
|
|
||
| // override GetFieldValue to apply the coordinate transforms | ||
| void GetFieldValue(const G4double Point[4], G4double *BEfield) const override; | ||
|
|
||
| // override DoesFieldChangeEnergy to return true for electro-magnetic fields | ||
| G4bool DoesFieldChangeEnergy() const override { return true; } | ||
|
|
||
| private: | ||
| // the inner field in the volume's local frame | ||
| G4ElectroMagneticField *m_inner; | ||
| }; | ||
|
|
||
| #endif // GateElectroMagneticField_h |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| /* -------------------------------------------------- | ||
| Copyright (C): OpenGATE Collaboration | ||
| This software is distributed under the terms | ||
| of the GNU Lesser General Public Licence (LGPL) | ||
| See LICENSE.md for further details | ||
| -------------------------------------------------- */ | ||
|
|
||
| #include "GateFieldBase.h" | ||
|
|
||
| #include "G4VSolid.hh" | ||
| #include "globals.hh" | ||
|
|
||
| #include <cmath> | ||
| #include <limits> | ||
| #include <sstream> | ||
| #include <stdexcept> | ||
|
|
||
| // constructor | ||
| GateFieldBase::GateFieldBase(const G4VSolid *solid, | ||
| std::vector<G4ThreeVector> translations, | ||
| std::vector<G4RotationMatrix> rotations, | ||
| double deltaChordMM) | ||
| : m_solid(solid), | ||
| m_fallbackFatalDistanceMM( | ||
| 5.0 * std::sqrt(8.0 * kMaxCurvatureRadiusMM * deltaChordMM)) { | ||
| if (solid == nullptr) | ||
| throw std::invalid_argument("GateFieldBase: solid must not be null"); | ||
|
|
||
| if (translations.size() != rotations.size() || translations.empty()) | ||
| throw std::invalid_argument("GateFieldBase: translations and rotations " | ||
| "must be non-empty and have the same size"); | ||
|
|
||
| m_transforms.reserve(translations.size()); | ||
| for (std::size_t i = 0; i < translations.size(); ++i) | ||
| m_transforms.emplace_back(rotations[i].inverse(), translations[i]); | ||
| } | ||
|
|
||
| // recache the world-to-local transforms (e.g. after a geometry change between | ||
| // runs) | ||
| void GateFieldBase::SetTransforms(std::vector<G4ThreeVector> translations, | ||
| std::vector<G4RotationMatrix> rotations) { | ||
| if (translations.size() != rotations.size() || translations.empty()) | ||
| throw std::invalid_argument( | ||
| "GateFieldBase::SetTransforms: translations and rotations must be " | ||
| "non-empty and have the same size"); | ||
|
|
||
| m_transforms.clear(); | ||
| m_transforms.reserve(translations.size()); | ||
| for (std::size_t i = 0; i < translations.size(); ++i) | ||
| m_transforms.emplace_back(rotations[i].inverse(), translations[i]); | ||
| } | ||
|
|
||
| // find the local coordinates of worldPoint in the containing placement of the | ||
| // field's logical volume | ||
| G4ThreeVector GateFieldBase::findContainingPlacement( | ||
| const G4ThreeVector &worldPoint, | ||
| const G4AffineTransform *&outTransform) const { | ||
| std::size_t closestIdx = 0; | ||
| double minDistToSurface = std::numeric_limits<double>::infinity(); | ||
| G4ThreeVector closestLocal{}; | ||
|
|
||
| for (std::size_t i = 0; i < m_transforms.size(); ++i) { | ||
| const auto &tr = m_transforms[i]; | ||
| const G4ThreeVector localPoint = tr.InverseTransformPoint(worldPoint); | ||
|
srmarcballestero marked this conversation as resolved.
|
||
|
|
||
| if (m_solid->Inside(localPoint) != kOutside) { | ||
| outTransform = &tr; | ||
| return localPoint; | ||
| } | ||
|
|
||
| const double d = m_solid->DistanceToIn(localPoint); | ||
| if (d < minDistToSurface) { | ||
| minDistToSurface = d; | ||
| closestIdx = i; | ||
| closestLocal = localPoint; | ||
| } | ||
| } | ||
|
|
||
| // if the point is outside every placement, check if it's within the | ||
| // fallback-fatal distance which accounts for any reasonable field integrator | ||
| // overshoot due to tolerances. if not, this is very likely a bug in the | ||
| // geometry or field setup -> fatal | ||
| if (minDistToSurface > m_fallbackFatalDistanceMM) { | ||
| std::ostringstream msg; | ||
| msg << "GateFieldBase::findContainingPlacement: world point (" | ||
| << worldPoint.x() << ", " << worldPoint.y() << ", " << worldPoint.z() | ||
| << ") mm is " << minDistToSurface | ||
| << " mm outside every cached placement of the field's solid — " | ||
| << "well beyond any chord-finder overshoot.\n" | ||
| << " Closest placement: index " << closestIdx << " (local point " | ||
| << closestLocal.x() << ", " << closestLocal.y() << ", " | ||
| << closestLocal.z() << ").\n" | ||
| << " Maximum allowed distance before fatal: " | ||
| << m_fallbackFatalDistanceMM << " mm.\n" | ||
| << " This likely indicates a real bug in the geometry or field " | ||
| "setup.\n"; | ||
|
|
||
| G4Exception("GateFieldBase::findContainingPlacement", "GateField0001", | ||
| FatalException, msg.str().c_str()); | ||
| } | ||
|
|
||
| outTransform = &m_transforms[closestIdx]; | ||
| return closestLocal; | ||
| } | ||
|
|
||
| // rotate a field vector from local to world coordinates using the given | ||
| // transform | ||
| G4ThreeVector GateFieldBase::rotateToWorld(const G4ThreeVector &localField, | ||
| const G4AffineTransform &transform) { | ||
| return transform.TransformAxis(localField); | ||
|
srmarcballestero marked this conversation as resolved.
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,81 @@ | ||
| /* -------------------------------------------------- | ||
| Copyright (C): OpenGATE Collaboration | ||
| This software is distributed under the terms | ||
| of the GNU Lesser General Public Licence (LGPL) | ||
| See LICENSE.md for further details | ||
| -------------------------------------------------- */ | ||
|
|
||
| #ifndef GateFieldBase_h | ||
| #define GateFieldBase_h | ||
|
|
||
| #include "G4AffineTransform.hh" | ||
| #include "G4RotationMatrix.hh" | ||
| #include "G4ThreeVector.hh" | ||
| #include "G4Types.hh" | ||
| #include <vector> | ||
|
|
||
| class G4VSolid; | ||
|
|
||
| // Shared base class for all GATE field types | ||
| // - stores the world-to-local transforms of every physical placement of the | ||
| // logical volume | ||
| // - handles the full coordinate transform journey for GetFieldValue | ||
| class GateFieldBase { | ||
| public: | ||
| // constructor | ||
| GateFieldBase(const G4VSolid *solid, std::vector<G4ThreeVector> translations, | ||
| std::vector<G4RotationMatrix> rotations, double deltaChordMM); | ||
|
|
||
| // update the transforms (e.g. after a geometry change between runs) | ||
| void SetTransforms(std::vector<G4ThreeVector> translations, | ||
| std::vector<G4RotationMatrix> rotations); | ||
|
|
||
| protected: | ||
| // LocalFieldFn is expected to be something like: | ||
| // void getLocalField(const G4double localPos[4], G4double *field); | ||
| // which fills the field array with the local field value at the given | ||
| // localPos. | ||
| template <typename LocalFieldFn> | ||
|
|
||
| // Get the field value at the given world point by transforming to local | ||
| // coordinates, calling getLocalField to fill the field value in local frame, | ||
| // and then rotating the field vector(s) back to world frame | ||
| void applyTransforms(const G4double Point[4], G4double *field, | ||
| int nComponents, LocalFieldFn getLocalField) const { | ||
| const G4ThreeVector worldPt(Point[0], Point[1], Point[2]); | ||
| const G4AffineTransform *tr = nullptr; | ||
| const G4ThreeVector localPt = findContainingPlacement(worldPt, tr); | ||
| const G4double localPos[4] = {localPt.x(), localPt.y(), localPt.z(), | ||
| Point[3]}; | ||
|
|
||
| getLocalField(localPos, field); | ||
|
|
||
| for (int i = 0; i < nComponents; i += 3) { | ||
| const G4ThreeVector v = rotateToWorld( | ||
| G4ThreeVector(field[i], field[i + 1], field[i + 2]), *tr); | ||
| field[i] = v.x(); | ||
| field[i + 1] = v.y(); | ||
| field[i + 2] = v.z(); | ||
| } | ||
| } | ||
|
|
||
| private: | ||
| // find the local coordinates of worldPoint in the containing placement of the | ||
| // field's logical volume | ||
| G4ThreeVector | ||
| findContainingPlacement(const G4ThreeVector &worldPoint, | ||
| const G4AffineTransform *&outTransform) const; | ||
|
|
||
| // rotate a field vector from local to world coordinates using the given | ||
| // transform | ||
| static G4ThreeVector rotateToWorld(const G4ThreeVector &localField, | ||
| const G4AffineTransform &transform); | ||
|
|
||
| const G4VSolid *m_solid; | ||
| double m_fallbackFatalDistanceMM; | ||
| std::vector<G4AffineTransform> m_transforms; | ||
|
|
||
| static constexpr double kMaxCurvatureRadiusMM = 100.0e3; | ||
| }; | ||
|
|
||
| #endif // GateFieldBase_h |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.