This documentation is structured according to the Diátaxis architecture:
Study | Work | |
---|---|---|
Practical | Tutorials | How-To Guides |
Theoretical | Explanation | Reference |
Introduction
nain4
has 3 main goals:
-
Enabling writing
Geant4
application code much more concisely: increasing the signal to noise ratio ofGeant4
code. -
Making it easy to write automated tests for
Geant4
application code. -
Promoting
Geant4
errors from run-time to compile-time.
Why is it called nain4
?
Géant and nain are French words:
- Géant: giant (noun); giant, huge (adjectives)
- Nain: dwarf, midget (nouns); dwarf, miniature (adjectives)
With nain4
, you can express in a tiny amount of code, that which would take huge amounts of code to express in plain Geant4
; turn your gigantic source files into miniature ones, without loss of meaning.
How should I pronounce nain4
?
Rendered in the International Phonetic Alphabet, the pronunciation of the French word nain, is \nɛ̃\.
In English this is, very roughly, something like nuh.
This sound tends to get drowned out and sound odd, in the middle of a sentence spoken in English, and many other languages. Therefore feel free to pronounce it nano. In Spanish, enano is especially appropriate.
Tutorials
How-To Guides
How to install nix
nain4
is distributed and its dependencies are managed using
nix
. The reasons for this are explained in why nix?.
On your personal computer
In order to install nix
on your personal computer, simply use1
curl --proto '=https' --tlsv1.2 -sSf -L https://install.determinate.systems/nix | sh -s -- install
The installer will guide you through the installation steps. This
installer requires super-user permissions (sudo
).
On a HPC cluster
The situation on HPC clusters is different, as we usually we don't have super-user permissions. At the moment the installation of nix in this kind of environments is not fully supported. In the meantime, you may want to check How to make nain4 available in a Geant4/cmake project
direnv
We strongly recommend using direnv
to automatically
activate and deactivate development environments when entering or
leaving a project directory.
To use direnv
:
-
Make sure that it is installed on your system. To install
direnv
withnix
, runnix profile install nixpkgs#direnv
-
Don't forget to hook it into your shell. Depending on which shell you are using, this will involve adding one of the following lines to the end of your shell configuration file:
eval "$(direnv hook bash)" # in ~/.bashrc eval "$(direnv hook zsh)" # in ~/.zshrc eval `direnv hook tcsh` # in ~/.cshrc
The first time direnv
wants to perform an automatic switch in a new context
(combination of directory + .envrc
contents), it asks you for permission to do
so. You can give it permission by typing direnv allow
in the shell. The
message that direnv
gives you at this stage is pretty clear, but it's usually
written in red, thus you might get the mistaken impression that there is an
error.
Now, every time you enter the directory you will see a message like:
direnv: loading .envrc
direnv export: +FOO +BAR +BAZ ...
and when you exit
direnv: unloading
How to make nain4 available in a Geant4/cmake project
There are 3 options to include nain4 in your project:
- Adding it as an external project (recommended)
- Adding it as a subdirectory in your project
- Creating an independent installation
Adding Nain4 as a external project
Add this snippet to your CMakeLists.txt:
include(FetchContent)
set(FETCHCONTENT_UPDATES_DISCONNECTED ON CACHE BOOL "Cache package contents to avoid unnecessary downloads")
FetchContent_Declare(
nain4
GIT_REPOSITORY https://github.com/${USERNAME}/nain4.git
GIT_TAG ${COMMIT_HASH}
# make sure that no other nain4 installation is used
OVERRIDE_FIND_PACKAGE
SOURCE_SUBDIR nain4/src
)
FetchContent_MakeAvailable(nain4)
The value given to GIT_TAG
may be a replaced with a branch name (origin/<branch-name>
) or a commit hash. When opting for the 'branch name' option, it's important to be aware of the potential risk of losing reproducibility. With each execution of CMake, the library may be updated to a different state, introducing changes that can impact the reproducibility of your project. This can lead to compilation or runtime errors, making it challenging to recreate specific build or runtime environments. Thus, we advise to use a commit hash or a tag instead.
Adding Nain4 as a subdirectory to your project
Clone the Nain4 repository into your own project or create a symbolic link to the clone. Then simply add
add_subdirectory(nain4/nain4/src)
to your CMakeLists.txt.
Install Nain4 independently
git clone https://github.com/jacg/nain4.git
cd nain4
cmake [-DCMAKE_INSTALL_PREFIX=/path/to/install/] -S nain4 -B build
cmake --build build --target install
Unless the option CMAKE_INSTALL_PREFIX
is speficied, the files will be installed in /path/to/nain4/install
.
Then add to your CMakeLists.txt:
set(nain4_DIR "$ENV{NAIN4_INSTALL}/lib/cmake/nain4/")
find_package(nain4 REQUIRED)
Make sure you have NAIN4_INSTALL
set in your shell environment before running cmake
or replace it by /path/to/install/
in the CMakeLists.txt file.
Linking to Nain4
Regardless of the chosen option, you will also need to link the library to each target like:
target_link_libraries(
client_exe
PRIVATE
nain4
${Geant4_LIBRARIES}
)
How to start a nain4-based project
The following instructions assume that you have nix
installed. If
not, please see Install nix.
Setting up a new nain4
project
In order to create a nain4
-based project use bootstrap-client-project
like this:
nix run github:jacg/nain4#bootstrap-client-project path/to/your/project your-chosen-name "one line project description"
where
path/to/your/project
should not exist before you run the command.your-chosen-name
should be replaced with a name of your choice: this should be a valid identifier in bothbash
and theNix
language, so, roughly speaking, it should not contain any spaces, slashes or other dodgy characters. It will be used to name various things within your project."one line project description"
should be replaced with some text of your choice. Don't forget the quotes.
Basic usage
just run -n 100
: run the application in batch mode with/run/beamOn 100
just run -g
: run the application with the interactive GUI WARNING: unless you are running onNixOS
macOS
this will probably take a long time the first time you try it, as it will download and compilenixGL
which is used to detect graphics hardware automatically and guarantee the presence of the required graphics drivers. On subsequent runs, this overhead will disappear.
- TODO: describe the early/late CLI options
- TODO: describe the macro path CLI options
Project contents
Your bootstrapped project will contain the following files
path/to/your/project
├── execute-with-nixgl-if-needed.sh
├── flake
│ └── outputs.nix
├── flake.lock
├── flake.nix
├── justfile
├── macs
│ ├── early-cli.mac
│ ├── early-hard-wired.mac
│ ├── late-cli.mac
│ ├── late-hard-wired.mac
│ ├── run.mac
│ ├── vis.mac
│ └── vis2.mac
├── run-each-test-in-separate-process.sh.in
├── src
│ ├── LXe.cc
│ ├── LXe.hh
│ ├── meson.build
│ └── n4app.cc
└── test
├── catch2-main-test.cc
├── meson.build
├── test-catch2-demo.cc
└── test-LXe.cc
-
TODO: discuss the contents of
src
andtest
-
The
macs
directory contains Geant4 macro files, used to provide some app configuration parameters at runtime.macs/run.mac
controls aspects of the simulationmacs/vis.mac
controls aspects of visulisation (only used in interactive mode)
-
The
justfile
file defines some recipes for easier usage of the application. -
The
flake
directory andflake.lock
,flake.nix
files describe the dependency tree for your project. In most cases, you will not need to read or modify them.
Make it your project
Because this is a template, there are a number of variables that take a generic name. We encourage you to change them to represent something meaningfull to you. You can find these variables by running
grep -Rn CHANGEME .
on the root folder of your project. You will see an output with several lines like
[file]:[line-number]:#CHANGEME: [description]
Open those files with your favourite text editor and rename those variables.
How to create a minimal nain4 app
Building the app
One of the most basic examples of a nain4 app can be found in templates/basic/src/n4app.cc
. Here is the full file:
#include <n4-all.hh>
#include <G4GenericMessenger.hh>
#include <G4PrimaryParticle.hh>
#include <G4String.hh>
#include <G4SystemOfUnits.hh> // physical units such as `m` for metre
#include <G4Event.hh> // needed to inject primary particles into an event
#include <G4Box.hh> // for creating shapes in the geometry
#include <G4Sphere.hh> // for creating shapes in the geometry
#include <FTFP_BERT.hh> // our choice of physics list
#include <G4RandomDirection.hh> // for launching particles in random directions
#include <G4ThreeVector.hh>
#include <cstdlib>
struct my {
G4double straw_radius{0.1 * m};
G4double bubble_radius{0.2 * m};
G4double socket_rot {-90 * deg};
G4String particle_name{"geantino"};
G4double particle_energy{511 * keV};
G4ThreeVector particle_dir {};
};
auto my_generator(const my& my) {
return [&](G4Event* event) {
auto particle_type = n4::find_particle(my.particle_name);
auto vertex = new G4PrimaryVertex();
auto r = my.particle_dir.mag2() > 0 ? my.particle_dir : G4RandomDirection();
vertex -> SetPrimary(new G4PrimaryParticle(
particle_type,
r.x(), r.y(), r.z(),
my.particle_energy
));
event -> AddPrimaryVertex(vertex);
};
}
n4::actions* create_actions(my& my, unsigned& n_event) {
auto my_stepping_action = [&] (const G4Step* step) {
auto pt = step -> GetPreStepPoint();
auto volume_name = pt -> GetTouchable() -> GetVolume() -> GetName();
if (volume_name == "straw" || volume_name == "bubble") {
auto pos = pt -> GetPosition();
std::cout << volume_name << " " << pos << std::endl;
}
};
auto my_event_action = [&] (const G4Event*) {
n_event++;
std::cout << "end of event " << n_event << std::endl;
};
return (new n4:: actions{my_generator(my) })
-> set( (new n4:: event_action{ }) -> end(my_event_action) )
-> set( new n4::stepping_action{my_stepping_action});
}
auto my_geometry(const my& my) {
auto r_bub = my.bubble_radius;
auto r_str = my.straw_radius;
auto water = n4::material("G4_WATER");
auto air = n4::material("G4_AIR");
auto steel = n4::material("G4_STAINLESS-STEEL");
auto world = n4::box("world").cube(2*m).x(3*m).volume(water);
n4::sphere("bubble").r(r_bub) .place(air).in(world).at (1.3*m, 0.8*m, 0.3*m).now();
n4::tubs ("straw" ).r(r_str).z(1.9*m).place(air).in(world).at_x(0.2*m ).now();
n4 ::sphere("socket-cap" ).r(0.3*m).phi_delta(180*deg)
.sub(n4::box ("socket-hole").cube(0.4*m))
.name("socket")
.place(steel).in(world).rotate_x(my.socket_rot).at(1*m, 0, 0.7*m).now();
return n4::place(world).now();
}
int main(int argc, char* argv[]) {
unsigned n_event = 0;
my my;
G4int physics_verbosity = 0;
// The trailing slash after '/my_geometry' is CRUCIAL: without it, the
// messenger violates the principle of least surprise.
auto messenger = new G4GenericMessenger{nullptr, "/my/", "docs: bla bla bla"};
messenger -> DeclarePropertyWithUnit("straw_radius" , "m" , my. straw_radius );
messenger -> DeclarePropertyWithUnit("bubble_radius" , "m" , my.bubble_radius );
messenger -> DeclarePropertyWithUnit("socket_rot" , "deg", my.socket_rot );
messenger -> DeclarePropertyWithUnit("particle_energy" , "keV", my.particle_energy);
messenger -> DeclareProperty ("particle" , my.particle_name );
messenger -> DeclareProperty ("particle_direction", my.particle_dir );
messenger -> DeclareProperty ("physics_verbosity" , physics_verbosity );
n4::run_manager::create()
.ui("CHANGEME-EXE", argc, argv)
.macro_path("macs")
.apply_command("/my/straw_radius 0.5 m")
.apply_early_macro("early-hard-wired.mac")
.apply_cli_early() // CLI --early executed at this point
// .apply_command(...) // also possible after apply_early_macro
.physics<FTFP_BERT>(physics_verbosity)
.geometry([&] { return my_geometry(my); })
.actions(create_actions(my, n_event))
.apply_command("/my/particle e-")
.apply_late_macro("late-hard-wired.mac")
.apply_cli_late() // CLI --late executed at this point
// .apply_command(...) // also possible after apply_late_macro
.run();
// Important! physics list has to be set before the generator!
}
Now, let's split it into bite-sized portions. First we create a simple program that reads the number of events from CLI:
int main(int argc, char* argv[]) {
}
In order to run a simulation we need to create a run manager:
n4::run_manager::create()
.ui("CHANGEME-EXE", argc, argv)
.macro_path("macs")
.apply_command("/my/straw_radius 0.5 m")
.apply_early_macro("early-hard-wired.mac")
.apply_cli_early() // CLI --early executed at this point
// .apply_command(...) // also possible after apply_early_macro
.physics<FTFP_BERT>(physics_verbosity)
.geometry([&] { return my_geometry(my); })
.actions(create_actions(my, n_event))
.apply_command("/my/particle e-")
.apply_late_macro("late-hard-wired.mac")
.apply_cli_late() // CLI --late executed at this point
// .apply_command(...) // also possible after apply_late_macro
.run();
The run manager needs to be initialized with 3 attributes:
- A physics list
- A geometry
- A set of actions
To build the action set we need to provide a primary generator action. Other actions are optional. Note that the physics list must be instantiated and given to the run manager before the primary generator is instantiated. We provide the run manager with these attributes:
// Important! physics list has to be set before the generator!
here, my_geometry
, and my_generator
are the two functions we need to provide to run our simulation:
auto my_geometry(const my& my) {
auto r_bub = my.bubble_radius;
auto r_str = my.straw_radius;
auto water = n4::material("G4_WATER");
auto air = n4::material("G4_AIR");
auto steel = n4::material("G4_STAINLESS-STEEL");
auto world = n4::box("world").cube(2*m).x(3*m).volume(water);
n4::sphere("bubble").r(r_bub) .place(air).in(world).at (1.3*m, 0.8*m, 0.3*m).now();
n4::tubs ("straw" ).r(r_str).z(1.9*m).place(air).in(world).at_x(0.2*m ).now();
n4 ::sphere("socket-cap" ).r(0.3*m).phi_delta(180*deg)
.sub(n4::box ("socket-hole").cube(0.4*m))
.name("socket")
.place(steel).in(world).rotate_x(my.socket_rot).at(1*m, 0, 0.7*m).now();
return n4::place(world).now();
}
auto my_generator(const my& my) {
return [&](G4Event* event) {
auto particle_type = n4::find_particle(my.particle_name);
auto vertex = new G4PrimaryVertex();
auto r = my.particle_dir.mag2() > 0 ? my.particle_dir : G4RandomDirection();
vertex -> SetPrimary(new G4PrimaryParticle(
particle_type,
r.x(), r.y(), r.z(),
my.particle_energy
));
event -> AddPrimaryVertex(vertex);
};
}
and create_actions
is defined as:
n4::actions* create_actions(my& my, unsigned& n_event) {
auto my_stepping_action = [&] (const G4Step* step) {
auto pt = step -> GetPreStepPoint();
auto volume_name = pt -> GetTouchable() -> GetVolume() -> GetName();
if (volume_name == "straw" || volume_name == "bubble") {
auto pos = pt -> GetPosition();
std::cout << volume_name << " " << pos << std::endl;
}
};
auto my_event_action = [&] (const G4Event*) {
n_event++;
std::cout << "end of event " << n_event << std::endl;
};
return (new n4:: actions{my_generator(my) })
-> set( (new n4:: event_action{ }) -> end(my_event_action) )
-> set( new n4::stepping_action{my_stepping_action});
}
With this, our setup is ready, and we can start the simulation with
Don't forget to add the relevant includes
#include <n4-all.hh>
#include <G4GenericMessenger.hh>
#include <G4PrimaryParticle.hh>
#include <G4String.hh>
#include <G4SystemOfUnits.hh> // physical units such as `m` for metre
#include <G4Event.hh> // needed to inject primary particles into an event
#include <G4Box.hh> // for creating shapes in the geometry
#include <G4Sphere.hh> // for creating shapes in the geometry
#include <FTFP_BERT.hh> // our choice of physics list
#include <G4RandomDirection.hh> // for launching particles in random directions
#include <G4ThreeVector.hh>
#include <cstdlib>
Compiling the app
The app comes with its own CMakeLists.txt
:
{{#include ../../../examples/00-basic/CMakeLists.txt:full_file}}
This contains the following bits. First, we have the project initialization:
{{#include ../../../examples/00-basic/CMakeLists.txt:project_setup}}
It specifies the minimum cmake version, defines the project name and some lines necessary for code analysis in IDEs.
Next, we include nain4
and Geant4
in our project:
{{#include ../../../examples/00-basic/CMakeLists.txt:include_nain4_geant4}}
Note that we have chosen to use an existing installation of nain4
so this example runs out of the box when run within nain4
. If you were to copy this example and run it elsewhere, make sure to check How to make nain4 available in a Geant4/cmake project.
Finally, we create an executable file and link it to the relevant libraries.
{{#include ../../../examples/00-basic/CMakeLists.txt:create_exe_and_link}}
To compile the app we cd
into 00-basic
and run just compile-app
or
cmake -S . -B build && cmake --build build
Running the app
To run this app type just run-app <number of events>
(after compilation) from the 00-basic
directory. You can also achieve the same typing ./build/n4-00-basic <number of events>
.
As this simulation is extremelly basic, it doesn't produce anything. You will only see a Geant4 header.
How to upgrade your nain4 dependency
To upgrade nain4
to the lastest version available on master
:
-
nix flake lock --update-input nain4
This operation modifies
flake.lock
. Commit these modifications, either right now, or after step 3. -
just clean
This ensures that there are no old build products which still use the old version. (Hopefully this will no longer be necessary once
nain4
migrates its build infrastructure fromcmake
tomeson
.) -
Build, run and test the behaviour of your project, and fix any problems caused by incompatibility of your code with the new version of
nain4
. -
Commit these changes to your repository. Don't forget to commit the modified
flake.lock
; this is important in order to ensure that every checkout of a given commit in your repository uses exactly the same version of dependencies and that the versions of the dependencies are compatible with the version of your code.
Using other versions of nain4
It is possible to use versions of nain4
other than those available on the official master
branch. See here for details.
How to generate random numbers
nain4
offers a range of utilities to generate random numbers. This includes
scalars, tuples and 3-vectors. All these methods are declared in the
header <n4-random.hh>
which is transitively
included by <n4-utils.hh>
. They are gathered in the namespace nain4::random
.
Scalars
The methods described in this section generate a single number.
Continuous distributions
uniform()
: generates a random floating point number homogeneously distributed in the range [0, 1).uniform(low, high)
: generates a random floating point number homogeneously distributed in the range [low
,high
).uniform_half_width(hw)
: generates a random floating point number homogeneously distributed in the range [-hw
,hw
).uniform_width(w)
: generates a random floating point number homogeneously distributed in the range [-w/2
,w/2
).
Discrete distributions
biased_coin(p_true)
: generates a random boolean with probabilityp_true
of beingtrue
.fair_die(n_sides)
: generates a random unsigned integer in the range [0,n_sides - 1
] with equal probability.gen = biased_choice(weights)
: provides a generator of unsigned integers in the range [0,weights.size() - 1
] with givenweights
. The generator must be called to obtain a number:random_number = gen()
.
Tuples
random_in_disc(r)
: generates a pair of floating point numbers{x, y}
that satisfyx^2 + y^2 <= r^2
.
3-vectors
The methods described in this section generate G4ThreeVector
s.
random_in_sphere(r)
: generates a vector of floating point numbers{x, y, z}
that satisfyx^2 + y^2 + z^2 <= r^2
.
Directions
nain4
provides the direction
builder to help generate random directions. By
default this tool generates unit vectors distributed isotropically in 4π, but it
can be configured to restrict the generation to certain directions.
The general usage pattern is
auto generator = n4::random::direction{}.<optional extra specifications>;
auto one_random_unit_vector = generator.get();
The <optional extra specifications>
are described below. θ
(theta) is the
polar angle measured with respect to the positive z-axis and φ (phi) is the
azimuthal angle with φ=0
in the plane of the positive x-axis and increasing
towards the positive y-axis, unless the axes are re-oriented with
.rotate_{x,y,z}
.
The following specifiers (with hopefully self-explanatory names) restrict the angles of the generated directions
-
{min,max}_theta(<angle in radians>)
or(<angle in degrees> * deg)
- range:
[0, π]
or[0, 180°]
- range:
-
{min,max}_cos_theta(<ratio>)
range:[-1, 1]
-
{min,max}_phi(<angle in radians>)
or(<angle in degrees> * deg)
- range:
[0, 2π]
or[0, 360°]
- range:
-
rotate_{x,y,z}(<angle in radians>)
or(<angle in degrees> * deg)
rotation around the specified axis. range:
[0, 2π]
or[0, 360°]
-
rotate(<G4RotationMatrix>)
-
bidirectional()
: accept both the current selection and its reflection in the origin. -
exclude()
: invert the selection. Generates the complement of the selected criteria. Bear in mind that this method is more computationally expensive as it needs to generate directions until the result is not rejected. Very restricted directionalities are discouraged.
Examples
TODO: add pictures
-
Isotropic emission:
n4::direction{}
-
Opening angle around the positive z axis:
n4::direction().max_theta(theta)
-
Opening angle around the positive x axis:
n4::direction().max_theta(theta).rotate_y(90 * deg)
-
Only one octant:
n4::direction().min_cos_theta(0).min_phi(CLHEP::halfpi).max_phi(CLHEP::pi)
-
Back-to-back beams around the positive z axis with some opening angle:
n4::direction().max_theta(theta).bidirectional()
-
Isotropic except for some small angle around the negative z-axis:
n4::direction().min_theta(7*CLHEP::pi/8).exclude()
Explanation
- Functions better than classes
- Closures
- Always prefer
std::vector
over C-arrays.
Placement: Laziness and Accumulation
The auxiliary placement methods belong to an object of type n4::place
.
Instances of this type can accumulate state.
This can be useful for reducing repetition: compare the following two code samples, which produce identical results
// Store complicated configuration
auto place_box = n4::box("box").cube(1*m).place(gold).in(world).at_x(1*m);
// Reuse it many times, emphasizing what differs: rot_z(angle) and copy_no(N)
place_box.clone().rot_z(23*deg).copy_no(1).now();
place_box.clone().rot_z(54*deg).copy_no(2).now();
place_box.clone().rot_z(91*deg).copy_no(3).now();
// Repeat complicated configuration explicitly: signal lost in the noise
n4::box("box").cube(1*m).place(gold).in(world).at_x(1*m).copy_no(1).rot_z(23*deg).now();
n4::box("box").cube(1*m).place(gold).in(world).at_x(1*m).copy_no(2).rot_z(54*deg).now();
n4::box("box").cube(1*m).place(gold).in(world).at_x(1*m).copy_no(3).rot_z(91*deg).now();
However, note the presence of .clone()
on each line of the first sample.
Without it, the effect of each call to rot_z
would be accumulated in
place_box
place_box = n4::box("box").cube(1*m).place(copper).in(world);
// Place boxes at 1, 2 and 3 metres
for (auto k : {1,2,3}) { place_box.clone().at_x(k*m).now(); }
// Place boxes at 1, 3 and 6 metres
for (auto k : {1,2,3}) { place_box .at_x(k*m).now(); }
Displacements and rotations are not commutative
TODO
Input types for boolean solids' methods
nain4
provides a collection of methods to create boolean solids
described in n4 boolean solids. These
functions accept either a n4::shape
or G4VSolid*
. However, when
building a geometry, it's fairly easy to get confused with the
different stages of the creation of components (solid
,
(logical)volume
, place(ment)
). This can produce errors that might
be difficult to understand. nain4
attempts to clarify them
by providing explicit compile-time messages for the most
likely cases. Here we describe all these errors and try to give a
through explanation of their causes and how to handle them.
For this explanation we will use the addition of two boxes as an example, but the rationale does not depend on the shapes or the methods used. Here is the definition of the boxes:
n4::box box1 = n4::box("box1").cube(1*m);
n4::box box2 = n4::box("box2").cube(2*m);
G4Box* box3 = new G4Box{"box3", 3*m, 3*m, 3*m};
The correct syntax
For reference, here are three possible options to create a union solid from these boxes:
box1.add(box2 ); // OK
box1.add(box2.solid()); // OK
box1.add(box3 ); // OK
Here, add
could be replaced with its alias join
to produce the same
result. Also, notice that the union solid is not created until
.solid()
, volume(...)
or .place(...)
is called on the result of
add(...)
.
Possible errors
All the errors share the same message, with a hint of what the type of the incorrect input is. The error message looks like this:
[n4::boolean_shape::<METHOD>]
Attempted to create a boolean shape using <TYPE_HINT>.
Only n4::shape and G4VSolid* are accepted.
For more details, please check https://jacg.github.io/nain4/explanation/boolean_solid_input_types.md
where <METHOD>
indicates the method which generated the error (this
might help in the case of chained operations) and <TYPE_HINT>
indicates the input type that gave rise to the error.
<TYPE_HINT> = a G4LogicalVolume
The variable passed to the boolean-creation method is of type
G4LogicalVolume*
. The most likely causes are:
-
[G4-style] this variable was bound to a
new G4LogicalVolume(...)
, e.g.G4Box* box2_solid = new G4Box(...); G4LogicalVolume* box2_logic = new G4LogicalVolume(box2_solid, ...); box1.add(box2_logic); // WRONG
The solution is to pass
box2_solid
instead ofbox2_logic
. -
[n4-style] you called
.volume(...)
on the input solid, e.g.box1.add(box2.volume(...)); // WRONG
The solution is to remove the call to
volume(...)
or call.solid()
instead.
<TYPE_HINT> = a G4PVPlacement
The variable passed to the boolean-creation method is of type
G4PVPlacement*
. The most likely causes are:
-
[G4-style] this variable was assigned to
new G4PVPlacement(...)
, e.g.G4Box* box2_solid = new G4Box(...); G4LogicalVolume* box2_logic = new G4LogicalVolume(box2_solid, ...); G4PVPlacement* box2_place = new G4PVPlacement(box2_logic, ...); box1.add(box2_place); // WRONG
The solution is to pass
box2_solid
instead ofbox2_place
. -
[n4-style] you called
.place(...).other().methods().now()
on the input solid, e.g.box1.add(box2.place(...).other().methods().now()); // WRONG
The solution is to remove the call to
place(...).other().methods().now()
or call.solid()
instead.
<TYPE_HINT> = a n4::place
The variable passed to the boolean-creation method is of type
n4::place
. The most likely cause is that you called .place(...)
(without calling .now()
) on the input solid, e.g.
box1.add(box2.place(...).other().methods()); // WRONG
The solution is to remove the call to place(...).other().methods()
or
call .solid()
instead.
<TYPE_HINT> = an unknown type
The type of the variable passed to the boolean-creation method is
neither a valid one (n4::shape
, G4VSolid*
) nor one of the specific
cases described above. In this case nain4
cannot provide any
help. You must find the latest assignment of this variable and
understand what its type is. A LSP plugin in you text editor might
help.
Requesting the handling of other cases
If you find that there is another case that nain4
should handle,
please open an issue at https://github.com/jacg/nain4/issues
!
Return value of G4VSensitiveDetector::ProcessHits
G4VSensitiveDetector
's method ProcessHits
must be implemented by the user. nain4
provides n4::sensitive_detector
to ease implementing G4VSensitiveDetector
subclasses. Because ProcessHits
is mandatory, nain4
obliges the user to provide an implementation as a construction argument to n4::sensitive_detector
.
The aforementioned method returns bool
for historical reasons. (This seems to be undocumented, but see this Geant4 forum comment.)
The summary is:
- The actual value returned doesn't matter: it is ignored by G4.
- One day the Geant4 developers may decide to change the return type to
void
at which point old code will give compilation errors.
Why nix?
TODO
Reference
Library headers
nain4
provides headers of different granularity, for convenience in different situations.
<n4-all.hh>
Gives access to all nain4
components. This header is meant to be used for exploration:
-
when you are just getting started with
nain4
and don't want to be distracted by the need to find specific headers for specific situations -
when you want LSP to help you discover available features
-
When your whole
nain4
application still lives in a single source file
As your program grows and is split into multiple files, and you gain more experience with nain4
, it would be more appropriate to use the more fine-grained headers, described below.
-
<n4-main.hh>
Groups together utilities that are required to write the
main
function of anain4
application. These are also available via the separate headers:<n4-run-manager.hh>
:nain4
interface for configuring the Geant4 run manager<n4-mandatory.hh>
:nain4
utilities for concise implementation of the user-defined classes that must be registered with the run manager
-
<n4-geometry.hh>
Groups together functionality that is typically used in defining detector geometries. These are also available via the separate headers:
<n4-material>
: finding existing materials; constructing new materials<n4-shape>
: construction ofG4VSolid
s<n4-boolean-shape>
: construction of boolean solids<n4-volume>
creation of logical volumes<n4-place>
creation and placement of physical volumes<n4-sensitive>
nain4
utilities for concise implementation ofG4VSensitiveDetector
-
<n4-utils.hh>
Various ready-made conveniences to alleviate the tedium and verbosity of oft-encountered tasks. These are also available via the separate headers:
<n4-constants.hh>
: physical constants not provided byCLHEP
<n4-inspect.hh>
: finding existing geometry components, materials, etc.<n4-random.hh>
: random number generation<n4-sequences.hh>
: convenient creation of sequences of numerical data<n4-stream.hh>
: redirecting or silencing C++ output streams
-
<n4-testing.hh>
Utilities to help with writing automated tests for
nain4
applications. These are also available via the separate headers:<n4-defaults.hh>
: ready-made dummy application components<n4-geometry-iterators.hh>
: iterating over all sub-elements of a geometry
-
Other headers
<n4-exceptions.hh>
: exceptions relating to situations arising in nain4 code
Constructing solids and logical volumes
Header: <n4-shapes.hh>
Summary
Constructing a G4VSolid
auto ball = n4::sphere("ball").r(1.2*m).solid();
auto radius = 1.2*m;
auto ball = new G4Sphere("ball", 0, radius, 0, CLHEP::twopi, 0, CLHEP::pi);
(In this specific example, n4::sphere
notices that it would be more efficient to create a G4Orb
instead of a G4Sphere
and does that for you automatically.)
Constructing a G4LogicalVolume
Frequently, after having made a G4VSolid
you immediately use it to make a G4LogicalVolume
with the same name. nain4
allows you to do this in a single step:
auto copper = n4::material("G4_Cu");
auto ball = n4::sphere("ball").r(1.2*m).volume(copper);
auto copper = G4NistManager::Instance() -> FindOrBuildMaterial("G4_Cu");
auto radius = 1.2*m;
auto ball_solid = new G4Sphere("ball", 0, radius, 0, CLHEP::twopi, 0, CLHEP::pi);
auto ball = new G4VLogicalVolume(ball_solid, copper, "ball");
If you need to do this as two separate steps
auto copper = n4::material("G4_Cu");
auto ball_solid = n4::sphere("ball").r(1.2*m).solid();
auto ball = n4::volume(ball_solid, copper);
Not all G4VSolid
s are supported by the nain4::shape
interface, yet. In such cases n4::volume
lets you combine the construction of a solid and corresponding logical volume in a single step. Here is how you could do it if n4::sphere
did not exist:
auto copper = n4::material("G4_Cu");
auto radius = 1.2*m;
auto ball = n4::volume<G4Sphere>("ball", copper, 0, radius, 0, CLHEP::twopi, 0, CLHEP::pi);
The arguments passed after the name and material, are forwarded to the specified G4VSolid
's constructor.
Placing a volume
Should you want to place your G4LogicalVolume
immediately, nain4
allows you to include this in a single step, too:
auto safe = ...
auto gold = n4::material("G4_Au");
n4::box("nugget").cube(2*cm).place(gold).in(safe).now();
auto safe_solid = ...
auto safe = ...
auto gold = G4NistManager::Instance() -> FindOrBuildMaterial("G4_Au");
auto nugget_solid = new G4Box("nugget", 2*cm/2, 2*cm/2, 2*cm/2);
auto nugget_logical = new G4VLogicalVolume(nugget_solid, gold, "nugget");
new G4PVPlacement(nullptr, {}, nugget_logical, "nugget", safe, false, 0);
However, frequently you need to keep a handle to the logical volume, in order to be able to place things into it later. In such cases you would break this into two separate steps:
auto safe = ...
auto gold = n4::material("G4_Au");
auto nugget = n4::box("nugget").cube(2*cm).volume(gold);
n4::place(nugget).in(safe).now();
nain4
's placement utilities have a rich interface, which is described in Placement of physical volumes.
Summary
G4VSolid * s = n4::SOLID("name", ...).solid();
G4VLogicalVolume* v = n4::SOLID("name", ...).volume(material);
G4PVPlacement * p = n4::SOLID("name", ...).place (material).in(volume) ... .now();
Specifying Dimensions
The G4VSolid
s, and hence also the n4::shape
s, are parameterized by combinations of four principal types of coordinate
- Cartesian lengths,
x
,y
andz
- Radial length,
r
- Azimuthal angle,
φ
- Polar angle,
θ
nain4
provides a consistent set of methods for setting these, in any n4::shape
that uses them. These methods are described here, the n4::shapes
are described in the next section.
All the methods provided by nain4
have short, but explicit names. This removes the need for the user to remember the order of the arguments while highlighting their meaning.
Cartesian lengths
The principal methods for setting Cartesian lengths are
x
half_x
and their equivalents for y
and z
.
Explicit names
Unlike Geant4, nain4
favours the use of full lengths instead of half-lengths.
Using half-lengths creates noise by either introducing /2
operations
continuously or by defining variables with long names to indicate the subtle but
important distinction between half- and full-lengths. For instance, in order to
create a cube of side 1 m in pure G4
:
auto box_half_length = 0.5*m;
G4Box* box1 = new G4Box("box", box_half_length, box_half_length, box_half_length);
// or
auto box_length = 1*m;
G4Box* box2 = new G4Box("box", box_length/2, box_length/2, box_length/2);
In contrast, in nain4
one can simply write
auto box = n4::box("box").cube(1*m);
Overriding
If you set a Cartesian length more than once in the same shape, the last setting overrides previous ones. For example:
.x(1*m).half_x(3*m) // `x` set to 6 m
.x(1*m). x(3*m) // `x` set to 3 m
Convenient alternatives
n4::shape
s which depend on more than one Cartesian length, typically provide extra methods for setting various combinations, for example n4::box
offers extra methods cube
, xyz
, xy
, xz
and yz
along with their half_
variants.
Radial length: r
Three methods are provided for specifying the two degrees of freedom in radial lengths:
r_inner
r_delta
r
with the constraint r = r_inner + r_delta
. Thus valid combinations of these methods are
Methods used | Implied value | ||||
---|---|---|---|---|---|
r_inner | r_delta | r | |||
r | 0 | r | |||
r_inner | r | r - r_inner | |||
r_delta | r | r - r_delta | |||
r_inner | r_delta | 0 | r_inner + r_delta |
Providing too few or too many values results in a run-time error.
Some shapes, such as n4::cons
(G4Cons
), have multiple radii. In such cases the method names acquire a number, to distinguish between them r*
-> r1*
, r2*
.
Azimuthal angle: φ
Three methods are provided for specifying the two degrees of freedom in azimuthal angles:
phi_start
phi_delta
phi_end
with the constraint phi_end = phi_start + phi_delta
. Thus valid combinations of these methods are
Methods used | Implied value | ||||
---|---|---|---|---|---|
phi_start | phi_delta | phi_end | |||
0 | 2π | 2π | |||
phi_start | 2π - start | 2π | |||
phi_delta | 0 | δ | |||
phi_end | 0 | end | |||
phi_delta | phi_end | end - δ | |||
phi_start | phi_end | end - start | |||
phi_start | phi_delta | start + δ |
Providing too few or too many values results in a run-time error.
Polar angle: θ
Three methods are provided for specifying the two degrees of freedom in polar angles:
theta_start
theta_delta
theta_end
with the constraint theta_end = theta_start + theta_delta
. Thus valid combinations of these methods are
Methods used | Implied value | ||||
---|---|---|---|---|---|
theta_start | theta_delta | theta_end | |||
0 | π | π | |||
theta_start | π - start | π | |||
theta_delta | 0 | δ | |||
theta_end | 0 | end | |||
theta_delta | theta_end | end - δ | |||
theta_start | theta_end | end - start | |||
theta_start | theta_delta | start + δ |
Providing too few or too many values results in a run-time error.
Common methods
All n4::SOLID
s share the following methods:
- Builder methods
- Boolean solid methods:
add()
/join()
sub()
/subtract()
inter()
/intersect()
- Optional logical volume settings:
sensitive(sensitive-detector)
- TODO maybe field manager, user limits, optimize
Available Shapes
n4::box
Constructs G4Box
: cuboid with side lengths x
, y
and z
. Within its frame of reference it is centred on the origin with sides parallel to the x
/y
/z
axes. Displacements and rotations can be applied with .place(material)
.
Example
G4Box* box = n4::box("box").xz(10*cm).y(50*cm).solid();
auto cross_section_length = 10*cm, y_length = 50*cm;
G4Box* box = new G4Box("box", cross_section_length/2, y_length/2, cross_section_length/2);
Methods
Full-length methods
All these methods take full (as opposed to half-) lengths:
x(lx)
,y(ly)
,z(ly)
: set one dimension.xy(l)
,xz(l)
,yz(l)
: set two dimensions to the same value.xyz(lx, ly, lz)
,xyz(g4-three-vector)
: set three dimensions independentlycube(l)
: set all dimensions to the same value.
Note the, perhaps surprising, difference between .xyz()
and the .xy()
-.xz()
-.yz()
triumvirate: The latter assign a single value to multiple coordinates; the former accepts a separate value for each coordinate it sets.
Half-length methods
All the aforementioned full-length methods have alternatives which accept half-lengths: half_x(lx/2)
, half_cube(l/2)
, half_xy(lxy)
, etc.
Overriding
If any value is specified more than once, the last setting overrides any earlier ones. Thus, the following three lines are equivalent.
.cube(1*m ).z(2*m)
.xyz (1*m, 1*m, 1*m).z(2*m)
.xy (1*m ).z(2*m)
While the first two work, the last one states the intent most clearly.
See the sections about setting Cartesian lengths for more details.
n4::sphere
Constructs G4Sphere
or G4Orb
, depending on values provided.
G4Sphere
: section of a spherical shell, between specified azimuthal (φ) and polar (θ) angles. Within its frame of reference, φ is measured counterclockwise WRT the x-axis when viewed from positive z; θ is measured WRT positive z. Displacements and rotations can be applied with.place(material)
.G4Orb
: special case ofG4Sphere
.
Examples
A solid sphere
G4Orb* ball = n4::sphere("ball").r(1*m).solid();
G4Orb* ball = new G4Orb("ball", 1*m);
thus nain4
helps you avoid the common mistake of creating an equivalent (but less efficient) G4Sphere
instead
G4Sphere* ball = new G4Sphere("ball", 0, 1*m, 0, CLHEP::twopi, 0, CLHEP::pi);
A hollow sphere
G4Sphere* hollow = n4::sphere("hollow").r(2*m).r_delta(10*cm).solid();
auto outer = 2*m, thickness = 10*cm;
G4Sphere* hollow = new G4Sphere("hollow", outer - thickness, outer, 0, CLHEP::twopi, 0, CLHEP::pi);
A spherical wedge
G4Sphere* wedge = n4::sphere("wedge").r(1*m).phi_start(20*deg).phi_end(30*deg).solid();
auto start_phi = 20*deg, end_phi = 30*deg;
G4Sphere* wedge = new G4Sphere("wedge", 0, 1*m, start_phi, end_phi - start_phi, 0, CLHEP::pi);
Methods
r_inner(l)
r_delta(l)
r(l)
phi_start(a)
phi_delta(a)
phi_end(a)
theta_start(a)
theta_delta(a)
theta_end(a)
See the sections about setting radial lengths, azimuthal angles and polar angles for more details.
n4::tubs
Constructs G4Tubs
: tube or tube segment. Within its frame of reference, it is parallel to and centred on the z-axis; φ is measured counterclockwise WRT the x-axis when viewed from positive z. Displacements and rotations can be applied with .place(material)
.
Examples
A solid cylinder
G4Tubs* cylinder = n4::tubs("cylinder").r(1*m).z(2*m).solid();
auto radius = 1*m;
auto z_length = 2*m;
G4Tubs* cylinder = new G4Tubs("cylinder", 0, radius, z_length/2, 0, CLHEP::twopi);
A tube
G4Tubs* tube = n4::tubs("tube").r(1*m).r_delta(10*cm).z(2*m).solid();
auto z_length = 2*m, outer_radius = 1*m, thickness = 10*cm;
G4Tubs* tube = new G4Tubs("tube", outer_radius - thickness, 1*m, z_length/2, 0, CLHEP::twopi);
A cylindrical wedge
G4Tubs* wedge = n4::tubs("wedge").r(1*m).z(2*m).phi_start(20*deg).phi_end(30*deg).solid();
auto z_length = 2*m, radius = 1*m, start_phi = 20*deg, end_phi = 30*deg;
G4Tubs* wedge = new G4Tubs("wedge", 0, radius, z_length/2, start_phi, end_phi - start_phi);
Methods
z()
half_z()
r_inner(l)
r_delta(l)
r(l)
phi_start(a)
phi_delta(a)
phi_end(a)
See the sections about setting Cartesian lengths, radial lengths and azimuthal angles for more details.
n4::trd
Constructs G4Trd
: frustrum (a.k.a. truncated pyramid) with two
parallel rectangular faces and four trapezoidal faces. The rectangular
faces are perpendicular to the z axis and their sides are parallel to
the x and y axes. The bottom face (placed at negative z) has side
lengths x1
, y1
, and the top face (placed at positive z) has side
lengths x2
, y2
. Both faces are separated by a distance
z
. Within its frame of reference it is centred on the
origin. Displacements and rotations can be applied with
.place(material)
.
Example
G4Trd* trd = n4::trd("trd").xy1(10*cm).xy2(5*cm).z(50*cm).solid();
auto cross_section_bottom = 10*cm, cross_section_top = 5*cm, z_length = 50*cm;
G4Trd* trd = new G4Trd("trd", cross_section_bottom/2, cross_section_top/2, cross_section_bottom/2, cross_section_top/2, z_length/2);
Methods
Full-length methods
All these methods take full (as opposed to half-) lengths:
x1(lx)
,x2(ly)
,y1(lx)
,y2(ly)
: set one dimension of one rectangular face. 1 Refers to the bottom face, while 2 refers to the top face.xy1(l)
,xy2(l)
: set both dimensions of one face. 1 Refers to the bottom face, while 2 refers to the top face.z(l)
: set the distance between the two parallel faces.
Half-length methods
All the aforementioned full-length methods have alternatives which
accept half-lengths: half_x1(lx/2)
, half_xy2(lxy/2)
,
half_z(lz/2)
, etc.
See the sections about setting Cartesian lengths for more details.
n4::cons
Constructs G4Cons
: truncated cone or cone section. Within its frame of reference, it is parallel to and centred on the z-axis; φ is measured counterclockwise WRT the x-axis when viewed from positive z. Displacements and rotations can be applied with .place(material)
.
Examples
A solid truncated cone
G4Tubs* solid_cone = n4::tubs("solid_cone").r1(1*m).r2(2*m).z(3*m).solid();
auto radius_1 = 1*m, radius_2 = 2*m, z_length = 3*m;
G4Cons* solid_cone = new G4Cons("solid_cone", 0, radius_1, 0, radius_2, z_length/2, 0, CLHEP::twopi);
A hollow cone
G4Cone* hollow_cone = n4::cone("hollow_cone").r1(1*m).r2(2*cm).r_delta(10*cm).z(3*m).solid();
auto radius_1 = 1*m, radius_2 = 2*m, thickness = 10*cm, z_length = 3*m;
G4Cons* hollow_cone = new G4Cons("hollow_cone", radius_1 - thickness, radius_1, radius_2 - thickness, radius_2, z_length/2, 0, CLHEP::twopi);
A conical wedge
G4Cons* wedge = n4::cons("wedge").r1(1*m).r2(2*m).z(3*m).phi_start(20*deg).phi_end(30*deg).solid();
auto radius_1 = 1*m, radius_2 = 2*m, z_length = 3*m, start_phi = 20*deg, end_phi = 30*deg;
G4Cons* wedge = new G4Cons("wedge", 0, radius_1, 0, radius_2, z_length/2, start_phi, end_phi - start_phi);
Methods
z()
half_z()
r1_inner(l)
,r1_delta(l)
,r1(l)
: Sets the radial limits of the bottom (negative z) sider2_inner(l)
,r2_delta(l)
,r2(l)
: Sets the radial limits of the top (positive z) sider_delta
: Sets bothr1_delta
andr2_delta
phi_start(a)
phi_delta(a)
phi_end(a)
See the sections about setting Cartesian lengths, radial lengths and azimuthal angles for more details.
Obviously missing shapes
n4::torus
n4::trap
n4::para
n4::polycone
n4::elliptical_tube
n4::ellipse
n4::ellipsoid
n4::elliptical_cone
n4::tet
(...rahedron)
n4::hype
(...rbolic cone)
n4::twisted_*
n4::breps
Boundary Represented Solids
n4::tesselated_solids
TODO: document envelope_of
Placement of physical volumes
Summary
World volumes
nain4
's placement API can be used to place a logical volume as it is being created
auto air = n4::material("G4_AIR");
n4::box("world").cube("1*m").place(air).now();
auto air = G4NistManager::Instance() -> FindOrBuildMaterial("G4_AIR");
auto world_size = 1*m;
auto world_solid = new G4Box("world", world_size/2, world_size/2, world_size/2);
auto world_logical = new G4LogicalVolume(world_solid, air, "world")
new G4PVPlacement(nullptr, {}, world_logical, "world", nullptr, false, 0);
or to place an existing logical volume
auto air = n4::material("G4_AIR");
G4LogicalVolume* existing_logical_volume = n4::box("world").cube(1*m).volume(air);
n4::place(existing_logical_volume).now();
(The Geant4 equivalent of this sample is essentially identical to that of the previous sample.)
In the above examples, no mother volume was specified in the placement phase, therefore these volumes are world volumes: all other volumes must be placed within the world volume—either directly, or in its daughters—and they must not protrude beyond the bounds of the world volume.
NOTE: the volume is not placed until the `.now()`method is called.
See Placement: Laziness and Accumulation for more details.Daughter volumes
- Daughter volumes are placed inside a mother volume with the
.in(mother)
method. - The position and orientation, as well as other information about the placement, can be specified with the placement API's auxiliary methods.
Just like in the case of world volumes, a logical volume can be placed as it is being created
auto copper = n4::material("G4_Cu");
n4::box("box").cube(1*cm).place(copper).in(world).at_y(3*cm).rot_z(30*deg).now();
auto copper = G4NistManager::Instance() -> FindOrBuildMaterial("G4_Cu");
auto box_size = 1*cm;
auto box_solid = new G4Box("box", box_size/2, box_size/2, box_size/2);
auto box_logical = new G4LogicalVolume(box_solid, copper, "box")
auto rot_z_30 = new G4RotationMatrix();
rot_z_30 -> rotateZ(30*deg);
new G4PVPlacement(rot_z_30, {0, 3*cm, 0}, box_logical, "box", world, false, 0);
or in two separate steps
auto copper = n4::material("G4_Cu");
auto box = n4::box("box").cube(1*cm).volume(copper);
n4::place(box).in(world).at_y(3*cm).rot_z(30*deg).now();
Methods
-
Mother volume
in(logical-volume)
in(physical-volume)
// not implemented yetin(n4::place)
// not implemented yet
If no mother volume is specified, the placed volume becomes the world volume. There can be only one world volume in a Geant4 application.
nain4
issues a run-time error if more than one world volume is created. -
Translations
at(G4ThreeVector)
at(x,y,z)
at_x(x)
,at_y(y)
,at_z(z)
The above methods specify the displacement of the daughter volume's origin with respect to the mother volume's origin.
By default there is no displacement between the two.
The effect of these methods is cumulative. Thus the following are equivalent
.at (1*m, 2*m, 3*m) .at_x(1*m).at_y(2*m).at_z(3*m)
as are these
.at_x(101*m) .at_x(1*m).at_x(100*m)
-
Rotations
rotate(G4RotationMatrix)
,rot(G4RotationMatrix)
(identical meanings)rotate_x(angle)
,rotate_y(angle)
,rotate_z(angle)
rot_x(angle)
,rot_y(angle)
,rot_z(angle)
(shorter-named equivalents of the above)
The above methods specify the rotation of the daughter volume with respect to its mother volume.
By default there is no rotation between the two.
The effect of these methods is cumulative. Thus, the following two lines are equivalent
.rot_x(60*deg).rot_x(30*deg) .rot_x(90*deg)
NOTE: Rotations around distinct axes are not commutative. Thus the following two lines are NOT equivalent
.rot_x(90*deg).rot_y(90*deg) .rot_y(90*deg).rot_x(90*deg)
-
Transformations
transform(G4Transform3D*)
,trans(G4Transform3D*)
(identical meanings)
The above methods specify the overall transformation of the daughter volume with respect to its mother volume.
-
name(string)
By default the physical volume inherits the name of the logical volume being placed. This method allows overriding the inherited name. -
copy_no(N)
Useful to distinguish between multiple placements of a single logical volume. Implicitly appends "-N" to the physical volume's name, in addition to setting an copy-number attribute. -
Overlap checking
Besides daughter volumes being placed entirely within their mothers, there should be no overlap between volumes. If this happens, the behaviour of tracking becomes inconsistent.
The following methods control overlap checking.
When overlap checking is enabled, Geant4 will throw a runtime error if overlaps are detected, as opposed to allowing your program to run with an ill-defined geometry. However, this process may consume significant resources and slow down the construction of the geometry. Therefore, you may not want to have overlap checking enabled permanently.
-
check_overlaps()
Activates checking for this placement only. -
n4::place::check_overlaps_switch_on()
Activate checking for all placements until further notice. -
n4::place::check_overlaps_switch_off()
Deactivate checking until further notice, for placements which do not have it explicitly enabled with.check_overlaps()
.
-
-
Controlling state accumulation
clone()
Create an independent copy of the current placement state.
Observe the difference in the effect of these two loops, whose source differs only in the presence/absence of
.clone()
place_box = n4::box("box").cube(1*m).place(copper).in(world); // Place boxes at 1, 2 and 3 metres for (auto k : {1,2,3}) { place_box.clone().at_x(k*m).now(); } // Place boxes at 1, 3 and 6 metres for (auto k : {1,2,3}) { place_box .at_x(k*m).now(); }
Constructing boolean solids
Managing nain4 versions
The version of nain4
used by your project is determined by two files in its top-level directory:
flake.nix
flake.lock
By default, flake.nix
contains the line
nain4.url = "github:jacg/nain4";
which states that nain4
should be taken from the default branch (master
) of the nain4
repository owned by user jacg
on GitHub.
However
- the official
master
branch receives frequent updates, and - you don't want the version of
nain4
that your project uses to change without your knowledge and consent.
This is the purpose of flake.lock
: it pins the precise version (the commit id) of all of your project's flake.nix
-specified dependencies.
Upgrading to the lastest version of nain4
available of master
If you want to upgrade to the most recent version of nain4
available on its master
branch, see here.
Using other versions of nain4
By default, flake.nix
instructs your system to use the latest master
and flake.lock
pins it to a specific version of master
. But you can specify other versions of any dependency, including ones which are not located in the official repository. Here a few examples of how you might modify the corresponding line in your project's flake.nix
:
-
Use a preview of a feature being developed on the
some-experimental-feature
branch in thenain4
repo.nain4.url = "github:jacg/nain4?branch=some-experimental-feature"
-
Use a specific tagged version of
nain4
.nain4.url = "github:jacg/nain4?ref=v1.2.3"
-
Use a specific commit available in the
nain4
repo.nain4.url = "github:jacg/nain4?ref=9b4699ca25539d41bd1f5965a341be5e8ff862f1"
-
Use a version of
nain4
made available in a repo other than the officialnain4
one.nain4.url = "github:gonzaponte/nain4?branch=cool-idea"
-
Use a version of
nain4
being developed on your own machine.nain4.url = "/home/me/src/nain4?branch=cool-idea"
For the full details, see the Nix manual.
Don't forget that, after switching to a different version of nain4
, you may need to just clean
and recompile your code, to avoid mysterious and annoying errors caused by stale build artefacts. (Hopefully this will be done for you automatically once nain4
migrates its build infrastructure from cmake
to meson
.)