Nuclides enum library

Posted on

Problem

I’m currently developing a program in nuclear physics and i would like to have a simple access to all nuclides.

Technical specifications are :

  • Since informations are static, I want them to be hard coded. (means no external file to read) -> Enum seems to be a good starting point then.
  • Each nuclide should carry :
    1. Atomic number A
    2. Mass number Z
    3. Isomeric number I
    4. Half decay time
    5. Natural decay mode
  • The access via an API should be really really simple something like Nuclides.get(Z,A,I) / Nuclide.get("C14") or equivalent is recommended.
  • The number of nuclides is almost 3000.

I tried this but it uses reflexion for the get method (example Nuclide.get("Li10"))and it seems to be an overkill :

public class Nuclide {
    public static String[] Symbols = {"H", "He", "Li", "Be", "B", "C", "N", "O", "F", "Ne", "Na", "Mg", "Al", "Si", "P", "S", "Cl", "Ar", "K", "Ca", "Sc", "Ti", "V", "Cr", "Mn", "Fe", "Co", "Ni", "Cu", "Zn", "Ga", "Ge", "As", "Se", "Br", "Kr", "Rb", "Sr", "Y", "Zr", "Nb", "Mo", "Tc", "Ru", "Rh", "Pd", "Ag", "Cd", "In", "Sn", "Sb", "Te", "I", "Xe", "Cs", "Ba", "La", "Ce", "Pr", "Nd", "Pm", "Sm", "Eu", "Gd", "Tb", "Dy", "Ho", "Er", "Tm", "Yb", "Lu", "Hf", "Ta", "W", "Re", "Os", "Ir", "Pt", "Au", "Hg", "Tl", "Pb", "Bi", "Po", "At", "Rn", "Fr", "Ra", "Ac", "Th", "Pa", "U", "Np", "Pu", "Am", "Cm", "Bk", "Cf", "Es", "Fm", "Md", "No", "Lr", "Rf", "Ha", "Sg", "Ns", "Hs", "Mt", "Ds","Rg"};
    private String symbol;
    private int Z;
    private int A;
    private int isomericState;
    private String reactions;
    private double decayTime;

    private Nuclide() {}
    private Nuclide(String symbol, int A, String isomericState, double decayTime, String reactions) {
        this.symbol = symbol;
        this.A = A;
        this.isomericState = determineIsomericState(isomericState);
        this.Z = determineZ(symbol);

    }

