Creating a custom gas with imported transport data

Hi everyone,

I am trying to create a custom gas in Garfield++ for a gas that is not implemented in Garfield/Magboltz (in my case TMS). Since integrating full cross sections into Magboltz is not feasible, I am instead using electron transport parameters computed with BOLSIG+.

Goal:
Generate a valid .gas file from externally computed swarm parameters (BOLSIG+) and use it in Garfield++ for transport simulations.

From BOLSIG+ output files, I extract:
-(E/N)
-mobility (mu N) → drift velocity via (v = (mu N)(E/N))
-Townsend coefficient (alpha/N)
-attachment coefficient (eta/N)
-longitudinal and transverse diffusion ((D_L/N), (D_T/N))
I parse these files, multiply by N if needed and convert into right units(and longitudinal and transverse diffusion coeff. to longitudinal and transverse diffusion constant).

Then I construct a MediumGas object using:

  • SetFieldGrid(...)
    -SetElectronVelocityE(...), SetElectronVelocityB(...), SetElectronVelocityExB(...)
    -SetElectronLongitudinalDiffusion(...), SetElectronTransverseDiffusion(...)
  • SetElectronTownsend(...)
    -SetElectronAttachment(...)

After filling the tables, I write a .gas file using:
-gas.WriteGasFile(“custom.gas”);

What works:
-The .gas file is successfully written
-It can be loaded in Garfield++ via LoadGasFile(...)

The problem
-when I load the .gas file, and visualize the transport parameters (e.g. using PlotVelocity), the resulting curves do not match the BOLSIG+ results and behave unphysically (e.g. drift velocity going to zero at higher fields)

I also verified this with single-point checks:
-Garfield values after reload do not match the original BOLSIG+ values
-but the values before writing the .gas file are correct
So it seems I am not filling the tables in the format Garfield internally expects

Questions

  1. Is MediumGas + SetFieldGrid + SetElectron... the correct way to construct a gas from external swarm data?

  2. Are there additional steps required before writing the .gas file (e.g. normalization, log-scaling, internal consistency requirements)?

  3. Is the .gas format intended to support this workflow, or is it mainly meant for files produced internally (e.g. via Magboltz)?

  4. More generally: What is the recommended way to use externally computed transport parameters (like BOLSIG+) in Garfield++?

Any guidance or example workflows for building a custom gas from swarm data would be greatly appreciated.

Thanks a lot!

I guess @hschindl can help you.

Hi Max,
the most frequent use case for .gas files is indeed to store and reload gas tables calculated using Magboltz, but they should also work for tables calculated by other means.
Could you upload (or send me) the script you are using and the plot to be compared with?

Dear Max

Thanks for your question. You are actually touching a point on which we would like to extend the garfield functionality in the (near) future, but we have not yet done this. So thanks very much for experimenting, this is definitively very interesting. For gases not implemented in Magboltz, but with cross sections available on lxcat, I would have followed the same procedure as you did by calculating swarm parameters with Bolsig+ (or mcig) and to construct a gas table.

The problem
-when I load the .gas file, and visualize the transport parameters (e.g. using PlotVelocity), the resulting curves do not match the BOLSIG+ results and behave unphysically (e.g. drift velocity going to zero at higher fields)

Can you show us? Please note that in MediumGas you can set the extrapolation method to constant, linear or exponential:

https://gitlab.cern.ch/garfield/garfieldpp/-/blob/master/Include/Garfield/Medium.hh#L557-570

Questions

  1. Is MediumGas + SetFieldGrid + SetElectron... the correct way to construct a gas from external swarm data?

You can also have a look at how a gasfile looks like and write some code that gives you the same txt file based on the input you give (your Bolsig+ output). You can get inspiration in the source code of GenerateGasTable and LoadGasTable, even though it is a bit complicated ;-). But I think in general there is nothing wrong with your approach. Could you eventually share your code?

  1. Are there additional steps required before writing the .gas file (e.g. normalization, log-scaling, internal consistency requirements)?

Yes, Townsend and Attachment coefficients are saved as their natural log values:

