private static void AddMissingResource(string resourceName, CelestialBody body, Dictionary <string, AtmosphericResource> bodyComposition)
        {
            // verify it is a defined resource
            var definition = PartResourceLibrary.Instance.GetDefinition(resourceName);

            if (definition == null)
            {
                Debug.LogWarning("[KSPI]: AddMissingResource : Failed to find resource definition for '" + resourceName + "'");
                return;
            }

            // skip it already registered or used as a Synonym
            if (bodyComposition.Values.Any(m => m.ResourceName == definition.name || m.DisplayName == definition.displayName || m.Synonyms.Contains(definition.name)))
            {
                Debug.Log("[KSPI]: AddMissingResource : Already found existing composition for '" + resourceName + "'");
                return;
            }

            // retrieve abundance
            var abundance = GetAbundance(definition.name, body);

            if (abundance <= 0)
            {
                Debug.LogWarning("[KSPI]: AddMissingResource : Abundance for resource '" + resourceName + "' was " + abundance);
                return;
            }

            // create resource from definition and abundance
            var resource = new AtmosphericResource(definition, abundance);

            // add to composition
            Debug.Log("[KSPI]: AddMissingResource : add resource '" + resourceName + "'");
            bodyComposition.Add(resource.ResourceName, resource);
        }
        private static void AddResource(string outputResourceName, string displayName, CelestialBody body, IDictionary <string, AtmosphericResource> atmosphericResourcesByName, string[] variantNames, double abundanceExponent = 1)
        {
            double finalAbundance;

            AtmosphericResource existingResource = FindAnyExistingAtmosphereVariant(atmosphericResourcesByName, variantNames);

            if (existingResource != null)
            {
                finalAbundance = existingResource.ResourceAbundance;
                Debug.Log("[KSPI]: using kspie resource definition " + outputResourceName + " for " + body.name + " with abundance " + finalAbundance);
            }
            else
            {
                var abundances = new[] { GetAbundance(outputResourceName, body) }.Concat(variantNames.Select(m => GetAbundance(m, body)));
                finalAbundance = abundances.Max();
                Debug.Log("[KSPI]: looked up stock resource definition " + outputResourceName + " for " + body.name + " with abundance " + finalAbundance);

                if (abundanceExponent != 1)
                {
                    finalAbundance = Math.Pow(finalAbundance / 100, abundanceExponent) * 100;
                }
            }

            var resource = new AtmosphericResource(outputResourceName, finalAbundance, displayName, variantNames);

            if (resource.ResourceAbundance <= 0)
            {
                return;
            }


            if (atmosphericResourcesByName.TryGetValue(outputResourceName, out existingResource))
            {
                atmosphericResourcesByName.Remove(existingResource.ResourceName);
            }
            atmosphericResourcesByName.Add(resource.ResourceName, resource);
        }