    private static int determineZ(String symbol) {
        for (int i = 0; i < Symbols.length; i++) {
            if (symbol.equals(Symbols[i])) return i + 1;
        }
        return -1;
    }
    private int determineIsomericState(String isomericState) {
        if (isomericState.equals("gs")) this.isomericState = 0;
        else if (isomericState.equals("m")) this.isomericState = 1;
        else if (isomericState.equals("m2")) this.isomericState = 2;
        else if (isomericState.equals("m3")) this.isomericState = 3;
        else {
            System.out.println(isomericState + " isomere inconu");
            this.isomericState = -1;
        }
        return this.isomericState;
    }
    public static Nuclide get(String name) {
        char[] nameArray = name.toCharArray();
        String symbol = "";
        String A = "";
        String I = "";
        boolean isSymbolRead = false, isARead = false;
        for (char c : nameArray) {
            if (Character.isLetter(c) && !isSymbolRead) {symbol += c;}
            if (Character.isDigit(c) && !symbol.equals("") && !isARead) {
                isSymbolRead = true;
                A += c;
            }
            if (c == 'm' && isSymbolRead) {isARead = true;}
            if (Character.isDigit(c) && isSymbolRead && isARead) {I += c;}
        }
        String classPath = Nuclide.class.getCanonicalName();
        try {
            Class<?> c = Class.forName(classPath + "$" + symbol);
            Object[] objects = c.getEnumConstants();
            for (Object obj : objects) {
                if (obj.toString().equals(symbol + A + I)) {
                    Method method = obj.getClass().getDeclaredMethod("getNuclide");
                    Nuclide nuclide = (Nuclide) (method.invoke(obj));
                    return nuclide;
                }
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
        return null;
    }


//Then all enums

public enum n {
        n1(1, "gs", 6.146000e+02, "b-:1.000000e+02");
        private Nuclide nuclide;
        n(int A, String isomericState, double decayTime, String reactions) {this.nuclide = new Nuclide("n", A, isomericState, decayTime, reactions);}
        public Nuclide getNuclide() {return nuclide;}
    }
    public enum H {
        H1(1, "gs", 0.000000e+00, "s"),
        H2(2, "gs", 0.000000e+00, "s"),
        H3(3, "gs", 3.891050e+08, "b-:1.000000e+02"),
        H4(4, "gs", 1.000000e-22, "n:1.000000e+02"),
        H5(5, "gs", 8.000000e-23, "n:1.000000e+02"),
        H6(6, "gs", 3.200000e-22, "nn:1.000000e+02");
        private Nuclide nuclide;
        H(int A, String isomericState, double decayTime, String reactions) {this.nuclide = new Nuclide("H", A, isomericState, decayTime, reactions);}
        public Nuclide getNuclide() {return nuclide;}
    }
    public enum He {
        He3(3, "gs", 0.000000e+00, "s"),
        He4(4, "gs", 0.000000e+00, "s"),
        He5(5, "gs", 7.600000e-22, "n:1.000000e+02"),
        He6(6, "gs", 8.081000e-01, "b-:1.000000e+02"),
        He7(7, "gs", 2.900000e-21, "n:1.000000e+02"),
        He8(8, "gs", 1.220000e-01, "b-:8.800000e+01,b-n:1.200000e+01"),
        He9(9, "gs", 7.000000e-21, "n:1.000000e+02"),
        He10(10, "gs", 2.700000e-21, "nn:1.000000e+02");
        private Nuclide nuclide;
        He(int A, String isomericState, double decayTime, String reactions) {this.nuclide = new Nuclide("He", A, isomericState, decayTime, reactions);}
        public Nuclide getNuclide() {return nuclide;}
    }
    public enum Li {
        Li4(4, "gs", 9.100000e-23, "p:1.000000e+02"),
        Li5(5, "gs", 3.000000e-22, "p:1.000000e+02"),
        Li6(6, "gs", 0.000000e+00, "s"),
        Li7(7, "gs", 0.000000e+00, "s"),
        Li8(8, "gs", 8.380000e-01, "b-a:1.000000e+02"),
        Li9(9, "gs", 1.783000e-01, "b-:5.050000e+01,b-n:4.950000e+01"),
        Li10(10, "gs", 2.000000e-21, "n:1.000000e+02"),
        Li11(11, "gs", 8.590000e-03, "b-:9.100000e+00,b-n:8.490000e+01,b-n:4.100000e+00,b-n:1.900000e+00"),
        Li12(12, "gs", 1.000000e-08, "n:1.000000e+02");
        private Nuclide nuclide;
        Li(int A, String isomericState, double decayTime, String reactions) {this.nuclide = new Nuclide("Li", A, isomericState, decayTime, reactions);}
        public Nuclide getNuclide() {return nuclide;}
    }

// ...
}

I found another post which is just for element table. I didn’t get the point of the private static Holder class !?

Is there a better way to define the Nuclide class (without reflexion) or I should stick to this definition ? Thank you

Solution

It’s a pity that there is a restriction to avoid the usage of an external resource file. This would be more flexible to read the data from elsewhere, thus separating the concrete stuff from its abstract representation in the code. And in case of a fix/update no rebuild of the application would be necessary.

Ok, so let’s remain on the requirement that the data should be hardcoded.

Before remarks on the design choices, here are some observations about the improvement of the current code.

The Private Fields

Since the information wrapped in Nuclide mostly prepresents constants, all the fields should be final.

private final String symbol;
private final int z; // lower case and should better be called "massNumber"
private final int a; // lower case and should better be called "atomicNumber"
private final int isomericState;
private final String reactions;
private final double decayTime;

BTW, the static Sympols should be upper-cased.

determineZ

It’s OK for the loop, but the return statement should be replaced with

throw new IllegalArgumentException("Failed to determine Z, invalid symbol: " + symbol);

This prevents from having invalid data, since all the expected symbols are normally declared in SYMBOLS.

determineIsomericState

  • The multiple if-else-if are overkill, this case can also be seen by analogy with determineZ method: declare a constant array with expected isometric states and return the i + 1 of the one that matched in the loop.

  • isomericState ref should not be assigned inside this method, only the int value should be returned.

Nuclide get(String)

Well, the contents of this method is really too complex and firstly should be split in two parts (dedicated methods): 1) parse name arg in order to extract symbol, a and i; 2) invoke getNuclide method on the target object.

name Parsing

This approach with a sort of ad-hoc parser is particularly difficult to test, because of the high complexity of if conditions.

There is a much simpler way to implement it: regular expressions! It looks like the name arg is expected to be something like “He7m2”. A corresponding regular expression would be ^([A-Za-z]+)(d+)m(d+)$. The values in the matched groups will correspond respectively to symbol, a and i. If name does not match the expression, this is a good reason to throw another IAE.

getNuclide Invocation

The use of reflection is the drawback of the choice of enums to wrap the data.

If you have to keep the enums, there might be a simplification, avoiding the brutal method invocation by name. It consists of the following:

1) Create an interface that provides access to the target method:

interface NuclideAware {
  Nuclide getNuclide();
}

2) Make each enum implement this interface by adding implements NuclideAware to the headers. The method is already implemented in each one.

3) Reduce the reflective invocation to the following:

final String targetNuclide = symbol + A + I;
try {
  Class<?> c = Class.forName(classPath + "$" + symbol);
  Object[] objects = c.getEnumConstants();
  for (Object obj : objects) {
    if (obj.toString().equals(targetNuclide)) {
      return ((NuclideAware) obj).getNuclide();
    }
  }
} catch (ClassNotFoundException e) {
  e.printStackTrace();
}

But this still remains rather brutal and rigid. There should be a much more flexible way to implement the entire thing using more OOP features.

Since this answer becomes much longer than the question, I suggest doing the job in two steps. If you find useful the remarks I wrote here, please apply them and publish a follow-up question where we will discuss further improvements.

Leave a Reply

Your email address will not be published. Required fields are marked *