https://gitlab.cern.ch/garfield/garfieldpp/-/blob/master/Source/MediumMagboltz.cc#L2775-2791

  1. Is the .gas format intended to support this workflow, or is it mainly meant for files produced internally (e.g. via Magboltz)?

No it was not intended for this workflow, as this was not foreseen at the time of the development (which was already done in the fortran version of Garfield). So it was meant only for Magboltz produced files. But I think it would be useful if also other sources (eg Bolsig+) could generate this output.

  1. More generally: What is the recommended way to use externally computed transport parameters (like BOLSIG+) in Garfield++?

We have no recommendations yet unfortunately.
Kind regards
Piet

Hi all,

thanks a lot for the helpful feedback so far — I really appreciate the time and suggestions.

Unfortunately, I am a new user on the forum and I am not yet able to upload attachments(I think). I will add the files as soon as that becomes possible, but in the meantime I can show the code of the converter and the .gas file.

The Garfield-specific part of the converter starts at around line 330, where I construct and fill the MediumGas object with the BOLSIG+ transport parameters.The lines before this part of the code are only used to parse and handle the BOLSIG+ output files.

#include <algorithm>
#include <cctype>
#include <cmath>
#include <cstdlib>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <map>
#include <regex>
#include <set>
#include <sstream>
#include <stdexcept>
#include <string>
#include <vector>
#include "Garfield/MediumGas.hh"



