Object lifetimes (C++ lifetimes)

Please fill out the following information to help in answering your question, and also see tips for posting code snippets. If you don’t provide this information it will take more time to help with your problem!

Geant4 Version: 11.1.3
Operating System: NixOS (Linux)
Compiler/Version: 12.3.0
CMake Version: 3.27.7


I am attempting to automatically create materials using a simplex noising algorithm and several passes of combining other materials. This will mean registering several hundred thousand materials with the material database. Because they are given a globally accessible id upon construction, if I decide to multi-thread the construction of this data (because it is embarrassingly parallel) I would need to protect all of my vivications with a mutex. (Otherwise there is a race condition in push_back that could result in writing to freed space.) I managed to gleam this from the source, and couldn’t find any documentation on it.

However, once I am done with the materials, and I want to try to construct another run, how should I dispose of the materials that I am no longer using? I can simply call delete on the pointer, and that will release the index of the material in theMaterialTable, but it won’t cause the index to be reused, so I still have unbounded memory usage.

Reading over the code, it seems to be possible to overwrite one material with another using the operator= but that requires the Material to already be constructed, and therefore be taking up its own entry in the table.

Is there a way to overwrite the data of a G4Material without having to construct a new one? Or is there a different way of handling Materials to avoid unbounded memory usage?

Apologies for the delay in replying - I’ll need to dig into this in a bit more detail which may take a few days. In the meantime, are you able to post or link to a minimum reproducible example of the issue/what you’re trying to do with the material table please (i.e. don’t have to include the noise algorithm, just add/removal etc of the material(s))?

No worries, I posted late on Friday, I was expecting it would take a few days to get back to me. :slight_smile: Thanks a lot for offering to help!

Essentially, I was setting up a scaffolding that looks like:

#include <map>
#include <string>

#include "G4Material.hh"
#include "json.hpp"

class MaterialDatabase {
public:
  explicit MaterialDatabase(nlohmann::json const& config);

private:
  G4Isotope parseIsotope(std::string name, nlohmann::json const& iso);
  G4Element parseElement(std::string name, nlohmann::json const& el);
  G4Material parseMaterial(std::string name, nlohmann::json const& mat);

  std::map<std::string, G4Material> m_materials;
  std::map<std::string, G4Element> m_elements;
  std::map<std::string, G4Isotope> m_isotopes;
};

G4Isotope MaterialDatabase::parseIsotope(std::string name, nlohmann::json const& iso) {
  return G4Isotope(
    name, // name
    iso.at("Z").get<G4int>(), // atomic number
    iso.at("A").get<G4int>(), // number of nucleons
    iso.at("a").get<G4double>(), // mass of mole
    0 // isomer level
  ); 
}

G4Element MaterialDatabase::parseElement(std::string name, nlohmann::json const& el) {
  return G4Element(
    name, // name
    el.at("symbol").get<std::string>(), // symbol
    el.at("Z").get<G4double>(), // atomic number
    el.at("a").get<G4double>() // mass of mole
  );
}

G4Material MaterialDatabase::parseMaterial(std::string name, nlohmann::json const& mat) {
  auto components = mat.at("components").get<std::map<std::string, int>>();
  G4Material res = G4Material(
    name, // name
    1, // density (faked at this stage)
    components.size()
  );

  for (auto [k, v] : components) {
    res.AddElement(m_elements.at(k), v);
  }

  return res;
}

MaterialDatabase::MaterialDatabase(nlohmann::json const& config) {
  auto isotopes = config.at("isotopes");
  for (auto [k, v] : isotopes.items()) {
    m_isotopes[k] = parseIsotope(k, v);
  }

  auto elements = config.at("elements");
  for (auto [k, v] : elements.items()) {
    m_elements[k] = parseElement(k, v);
  }

  auto materials = config.at("materials");
  for (auto [k, v] : materials.items()) {
    m_materials[k] = parseMaterial(k, v);
  }
}

In order to pull in material definitions from json files in preparation for mixing them, when I originally ran into the errors about the copy ctor being deleted. This is easy enough to fix.

Now, I am a Geant beginner, but I’ve had quite a bit of C++ experience, so I didn’t realize that this was duplicating effort with Geant at the time I wrote it (I figured it out prior to writing the original post), which maintains its own database of materials automagically. So the parts of this class that store the data in a hashmap have been removed, and everything just returns pointers now. But that raised a new question: how can I prevent unbounded linear growth in the size of the internally maintained Geant Materials Table? I don’t have access to it, so I can’t change or remove items from it, or force it to compact indices or reuse freed ones.

Specifically, if I were to just generate (for instance) 1,000,000 materials for 1,000,000 cells in the ground, then free them, then do it again, and so on (following code untested):

// generate a randomized material
G4Material* makeRandMat(int i, int j, int k, int seed) {
  // uses x, y, and z, along with the previously read data from the json using a noising algorithm
  // simply stubbed out here, it will have the name "voxel-mat-i-j-k"
  return G4NistManager::Instance()->FindOrBuildMaterial("G4_AIR");
}

void populateWorld(G4LogicalVolume *voxel_arena, int seed) {
  G4double voxel_size = 1*cm;
  bool checkOverlaps = true;
  for (int i=0; i < 100; ++i) {
    for (int j=0; j < 100; ++j) {
      for (int k=0; k < 100; ++k) {
        auto voxel = new G4Box(std:format("voxel-{}-{}-{}", i, j, k), voxel_size, voxel_size, voxel_size);
        auto voxel_logic = new G4LogicalVolume(voxel, makeRandMat(i, j, k, seed), std::format("voxel-logic-{}-{}-{}", i, j, k));
        auto voxel_placement = new G4VPlacement(nullptr, G4ThreeVector(i*cm, j*cm, k*cm), voxel_logic, std::format("voxel-place-{}-{}-{}", i, j, k), voxel_arena, false, 0, checkOverlaps);
      }
    }
  }
}

// later on we can delete all of these materials and voxels and so on, if we choose,
// but if we intend to run this 100 times to generate 100 different material configurations
// then we use 100 times the memory in this list.  It's akin to memory leak.

void clearMaterials() {
  for (int i=0; i < 100; ++i) {
    for (int j=0; j < 100; ++j) {
      for (int k=0; k < 100; ++k) {
        G4Material *matptr = G4Material::GetMaterial(std::format("voxel-mat-{}-{}-{}", i, j, k), false));
        delete matptr; // Frees memory, but not the `theMaterialTable` entry
      }
    }
  }
}

Thanks for the detailed info! I’ll have a full look through and test things as soon as I can.

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.