diff --git a/Project.toml b/Project.toml index 2a6ed85..c7e0f91 100644 --- a/Project.toml +++ b/Project.toml @@ -9,6 +9,7 @@ DisplayAs = "0b91fe84-8a4c-11e9-3e1d-67c38462b6d6" FHist = "68837c9b-b678-4cd5-9925-8a54edc8f695" GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a" Geant4 = "559df036-b7a0-42fd-85df-7d5dd9d70f44" +Geant4_jll = "872b6946-528a-5ac7-9145-d37eec569368" GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" IGLWrap_jll = "283677c1-8365-580c-84e5-ef4b5d190868" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" @@ -22,6 +23,7 @@ Rotations = "6038ab10-8711-5258-84ad-4b1120ba62dc" [compat] FHist = "0.11" Geant4 = "0.2" +Geant4_jll = "11.2" Literate = "2.20" [extras] diff --git a/advanced/CustomLib/UserLib.ipynb b/advanced/CustomLib/UserLib.ipynb index 584e77c..4b1ba36 100644 --- a/advanced/CustomLib/UserLib.ipynb +++ b/advanced/CustomLib/UserLib.ipynb @@ -3,7 +3,7 @@ { "cell_type": "markdown", "source": [ - "# Calling C++ code from Julia\n", + "# Calling Custom C++ library\n", "\n", "An example of calling user libraries that can provide additional Geant4 functionally that is not\n", "provided by the set of wrapped classes. In this example we define a custom solid called `RoundCube`,\n", @@ -49,8 +49,24 @@ "cell_type": "code", "source": [ "prefix = Geant4.Geant4_jll.artifact_dir\n", - "dlext = Libdl.dlext\n", - "# Compilation of the custom library\n", + "dlext = Libdl.dlext;\n", + "# Compilation of the custom library" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "The custom library is defined in the C++ file `UserLib.cpp`. Please note that the\n", + "callable functions are defined with the `extern \"C\"` attribute to avoid name mangling." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ "Base.run(`c++ -O2 -shared -fPIC -std=c++17 -I$prefix/include/Geant4\n", " -Wl,-rpath,$prefix/lib -L$prefix/lib\n", " -lG4geometry -lG4materials -lG4global -lG4clhep\n", @@ -84,7 +100,9 @@ "cell_type": "markdown", "source": [ "## Testing the custom library\n", - "We create a `RoundCube` with side length `100` and radius `10` and draw the distance to the outside" + "We create a `RoundCube` with side length `100` and radius `10` and draw the distance to the outside of the solid\n", + "from a number of randomly distributed points in a random directions. This should get a nice image of the\n", + "surface `RoundCube`. It is exercising `Inside` and `DistanceToOut` methods of the custom solid." ], "metadata": {} }, @@ -92,8 +110,8 @@ "outputs": [], "cell_type": "code", "source": [ - "rcube = createRoundCube(10cm, 1cm)\n", - "img = drawDistanceToOut(rcube[], 100000)\n", + "rcube = createRoundCube(10cm, 1cm) # returns a CxxPtr{G4VSolid}\n", + "img = drawDistanceToOut(rcube[], 100000) # implemented in G4Vis ext. It expects a G4VSolid.\n", "display(\"image/png\", img)" ], "metadata": {}, diff --git a/advanced/CustomLib/UserLib.jl b/advanced/CustomLib/UserLib.jl index 1620348..1360d87 100644 --- a/advanced/CustomLib/UserLib.jl +++ b/advanced/CustomLib/UserLib.jl @@ -4,8 +4,9 @@ using Libdl using CairoMakie, Rotations, LinearAlgebra, IGLWrap_jll # to force loading G4Vis extension prefix = Geant4.Geant4_jll.artifact_dir -dlext = Libdl.dlext +dlext = Libdl.dlext; # Compilation of the custom library + Base.run(`c++ -O2 -shared -fPIC -std=c++17 -I$prefix/include/Geant4 -Wl,-rpath,$prefix/lib -L$prefix/lib -lG4geometry -lG4materials -lG4global -lG4clhep @@ -16,8 +17,8 @@ createRoundCube(a,r) = @ccall lib.createRoundCube(a::Float64, r::Float64)::CxxPt deleteRoundCube(s::CxxPtr{G4VSolid}) = @ccall lib.deleteRoundCube(s::CxxPtr{G4VSolid})::Cvoid infoRoundCube(s::CxxPtr{G4VSolid}) = (@ccall lib.infoRoundCube(s::CxxPtr{G4VSolid})::Cstring) |> unsafe_string -rcube = createRoundCube(10cm, 1cm) -img = drawDistanceToOut(rcube[], 100000) +rcube = createRoundCube(10cm, 1cm) # returns a CxxPtr{G4VSolid} +img = drawDistanceToOut(rcube[], 100000) # implemented in G4Vis ext. It expects a G4VSolid. display(img) info = infoRoundCube(rcube) diff --git a/advanced/CustomLib/UserLib_lit.jl b/advanced/CustomLib/UserLib_lit.jl index eb982be..4cb278c 100644 --- a/advanced/CustomLib/UserLib_lit.jl +++ b/advanced/CustomLib/UserLib_lit.jl @@ -1,4 +1,4 @@ -# # Calling C++ code from Julia +# # Calling Custom C++ library # # An example of calling user libraries that can provide additional Geant4 functionally that is not # provided by the set of wrapped classes. In this example we define a custom solid called `RoundCube`, @@ -24,7 +24,7 @@ using CairoMakie, Rotations, LinearAlgebra, IGLWrap_jll # to force loading G4Vi #md import DisplayAs: PNG #hide # ## Building the custom library -# The custom library is defined in the C++ file `UserLib.cpp`. +# The custom library is defined in the C++ file `UserLibrary.cpp`. # The library provides a function to create a custom solid `RoundCube` and some additional functions # to interact with the solid. # @@ -32,28 +32,36 @@ using CairoMakie, Rotations, LinearAlgebra, IGLWrap_jll # to force loading G4Vi # We use only a sub-set of libraries needed to link shared library. prefix = Geant4.Geant4_jll.artifact_dir -dlext = Libdl.dlext +dlext = Libdl.dlext; +if Sys.KERNEL == :Linux + ldflags = "-Wl,-rpath,$prefix/lib -Wl,--no-as-needed" +else + ldflags = "-Wl,-rpath,$prefix/lib -Wl" +end ## Compilation of the custom library -Base.run(`c++ -O2 -shared -fPIC -std=c++17 -I$prefix/include/Geant4 - -Wl,-rpath,$prefix/lib -L$prefix/lib - -lG4geometry -lG4materials -lG4global -lG4clhep - -o UserLib.$dlext $(@__DIR__)/UserLib.cpp`).exitcode == 0 || error("Compilation failed") +# The custom library is defined in the C++ file [`UserLibrary.cpp`](@ref). Please note that the +# callable functions are defined with the `extern "C"` attribute to avoid name mangling. +Base.run(`c++ -O2 -shared -fPIC -std=c++17 -I$prefix/include/Geant4 $ldflags + -L$prefix/lib -lG4geometry -lG4materials -lG4global -lG4clhep + -o UserLibrary.$dlext $(@__DIR__)/UserLibrary.cpp`).exitcode == 0 || error("Compilation failed") # ## Define Julia functions to interact with the custom library # The `@call` macro provides a very convenient way to call C functions (or extern "C"). # We define the following functions to interact with the custom library in a more Julia-friendly way: -const lib = "./UserLib.$(Libdl.dlext)" +const lib = "./UserLibrary.$(Libdl.dlext)" createRoundCube(a,r) = @ccall lib.createRoundCube(a::Float64, r::Float64)::CxxPtr{G4VSolid} deleteRoundCube(s::CxxPtr{G4VSolid}) = @ccall lib.deleteRoundCube(s::CxxPtr{G4VSolid})::Cvoid infoRoundCube(s::CxxPtr{G4VSolid}) = (@ccall lib.infoRoundCube(s::CxxPtr{G4VSolid})::Cstring) |> unsafe_string # ## Testing the custom library -# We create a `RoundCube` with side length `100` and radius `10` and draw the distance to the outside -rcube = createRoundCube(10cm, 1cm) -img = drawDistanceToOut(rcube[], 100000) +# We create a `RoundCube` with side length `100` and radius `10` and draw the distance to the outside of the solid +# from a number of randomly distributed points in a random directions. This should get a nice image of the +# surface `RoundCube`. It is exercising `Inside` and `DistanceToOut` methods of the custom solid. +rcube = createRoundCube(10cm, 1cm) # returns a CxxPtr{G4VSolid} +img = drawDistanceToOut(rcube[], 100000) # implemented in G4Vis ext. It expects a G4VSolid. #jl display(img) #nb display("image/png", img) -#md PNG(img) +#md PNG(img) #hide # Get the information about the `RoundCube` info = infoRoundCube(rcube) println(info) diff --git a/advanced/CustomLib/UserLib.cpp b/advanced/CustomLib/UserLibrary.cpp similarity index 94% rename from advanced/CustomLib/UserLib.cpp rename to advanced/CustomLib/UserLibrary.cpp index 672c8da..073c85c 100644 --- a/advanced/CustomLib/UserLib.cpp +++ b/advanced/CustomLib/UserLibrary.cpp @@ -5,6 +5,7 @@ #include "G4SubtractionSolid.hh" #include "G4UnionSolid.hh" +// The custom solid class needs to inherit from G4VSolid and implement the required methods class RoundCube : public G4VSolid { public: @@ -31,6 +32,7 @@ class RoundCube : public G4VSolid double a, r; }; +// Constructor of the custom solid (RoundCube) implemented as a union and subtractions of several solids RoundCube::RoundCube(double a, double r) : a(a), r(r), G4VSolid("RoundCube") { G4double ca = a / 4; G4VSolid* cube = new G4Box("cube", ca, ca, ca); @@ -69,6 +71,7 @@ RoundCube::RoundCube(double a, double r) : a(a), r(r), G4VSolid("RoundCube") { solid = new G4UnionSolid("u5", u4, u4, G4Transform3D(G4RotationMatrix(0, 0, M_PI), G4ThreeVector())); } +// Define the callable functions for the custom solid extern "C" { G4VSolid* createRoundCube(double a, double r) { return new RoundCube(a, r); diff --git a/advanced/EmbedJulia/G4example.cpp b/advanced/EmbedJulia/G4example.cpp new file mode 100644 index 0000000..1ec2656 --- /dev/null +++ b/advanced/EmbedJulia/G4example.cpp @@ -0,0 +1,170 @@ +#include "G4VUserDetectorConstruction.hh" +#include "G4VUserPrimaryGeneratorAction.hh" +#include "G4UserEventAction.hh" +#include "G4UserRunAction.hh" +#include "G4ParticleGun.hh" +#include "G4RunManager.hh" +#include "G4UImanager.hh" +#include "G4RunManagerFactory.hh" +#include "QBBC.hh" +#include "G4NistManager.hh" +#include "G4Box.hh" +#include "G4LogicalVolume.hh" +#include "G4PVPlacement.hh" +//#include "G4SystemOfUnits.hh" +#include "globals.hh" +#include "G4VUserActionInitialization.hh" +#include +#include + +//---Detector construction class------------------------------------------------------------------- +class DetectorConstruction : public G4VUserDetectorConstruction +{ + public: + DetectorConstruction() = default; + ~DetectorConstruction() override = default; + G4VPhysicalVolume* Construct() override { + auto nist = G4NistManager::Instance(); + auto world_mat = nist->FindOrBuildMaterial("G4_AIR"); + auto core_mat = nist->FindOrBuildMaterial("G4_WATER"); + auto world_size = 1.0*CLHEP::m; + auto solidWorld = new G4Box("World", world_size, world_size, world_size); + auto logicWorld = new G4LogicalVolume(solidWorld, world_mat, "World"); + auto physWorld = new G4PVPlacement(0, G4ThreeVector(), logicWorld, "World", 0, false, 0); + auto core_size = 0.5*CLHEP::m; + auto solidCore = new G4Box("Core", core_size, core_size, core_size); + auto logicCore = new G4LogicalVolume(solidCore, core_mat, "Core"); + new G4PVPlacement(0, G4ThreeVector(), logicCore, "Core", logicWorld, false, 0); + return physWorld; + } +}; + +//---Primary generator action class---------------------------------------------------------------- +class PrimaryGeneratorAction : public G4VUserPrimaryGeneratorAction +{ +public: + PrimaryGeneratorAction() { fParticleGun = new G4ParticleGun(); } + ~PrimaryGeneratorAction() { delete fParticleGun; } + void GeneratePrimaries(G4Event* anEvent) override { + fPrimaryParticleName = fParticleGun->GetParticleDefinition()->GetParticleName(); + fParticleGun->SetParticleMomentumDirection(G4ThreeVector(0.,0.,1.)); + fParticleGun->SetParticlePosition(G4ThreeVector(0.,0.,-1.*CLHEP::m)); + fParticleGun->GeneratePrimaryVertex(anEvent); + } + G4ParticleGun* GetParticleGun() {return fParticleGun;}; + const G4String& GetParticleName() { return fPrimaryParticleName;} +private: + G4String fPrimaryParticleName; + G4ParticleGun* fParticleGun; +}; + +typedef void (*stepaction_f)(const G4Step*); +class SteppingAction : public G4UserSteppingAction +{ +public: + SteppingAction() { + action_jl = (stepaction_f)(jl_unbox_voidpointer(jl_eval_string("@cfunction(stepping_action, Nothing, (CxxPtr{G4Step},))"))); + std::cout << "=====> " << action_jl << std::endl; + } + ~SteppingAction() {} + void UserSteppingAction(const G4Step* step) override { action_jl(step); } +private: + stepaction_f action_jl; +}; + +typedef void (*eventaction_f)(const G4Event*); +class EventAction : public G4UserEventAction +{ + public: + EventAction() { + beginevent_jl = (eventaction_f)(jl_unbox_voidpointer(jl_eval_string("@cfunction(begin_of_event_action, Nothing, (CxxPtr{G4Event},))"))); + endevent_jl = (eventaction_f)(jl_unbox_voidpointer(jl_eval_string("@cfunction(end_of_event_action, Nothing, (CxxPtr{G4Event},))"))); + } + ~EventAction() override = default; + + void BeginOfEventAction(const G4Event* event) override { beginevent_jl(event); } + void EndOfEventAction(const G4Event* event) override { endevent_jl(event); } + private: + eventaction_f beginevent_jl; + eventaction_f endevent_jl; +}; + +typedef void (*runaction_f)(const G4Run*); +class RunAction : public G4UserRunAction +{ + public: + RunAction() { + beginrun_jl = (runaction_f)(jl_unbox_voidpointer(jl_eval_string("@cfunction(begin_of_run_action, Nothing, (CxxPtr{G4Run},))"))); + endrun_jl = (runaction_f)(jl_unbox_voidpointer(jl_eval_string("@cfunction(end_of_run_action, Nothing, (CxxPtr{G4Run},))"))); + } + ~RunAction() override = default; + + void BeginOfRunAction(const G4Run* run) override { beginrun_jl(run); } + void EndOfRunAction(const G4Run* run) override { endrun_jl(run); } + + private: + runaction_f beginrun_jl; + runaction_f endrun_jl; +}; + +//---Action initialization class------------------------------------------------------------------- +class ActionInitialization : public G4VUserActionInitialization +{ + public: + ActionInitialization() = default; + ~ActionInitialization() override = default; + void BuildForMaster() const override {} + void Build() const override { + SetUserAction(new PrimaryGeneratorAction); + SetUserAction(new RunAction); + SetUserAction(new EventAction); + SetUserAction(new SteppingAction); + } +}; + +JULIA_DEFINE_FAST_TLS // only define this once, in an executable (not in a shared library) if you want fast code. + +//----Main program--------------------------------------------------------------------------------- +int main(int, char**) +{ + //--- Required to setup the Julia context + jl_init(); + /* run Julia commands */ + jl_eval_string("include(\"MyCode.jl\")"); + if (jl_exception_occurred()) { + std::cout << "=====> " << jl_typeof_str(jl_exception_occurred()) << std::endl; + } + + //---Construct the default run manager + auto runManager = G4RunManagerFactory::CreateRunManager(G4RunManagerType::Serial); + + //---Set mandatory initialization classes + // Detector construction + runManager->SetUserInitialization(new DetectorConstruction()); + + // Physics list + runManager->SetUserInitialization(new QBBC(0)); + + // User action initialization + runManager->SetUserInitialization(new ActionInitialization()); + + // Initialize G4 kernel + runManager->Initialize(); + + // Get the pointer to the User Interface manager + auto UImanager = G4UImanager::GetUIpointer(); + UImanager->ApplyCommand("/control/verbose 1"); + UImanager->ApplyCommand("/run/verbose 1"); + //UImanager->ApplyCommand("/event/verbose 0"); + //UImanager->ApplyCommand("/tracking/verbose 1"); + UImanager->ApplyCommand("/gun/particle e+"); + UImanager->ApplyCommand("/gun/energy 100 MeV"); + UImanager->ApplyCommand("/run/beamOn 100000"); + + // Job termination + delete runManager; + + // strongly recommended: notify Julia that the program is about to terminate. + // this allows Julia time to cleanup pending write requests and run all finalizers + jl_atexit_hook(0); +} diff --git a/advanced/EmbedJulia/JuliaAction.ipynb b/advanced/EmbedJulia/JuliaAction.ipynb new file mode 100644 index 0000000..c3f8c8e --- /dev/null +++ b/advanced/EmbedJulia/JuliaAction.ipynb @@ -0,0 +1,195 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "source": [ + "# Calling G4 actions in Julia\n", + "\n", + "This is a very simple example of calling user actions in Julia from a C++\n", + "Geant4 application.\n", + "We define the user actions in Julia language in the file `MyCode.jl`\n", + "and call them from the C++ application. The name and signatures of the functions\n", + "are important since the C++ will associate them in the corresponding inherited\n", + "classes.\n", + "\n", + "The C++ code is a single file `G4example.cpp` that defines the Geant4 the minimal\n", + "set of classes to run a simulation.\n", + "- The main program is responsible of initializing Julia by calling `julia_init` and\n", + " loading the Julia code executing.\n", + " ```cpp\n", + " jl_init()\n", + " jl_eval_string(\"include(\\\"MyCode.jl\\\")\");\n", + " ```\n", + "- Each constructor of a user action class needs to initialize a C++ function pointer to the\n", + " corresponding Julia function. This is done in the constructor to avoid any dynamic dispatch\n", + " at runtime. For example, for the `EventAction` class:\n", + " ```cpp\n", + " typedef void (*eventaction_f)(const G4Event*);\n", + " class EventAction : public G4UserEventAction {\n", + " public:\n", + " EventAction() {\n", + " beginevent_jl = (eventaction_f)(jl_unbox_voidpointer(jl_eval_string(\"@cfunction(begin_of_event_action, Nothing, (CxxPtr{G4Event},))\")));\n", + " endevent_jl = (eventaction_f)(jl_unbox_voidpointer(jl_eval_string(\"@cfunction(end_of_event_action, Nothing, (CxxPtr{G4Event},))\")));\n", + " }\n", + " ...\n", + " private:\n", + " eventaction_f beginevent_jl;\n", + " eventaction_f endevent_jl;\n", + " };\n", + " ```\n", + "- Finally the actions are called in the corresponding Geant4 classes. For example in the `EventAction` class:\n", + " ```cpp\n", + " void EventAction::BeginOfEventAction(const G4Event* event) {\n", + " beginevent_jl(event);\n", + " }\n", + " ...\n", + " ```" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "## Loading the necessary Julia modules" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "using Geant4_jll # Needed to locate the Geant4 installation directory" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Building G4Example Application\n", + "The custom library is defined in the C++ file `G4example.cpp`. It is a single file to\n", + "facilitate the building of the executable.\n", + "\n", + "The attribute `Geant4_jll.artifact_dir` provides the path to the Geant4 installation directory.\n", + "Sources are in the same location as this script." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "cd(@__DIR__)\n", + "g4prefix = Geant4_jll.artifact_dir\n", + "jlprefix = dirname(Sys.BINDIR)" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "We use the executables `geant4-config` and `julia-config.jl` to get the needed\n", + "libraries and compiler/linker flags." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "g4libs = read(`$g4prefix/bin/geant4-config --libs`, String) |> split\n", + "filter!(x -> x != \"-lG4gdml\", g4libs)\n", + "jllibs = read(`$jlprefix/share/julia/julia-config.jl --ldlibs`, String) |> split\n", + "append!(jllibs, [\"-L$jlprefix/lib\"])\n", + "cflags = read(`$g4prefix/bin/geant4-config --cflags`, String) |> split\n", + "ldflags = [\"-Wl,-rpath,$g4prefix/lib\", \"-Wl,-rpath,$jlprefix/lib\"];" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "Run the compilation and link command" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "Base.run(`c++ -O2 -fPIC $cflags -I$jlprefix/include/julia $ldflags $g4libs $jllibs\n", + " -o G4example $(@__DIR__)/G4example.cpp`).exitcode == 0 || error(\"Compilation failed\");" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Run the application\n", + "We need to set the variable `JULIA_PROJECT` pointing to correctly setup Julia environment." + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "withenv(\"JULIA_PROJECT\" => \"@.\") do\n", + " Base.run(`./G4example`).exitcode == 0 || error(\"Execution failed\");\n", + "end" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "## Display the results" + ], + "metadata": {} + }, + { + "outputs": [], + "cell_type": "code", + "source": [ + "display(\"image/png\", read(\"edepHist.png\"))" + ], + "metadata": {}, + "execution_count": null + }, + { + "cell_type": "markdown", + "source": [ + "next" + ], + "metadata": {} + }, + { + "cell_type": "markdown", + "source": [ + "---\n", + "\n", + "*This notebook was generated using [Literate.jl](https://github.com/fredrikekre/Literate.jl).*" + ], + "metadata": {} + } + ], + "nbformat_minor": 3, + "metadata": { + "language_info": { + "file_extension": ".jl", + "mimetype": "application/julia", + "name": "julia", + "version": "1.11.1" + }, + "kernelspec": { + "name": "julia-1.11", + "display_name": "Julia 1.11.1", + "language": "julia" + } + }, + "nbformat": 4 +} diff --git a/advanced/EmbedJulia/JuliaAction.jl b/advanced/EmbedJulia/JuliaAction.jl new file mode 100644 index 0000000..3e01a58 --- /dev/null +++ b/advanced/EmbedJulia/JuliaAction.jl @@ -0,0 +1,23 @@ +using Geant4_jll # Needed to locate the Geant4 installation directory + +cd(@__DIR__) +g4prefix = Geant4_jll.artifact_dir +jlprefix = dirname(Sys.BINDIR) + +g4libs = read(`$g4prefix/bin/geant4-config --libs`, String) |> split +filter!(x -> x != "-lG4gdml", g4libs) +jllibs = read(`$jlprefix/share/julia/julia-config.jl --ldlibs`, String) |> split +append!(jllibs, ["-L$jlprefix/lib"]) +cflags = read(`$g4prefix/bin/geant4-config --cflags`, String) |> split +ldflags = ["-Wl,-rpath,$g4prefix/lib", "-Wl,-rpath,$jlprefix/lib"]; + +Base.run(`c++ -O2 -fPIC $cflags -I$jlprefix/include/julia $ldflags $g4libs $jllibs + -o G4example $(@__DIR__)/G4example.cpp`).exitcode == 0 || error("Compilation failed"); + +withenv("JULIA_PROJECT" => "@.") do + Base.run(`./G4example`).exitcode == 0 || error("Execution failed"); +end + +display("image/png", read("edepHist.png")) + +display("image/png", read("edepHist.png")) diff --git a/advanced/EmbedJulia/JuliaAction_lit.jl b/advanced/EmbedJulia/JuliaAction_lit.jl new file mode 100644 index 0000000..19e45b0 --- /dev/null +++ b/advanced/EmbedJulia/JuliaAction_lit.jl @@ -0,0 +1,92 @@ +# # Calling G4 actions in Julia +# +# This is a very simple example of calling user actions in Julia from a C++ +# Geant4 application. +# We define the user actions in Julia language in the file [`MyCode.jl`](@ref) +# and call them from the C++ application. The name and signatures of the functions +# are important since the C++ will associate them in the corresponding inherited +# classes. +# +# The C++ code is a single file [`G4example.cpp`](@ref) that defines the Geant4 the minimal +# set of classes to run a simulation. +# - The main program is responsible of initializing Julia by calling `julia_init` and +# loading the Julia code executing. +# ```cpp +# jl_init() +# jl_eval_string("include(\"MyCode.jl\")"); +# ``` +# - Each constructor of a user action class needs to initialize a C++ function pointer to the +# corresponding Julia function. This is done in the constructor to avoid any dynamic dispatch +# at runtime. For example, for the `EventAction` class: +# ```cpp +# typedef void (*eventaction_f)(const G4Event*); +# class EventAction : public G4UserEventAction { +# public: +# EventAction() { +# beginevent_jl = (eventaction_f)(jl_unbox_voidpointer(jl_eval_string("@cfunction(begin_of_event_action, Nothing, (CxxPtr{G4Event},))"))); +# endevent_jl = (eventaction_f)(jl_unbox_voidpointer(jl_eval_string("@cfunction(end_of_event_action, Nothing, (CxxPtr{G4Event},))"))); +# } +# ... +# private: +# eventaction_f beginevent_jl; +# eventaction_f endevent_jl; +# }; +# ``` +# - Finally the actions are called in the corresponding Geant4 classes. For example in the `EventAction` class: +# ```cpp +# void EventAction::BeginOfEventAction(const G4Event* event) { +# beginevent_jl(event); +# } +# ... +# ``` + +#md # !!! note "Note that" +#md # You can also download this example as a +#md # [Jupyter notebook](JuliaAction.ipynb) and a plain +#md # [Julia source file](JuliaAction.jl). +# +#md # #### Table of contents +#md # ```@contents +#md # Pages = ["JuliaAction.md"] +#md # Depth = 2:3 +#md # ``` + +# ## Loading the necessary Julia modules +using Geant4_jll # Needed to locate the Geant4 installation directory +#md import DisplayAs: PNG #hide + +# ## Building G4Example Application +# The custom library is defined in the C++ file `G4example.cpp`. It is a single file to +# facilitate the building of the executable. +# +# The attribute `Geant4_jll.artifact_dir` provides the path to the Geant4 installation directory. +# Sources are in the same location as this script. +cd(@__DIR__) +g4prefix = Geant4_jll.artifact_dir +jlprefix = dirname(Sys.BINDIR) + +# We use the executables `geant4-config` and `julia-config.jl` to get the needed +# libraries and compiler/linker flags. + +g4libs = read(`$g4prefix/bin/geant4-config --libs`, String) |> split +filter!(x -> x != "-lG4gdml", g4libs) +jllibs = read(`$jlprefix/share/julia/julia-config.jl --ldlibs`, String) |> split +append!(jllibs, ["-L$jlprefix/lib"]) +cflags = read(`$g4prefix/bin/geant4-config --cflags`, String) |> split +ldflags = ["-Wl,-rpath,$g4prefix/lib", "-Wl,-rpath,$jlprefix/lib"]; + +# Run the compilation and link command +Base.run(`c++ -O2 -fPIC $cflags -I$jlprefix/include/julia $ldflags $g4libs $jllibs + -o G4example $(@__DIR__)/G4example.cpp`).exitcode == 0 || error("Compilation failed"); + +# ## Run the application +# We need to set the variable `JULIA_PROJECT` pointing to correctly setup Julia environment. +withenv("JULIA_PROJECT" => "@.") do + Base.run(`./G4example`).exitcode == 0 || error("Execution failed"); +end + +# ## Display the results +display("image/png", read("edepHist.png")) +# next +#jl display("image/png", read("edepHist.png")) +#md PNG(read("edepHist.png")) diff --git a/advanced/EmbedJulia/MyCode.jl b/advanced/EmbedJulia/MyCode.jl new file mode 100644 index 0000000..4bafe50 --- /dev/null +++ b/advanced/EmbedJulia/MyCode.jl @@ -0,0 +1,39 @@ +#---Load the needed Julia modules------------------------------------------------------------------ + +using Geant4 +using GeometryBasics +using FHist +using Plots + +println("=====> Loading MyCode.jl") + +edepHist = H1D("Event total Edep distribution", 100, 0., 110.) +edep = 0.0 + +function end_of_event_action(event) + push!(edepHist, edep) + return # This is mandatory to force to return nothing +end + +function begin_of_event_action(event) + global edep = 0.0 + return # This is mandatory to force to return nothing +end + +function stepping_action(step) + global edep += GetTotalEnergyDeposit(step) + return # This is mandatory to force to return nothing +end + +function begin_of_run_action(run) + return # This is mandatory to force to return nothing +end + +function end_of_run_action(run) + println("=====> End of run") + h = edepHist + img = plot(h.hist, title=h.title) + savefig(img, "edepHist.png") + println("=====> edepHist.png saved") + return # This is mandatory to force to return nothing +end