namespace {

constexpr double kBoltzmann = 1.380649e-23;          // J/K
constexpr double kTorrToPa  = 133.32236842105263;    // Pa / Torr
constexpr double kPi        = 3.14159265358979323846;

// ---------- utilities ----------

std::string Trim(const std::string& s) {
  size_t i = 0, j = s.size();
  while (i < j && std::isspace(static_cast<unsigned char>(s[i]))) ++i;
  while (j > i && std::isspace(static_cast<unsigned char>(s[j - 1]))) --j;
  return s.substr(i, j - i);
}

std::string Lower(std::string s) {
  for (char& c : s) c = static_cast<char>(std::tolower(static_cast<unsigned char>(c)));
  return s;
}

std::string NormalizeLabel(std::string s) {
  s = Lower(Trim(s));
  s = std::regex_replace(s, std::regex(R"(\s+)"), " ");
  return s;
}

struct TransportRow {
  double en_td;
  double muN;
  double alpha_over_N;
  double eta_over_N;
  double dL_N;
  double dT_N;
};

bool ParseDoubleToken(const std::string& token, double& out) {
  char* end = nullptr;
  out = std::strtod(token.c_str(), &end);
  return end != token.c_str() && *end == '\0';
}

std::vector<double> ExtractNumbers(const std::string& line) {
  std::vector<double> values;
  std::stringstream ss(line);
  std::string tok;
  while (ss >> tok) {
    double x = 0.;
    if (ParseDoubleToken(tok, x)) values.push_back(x);
  }
  return values;
}

bool IsMostlyNumericLine(const std::string& line) {
  auto nums = ExtractNumbers(line);
  if (nums.empty()) return false;
  // Require at least one numeric token and no alphabetic chars.
  for (char c : line) {
    if (std::isalpha(static_cast<unsigned char>(c))) return false;
  }
  return true;
}

double GasNumberDensity(double pressure_torr, double temperature_K) {
  const double p_pa = pressure_torr * kTorrToPa;
  return p_pa / (kBoltzmann * temperature_K);   // m^-3
}

// BOLSIG+ reduced field: E/N in Td, with 1 Td = 1e-21 V m^2.
// Garfield++ grid needs E in V/cm.
double ENTdToEfieldVcm(double en_td, double N) {
  return en_td * 1.e-23 * N;
}

// BOLSIG+ mobility*N is in 1/(m V s).
// v = mu * E = (muN / N) * (E/N * N) = muN * (E/N).
double DriftVelocityCmNs(double muN, double en_td) {
  const double en_si = en_td * 1.e-21;     // V m^2
  const double v_m_s = muN * en_si;        // m/s
  return v_m_s * 1.e-7;                    // cm/ns
}

// alpha/N, eta/N in m^2 -> alpha, eta in cm^-1
double CoeffCmInv(double coeff_over_N, double N) {
  return coeff_over_N * N * 1.e-2;
}

// D*N in 1/(m s) -> D in m^2/s
double ReducedDiffusionToSI(double DN, double N) {
  return DN / N;
}

// Garfield++ wants diffusion coefficients in sqrt(cm). The user guide gives
// dl, dt in sqrt(cm). A consistent conversion from a conventional diffusion
// constant D uses sqrt(2D/v)
double GarfieldDiffusionSqrtCm(double D_m2_s, double v_cm_ns) {
  if (v_cm_ns <= 0.) return 0.;
  const double D_cm2_ns = D_m2_s * 1.e-5;
  const double coeff_cm = 2.0 * D_cm2_ns / v_cm_ns;
  return coeff_cm > 0. ? std::sqrt(coeff_cm) : 0.;
}

// ---------- block parser ----------
// files are block-style:
//   Label line
//   one or more numeric lines
//   next label line
//
// parse into: label -> concatenated numeric array.
std::map<std::string, std::vector<double>> ParseParameterBlocks(const std::string& filename) {
  std::ifstream fin(filename);
  if (!fin) throw std::runtime_error("Cannot open file: " + filename);

  std::map<std::string, std::vector<double>> blocks;
  std::string line, currentLabel;

  while (std::getline(fin, line)) {
    line = Trim(line);
    if (line.empty()) continue;

    auto nums = ExtractNumbers(line);

    std::stringstream ss(line);
    std::string tok;
    int nTokens = 0;
    int nNumeric = 0;

    while (ss >> tok) {
      ++nTokens;
      double x = 0.;
      if (ParseDoubleToken(tok, x)) ++nNumeric;
    }

    // If almost all tokens are numeric, treat as data.
    if (nTokens > 0 && nNumeric == nTokens) {
      if (!currentLabel.empty()) {
        auto& dest = blocks[currentLabel];
        dest.insert(dest.end(), nums.begin(), nums.end());
      }
      continue;
    }

    // Otherwise treat as a label.
    currentLabel = NormalizeLabel(line);
    if (!blocks.count(currentLabel)) {
      blocks[currentLabel] = {};
    }
  }

  return blocks;
}

const std::vector<double>& GetRequiredBlock(
    const std::map<std::string, std::vector<double>>& blocks,
    const std::vector<std::string>& candidateLabels,
    const std::string& fileTag) {

  for (const auto& label : candidateLabels) {
    auto it = blocks.find(NormalizeLabel(label));
    if (it != blocks.end() && !it->second.empty()) return it->second;
  }

  std::ostringstream os;
  os << "Could not find required block in " << fileTag << ". Tried labels:";
  for (const auto& s : candidateLabels) os << "\n  - " << s;

  os << "\n\nAvailable parsed labels in " << fileTag << ":";
  for (const auto& kv : blocks) {
    os << "\n  * [" << kv.first << "]  (" << kv.second.size() << " values)";
  }

  throw std::runtime_error(os.str());
}


void CheckSameSize(const std::vector<std::pair<std::string, size_t>>& sizes) {
  if (sizes.empty()) return;
  const size_t n = sizes.front().second;
  for (const auto& x : sizes) {
    if (x.second != n) {
      std::ostringstream os;
      os << "Array size mismatch:\n";
      for (const auto& y : sizes) os << "  " << y.first << ": " << y.second << "\n";
      throw std::runtime_error(os.str());
    }
  }
}

void Usage(const char* argv0) {
  std::cerr
      << "Usage:\n"
      << "  " << argv0
      << " output_PT.dat output_SST.dat output_gn_PT.dat output.gas pressure_torr temperature_K\n\n"
      << "Example:\n"
      << "  " << argv0
      << " output_PT.dat output_SST.dat output_gn_PT.dat custom.gas 760 293.15\n";
}

} // namespace
// to get all the transport parameters, I need three different runs with BOLSIG+ with different settings/growth models=> get three output files
// parse three outfiles. The E/N grid is the same in all the runs. I only take the E/N values from the output_PT file.
int main(int argc, char* argv[]) {
  if (argc != 7) {
    Usage(argv[0]);
    return 1;
  }

  const std::string filePT   = argv[1];
  const std::string fileSST  = argv[2];
  const std::string fileGNPT = argv[3];
  const std::string outGas   = argv[4];
  const double pressure_torr = std::stod(argv[5]);
  const double temperature_K = std::stod(argv[6]);

  try {
    const auto pt   = ParseParameterBlocks(filePT);
    const auto sst  = ParseParameterBlocks(fileSST);
    const auto gnpt = ParseParameterBlocks(fileGNPT);

    // PT file
    const auto& en_td = GetRequiredBlock(
        pt,
        {
          "Electric field / N (Td)"
        },
        "output_PT.dat");

    const auto& muN = GetRequiredBlock(
        pt,
        {
          "Mobility *N (1/m/V/s)"
        },
        "output_PT.dat");

    // SST file
    const auto& alpha_over_N = GetRequiredBlock(
        sst,
        {
          "Townsend ioniz. coef. alpha/N (m2)"         
        },
        "output_SST.dat");

    const auto& eta_over_N = GetRequiredBlock(
        sst,
        {
          "Townsend attach. coef. eta/N (m2)"         
        },
        "output_SST.dat");

    // gnPT file
    const auto& dL_N = GetRequiredBlock(
        gnpt,
        {
          "Longitud. diffusion coef. *N (1/m/s)"         
        },
        "output_gnPT.dat");

    const auto& dT_N = GetRequiredBlock(
        gnpt,
        {         
          "Diffusion coefficient *N (1/m/s)"
        },
        "output_gnPT.dat");
    
    CheckSameSize({
      {"E/N", en_td.size()},
      {"Mobility*N", muN.size()},
      {"alpha/N", alpha_over_N.size()},
      {"eta/N", eta_over_N.size()},
      {"DL*N", dL_N.size()},
      {"DT*N", dT_N.size()}
    });

    const size_t n = en_td.size();
    const double N = GasNumberDensity(pressure_torr, temperature_K);

    std::vector<TransportRow> rows;
    rows.reserve(n);

    for (size_t i = 0; i < n; ++i) {
      rows.push_back({
        en_td[i],
        muN[i],
        alpha_over_N[i],
        eta_over_N[i],
        dL_N[i],
        dT_N[i]
      });
    }

    std::sort(rows.begin(), rows.end(),
              [](const TransportRow& a, const TransportRow& b) {
                return a.en_td < b.en_td;
              });

    std::vector<TransportRow> uniqueRows;
    uniqueRows.reserve(rows.size());

    for (const auto& r : rows) {
      if (uniqueRows.empty() || r.en_td > uniqueRows.back().en_td) {
        uniqueRows.push_back(r);
      } else if (r.en_td == uniqueRows.back().en_td) {
        std::cerr << "Warning: duplicate E/N value removed: "
                  << r.en_td << " Td\n";
      } else {
        std::cerr << "Warning: non-monotonic E/N value skipped: "
                  << r.en_td << " Td\n";
      }
    }

    std::vector<double> efields;
    efields.reserve(uniqueRows.size());
    for (const auto& r : uniqueRows) {
      efields.push_back(ENTdToEfieldVcm(r.en_td, N));
    }
//Garfield related stuff happens here. 

    Garfield::MediumGas gas;
    gas.PrintGas();
 

    gas.SetPressure(pressure_torr);
    gas.SetTemperature(temperature_K);
    for (size_t i = 1; i < efields.size(); ++i) {
      if (efields[i] <= efields[i - 1]) {
        std::cerr << "Non-ascending E-field at i=" << i
              << ": " << efields[i - 1] << " then " << efields[i] << "\n";
      }
    }
    gas.SetFieldGrid(efields, std::vector<double>{0.0}, std::vector<double>{0.5 * kPi});

    for (size_t i = 0; i < uniqueRows.size(); ++i) {
      const auto& r = uniqueRows[i];
      const double vE    = DriftVelocityCmNs(r.muN, r.en_td);
      const double vB    = 0.0;
      const double vExB  = 0.0;
      const double DL_SI = ReducedDiffusionToSI(r.dL_N, N);
      const double DT_SI = ReducedDiffusionToSI(r.dT_N, N);
      const double dl    = GarfieldDiffusionSqrtCm(DL_SI, vE);
      const double dt    = GarfieldDiffusionSqrtCm(DT_SI, vE);
      const double alpha = CoeffCmInv(r.alpha_over_N, N);
      const double eta   = CoeffCmInv(r.eta_over_N, N);

      const size_t ib = 0;
      const size_t ia = 0;
      std::cout << "E index: " << i << "\n";
      std::cout << "E field: " << efields[i] << "\n";

      std::cout << "drift: " << vE << "\n";
      std::cout << "dl: " << dl << "\n";
      std::cout << "dt: " << dt << "\n";
      std::cout << "alpha: " << alpha << "\n";
      std::cout << "eta: " << eta << "\n";
      
      // set velocity
      if (!gas.SetElectronVelocityE(i, ib, ia, vE)) {
        throw std::runtime_error("SetElectronVelocityE failed at row " + std::to_string(i));
      }
      // check if the set velocity 
      double v = 0;
      gas.GetElectronVelocityE(i, ib, ia, v);
      std::cout << "test drift: " << v << "\n";

      
      
      if (!gas.SetElectronVelocityB(i, ib, ia, vB)) {
        throw std::runtime_error("SetElectronVelocityB failed at row " + std::to_string(i));
      }


      if (!gas.SetElectronVelocityExB(i, ib, ia, vExB)) {
        throw std::runtime_error("SetElectronVelocityExB failed at row " + std::to_string(i));
      }
      if (!gas.SetElectronLongitudinalDiffusion(i, ib, ia, dl)) {
        throw std::runtime_error("SetElectronLongitudinalDiffusion failed at row " + std::to_string(i));
      }
      if (!gas.SetElectronTransverseDiffusion(i, ib, ia, dt)) {
        throw std::runtime_error("SetElectronTransverseDiffusion failed at row " + std::to_string(i));
      }
      if (!gas.SetElectronTownsend(i, ib, ia, alpha)) {
        throw std::runtime_error("SetElectronTownsend failed at row " + std::to_string(i));
      }

      if (!gas.SetElectronAttachment(i, ib, ia,eta)) {
        throw std::runtime_error("SetElectronAttachment failed at row " + std::to_string(i));
      }
    }
    
    
    gas.PrintGas();
    gas.WriteGasFile(outGas);
    
    double vcheck;
    std::cout << "test " << gas.GetElectronVelocityE(100., 0., 0., vcheck)<<"\nS";
    std::cout << "Wrote " << outGas << "\n";
    std::cout << "Rows: " << n << "\n";
    std::cout << "Pressure [Torr]: " << pressure_torr << "\n";
    std::cout << "Temperature [K]: " << temperature_K << "\n";
    std::cout << "Number density [m^-3]: " << std::setprecision(12) << N << "\n";
    std::cout << "E/N range [Td]: " << en_td.front() << " -> " << en_td.back() << "\n";
    std::cout << "E range [V/cm]: " << efields.front() << " -> " << efields.back() << "\n";
  

  } catch (const std::exception& e) {
    std::cerr << "Error: " << e.what() << "\n";
    return 2;
  }

  return 0;
  
}

And the first lines of the resulting .gas look like this.

*----.----1----.----2----.----3----.----4----.----5----.----6----.----7----.----8----.----9----.---10----.---11----.---12----.---13--
% Created 13/04/26 at 13.42.14 < none > GAS      "none                         "
 Version   : 13
 GASOK bits: TFTTFTFTTTFFFFFFFFFF
 Identifier: Ar 100%, T=293.15 K, p=1 atm                                                    
 Clusters  :                                                                                 
 Dimension : F       249         1         1         0         0
 E fields   
 3.21883328E-03 3.41054699E-03 3.61368756E-03 3.82889875E-03 4.05695309E-03
 4.29859090E-03 4.55461690E-03 4.82590017E-03 5.11334198E-03 5.41787580E-03
 5.74056383E-03 6.08246831E-03 6.44474799E-03 6.82859386E-03 7.23532563E-03
 7.66626303E-03 8.12285453E-03 8.60664517E-03 9.11927656E-03 9.66242249E-03
 1.02379177E-02 1.08476613E-02 1.14937777E-02 1.21783270E-02 1.29036910E-02
 1.36722197E-02 1.44865523E-02 1.53493606E-02 1.62635736E-02 1.72322493E-02
 1.82586065E-02 1.93460893E-02 2.04983350E-02 2.17192063E-02 2.30128232E-02
 2.43834668E-02 2.58357400E-02 2.73745354E-02 2.90049711E-02 3.07325189E-02
 3.25630050E-02 3.45023520E-02 3.65572552E-02 3.87347959E-02 4.10417337E-02
 4.34861157E-02 4.60763109E-02 4.88206881E-02 5.17282602E-02 5.48093274E-02
 5.80738681E-02 6.15325045E-02 6.51974681E-02 6.90806685E-02 7.31953031E-02
 7.75545690E-02 8.21739167E-02 8.70681527E-02 9.22540150E-02 9.77485634E-02
 1.03570467E-01 1.09739039E-01 1.16275202E-01 1.23200522E-01 1.30538496E-01
 1.38313266E-01 1.46551548E-01 1.55280058E-01 1.64528410E-01 1.74327826E-01
 1.84710816E-01 1.95712145E-01 2.07368828E-01 2.19719813E-01 2.32806301E-01
 2.46672391E-01 2.61364434E-01 2.76931034E-01 2.93425301E-01 3.10901634E-01
 3.29418617E-01 3.49040624E-01 3.69827850E-01 3.91854326E-01 4.15194086E-01
 4.39924382E-01 4.66125685E-01 4.93888122E-01 5.23305039E-01 5.54473002E-01
 5.87495012E-01 6.22486949E-01 6.59561471E-01 6.98847331E-01 7.40470064E-01
 7.84571299E-01 8.31302320E-01 8.80814414E-01 9.33274958E-01 9.88860990E-01
 1.04775920E+00 1.11016272E+00 1.17628400E+00 1.24634512E+00 1.32057785E+00
 1.39923005E+00 1.48256886E+00 1.57087111E+00 1.66443294E+00 1.76356657E+00
 1.86860675E+00 1.97990113E+00 2.09782309E+00 2.22276854E+00 2.35515593E+00
 2.49543269E+00 2.64406231E+00 2.80154051E+00 2.96840161E+00 3.14519925E+00
 3.33252247E+00 3.53102792E+00 3.74131430E+00 3.96415412E+00 4.20025555E+00
 4.45042327E+00 4.71549419E+00 4.99633739E+00 5.29395072E+00 5.60923544E+00
 5.94331814E+00 6.29732543E+00 6.67238388E+00 7.06981322E+00 7.49086881E+00
 7.93703129E+00 8.40978133E+00 8.91066398E+00 9.44138521E+00 1.00037154E+01
 1.05995214E+01 1.12308312E+01 1.18997369E+01 1.26084918E+01 1.33594456E+01
 1.41551412E+01 1.49982180E+01 1.58915408E+01 1.68380388E+01 1.78408985E+01
 1.89035319E+01 2.00294154E+01 2.12223794E+01 2.24863830E+01 2.38256752E+01
 2.52447300E+01 2.67483114E+01 2.83414408E+01 3.00294935E+01 3.18180382E+01
 3.37130941E+01 3.57210023E+01 3.78486511E+01 4.01028001E+01 4.24914962E+01
 4.50221430E+01 4.77037530E+01 5.05450171E+01 5.35555919E+01 5.67451338E+01
 6.01249087E+01 6.37061826E+01 6.75005433E+01 7.15208660E+01 7.57806700E+01
 8.02941180E+01 8.50763386E+01 9.01434260E+01 9.55124399E+01 1.01201406E+02
 1.07228671E+02 1.13615480E+02 1.20382433E+02 1.27552384E+02 1.35149475E+02
 1.43198811E+02 1.51727754E+02 1.60764950E+02 1.70340013E+02 1.80485454E+02
 1.91235070E+02 2.02625233E+02 2.14693605E+02 2.27480742E+02 2.41029455E+02
 2.55385451E+02 2.70596048E+02 2.86713068E+02 3.03789622E+02 3.21883328E+02
 3.37369135E+02 3.53601711E+02 3.70616464E+02 3.88445581E+02 4.07137346E+02
 4.26723947E+02 4.47256884E+02 4.68774785E+02 4.91329149E+02 5.14968261E+02
 5.39743621E+02 5.65713168E+02 5.92931622E+02 6.21460141E+02 6.51359883E+02
 6.82698444E+02 7.15546638E+02 7.49972060E+02 7.86055181E+02 8.23876472E+02
 8.63516404E+02 9.05061885E+02 9.48606261E+02 9.94246098E+02 1.04208440E+03
 1.09222095E+03 1.14477162E+03 1.19984907E+03 1.25757885E+03 1.31808648E+03
 1.38150071E+03 1.44796962E+03 1.51763805E+03 1.59065406E+03 1.66718504E+03
 1.74740158E+03 1.83147429E+03 1.91958985E+03 2.01194784E+03 2.10875103E+03
 2.21020865E+03 2.31654925E+03 2.42800457E+03 2.54482247E+03 2.66726366E+03
 2.79559211E+03 2.93009750E+03 3.07107274E+03 3.21883328E+03
 E-B angles 
 1.57079633E+00
 B fields   
 0.00000000E+00
...

a few things in the generated .gas file seem a bit odd:

  • The gas is identified as Argon, although I never explicitly specified or set Argon anywhere in the conversion process.
  • The electric field values listed in the .gas file do not match the values I obtain when constructing the field grid in my program.

I am not sure if these observations are relevant, but they seemed a bit strange and might indicate that I am misunderstanding how the .gas format or the MediumGas interface is supposed to be used.

Thanks again for your help!

Best regards
Max

Dear Max

It is indeed a feature, not to say cornerstone, of the discourse forum to give more rights to regular users. You can read more about it here [1], and If you scroll down you will see that it will not take much to obtain a trustlevel1 account, after which you can add attachments.

If you do not specify the components of your gasmixture it is set by default to Argon. So you could just add the following line after you declared your MediumGas instance:

gas.SetComposition()

But on the other hand your tetramethylsilane is not included in magboltz, so this might lead to some error. About the Efields that do not match, I do not have an idea, you might want to put some cout in Medium.cc to see what happens when you call the method.

[1] Understanding Discourse Trust Levels

1 Like

Hi all,

thanks again for the helpful feedback.

I did a few additional tests to better understand the issue:

First, I switched to a P10 gas mixture (P10: 90% Ar, 10% CH₄). I generated a .gas file using Magboltz (these correspond to the red curve in the plot at the end). Then I loaded this gas file in Garfield++ and manually overwrote the transport parameters using the SetElectronVelocity , SetElectronTownsend , etc. functions with values from BOLSIG+ (these correspond to the blue curve).

Garfield::MediumMagboltz gas;
gas.LoadGasFile("ar_90_ch4_10.gas");
gas.SetFieldGrid(...);
gas.SetElectronVelocityE(...);
...

This approach does work in the sense that Garfield runs and reproduces a curve that differs slightly from the .gas file made purely from Magboltz. I would interpret this as the Magboltz transport parameters being modified toward the BOLSIG+ values.

I also tried a second approach: instead of starting from a Magboltz-generated gas file, I created a MediumGas object, set the composition to 90% Ar and 10% CH₄, and then directly filled the transport tables using the BOLSIG+ values, without running Magboltz first. In this case, the setup does not seem to work properly at all(these correspond to the green curve)..

Garfield::MediumMagboltz gas;
gas.SetComposition("ar", 90., "ch4", 10.);;
gas.SetFieldGrid(...);
gas.SetElectronVelocityE(...);
...

So in summary:

  • Starting from a valid Magboltz gas file and overwriting the transport parameters changes the resulting curves toward the BOLSIG+ transport parameters.
  • Constructing a gas purely from external transport data, without Magboltz, does not work as expected.

When comparing the resulting .gas files, the numbers inside the transport tables seem to be the same in both approaches. However, the .gas file that originates from a Magboltz-generated gas appears to contain additional information beyond the transport tables.

This makes me suspect that Garfield may require more internal information in the gas file than just the transport coefficients themselves, and that this extra information might be important for obtaining sensible results.

Looking at the source code, it seems that implementing a purely custom gas like TMS from externally calculated transport parameters may be quite challenging.

One possible workaround I am considering would be the following: start from a Magboltz-generated gas file for a molecule that is at least somewhat similar to TMS, for example silane, and then overwrite the transport parameters and possibly also the reaction rates.

My thought is that this might provide Garfield with the internal structure it expects from a valid Magboltz-based gas file, while still allowing me to replace the relevant transport data with values calculated externally.

I am not sure whether this is a sensible approach or whether Garfield relies too strongly on the original Magboltz-generated content for this to work properly, but I wanted to mention it as a possible direction.

Best regards
Max

Thanks Max for the investigations. Can you save the gas file after you have modified it with your TMS values and attach both this gasfile and your original gasfile (giving the green peaks)?

I cannot upload the .gas files directly, so I copied their contents into .txt files.

At the moment I am focusing on P10 (not TMS yet).

  • Blue curve : customP10_fromgasfile.txt (106.8 KB)
    This is based on a .gas file generated with Magboltz, where I replaced the transport parameters using SetElectronVelocity , SetElectronTownsend , etc.
  • Green curve : customP10.txt (20.9 KB)
    Here I define the gas composition manually and then set all transport parameters using the same functions (SetElectronVelocity , SetElectronTownsend , etc.).

What I find puzzling is that both files contain identical E-field values and partially identical table entries. However, the Magboltz-based file (blue curve) includes additional entries in the table.

My current suspicion is that these extra entries might be required internally and could explain why the Magboltz-based setup works while the manually constructed one does not.

Thanks for sending your gas files. Indeed you should not be worried that the original gas files contain information about the excitation and ionization levels of Ar and CH4. These are not used for plots of drift velocity or townsend coefficient.

I can see however a discrepancy between your custom gas file (green curve) and the gasfile based on an existing gas file. In your custom gasfile you have only 41 values for each E-field point, while in the one generated by magboltz you have 211 values. In the custom file, you have only zeros after the 20th position, while in the magboltz generated (version 13) you have non-zero values up till the end. To me it seems that your first approach does not generate a gasfile in the same format. I ll look into it further.

Here below you can see the first two Efield points:

Dear @Max_Schmit

Without the exact code of how you looped and filled your gas table and how you afterwards plotted it, it is not possible to debug. Just to try I made a small program (attached) that first reads a gasfile, obtains the efields, townsend, driftvelocity etc, and then creates a second gasfile where it creates first the structure of the efields and then fills in all swarm parameters. Plotting the electron drift velocity or townsend coefficient results in overlapping plots.

regenerate.C (4.1 KB)

Hi, thanks for your answer.

I am essentially doing the same thing in my code:
bolsig3_to_gas.cc (13.7 KB)
When I plot the drift velocity directly within that code, the curves overlap as expected (with a small difference, because Bolsig+ computes transport parameters differently):


However, the behavior changes again when I use a separate script: plot_velocity3.C (1.0 KB)
In this second code, I load the two .gas files:

  • one generated using Bolsig+ values
  • one generated with Magboltz

In this case, I obtain completely different drift velocity curves:


So the discrepancy only appears after writing and reloading the .gas files.

Could you try the same procedure on your side—i.e., plotting after reloading the newly written .gas file? I’d be interested to see if you observe the same behavior.

Dear @Max_Schmit

I confirm. When I feed that copied gasfile into Examples/Gasfile/read.C or printTable.C then I get the same behaviour as you have. This confirms what you observe, very interesting :slight_smile: