GeolOil copyrighted Logo
Home link icon Products Free Download Prices About GeolOil Contact



GLS Reference Manual and API Documentation


The GeolOil GLS is a minimal, yet powerful simple scripting programming language specifically designed to process petrophysics LAS files, just that. It allows easy handling of LAS curves without worrying too much about the dummy or missed -999.25 values. It also includes several methods to process LAS file curves.

Although some minimum exposure to basic object oriented programming would be desired, the built-in recipe cookbook examples that runs out of the box with your GeolOil license.

We strongly recommend to read and run the recipe examples from the GLS cook-book before digging into these heavy formal reference details.

GLS core foundation classes: LogLas and LogCurve

The core of GLS consists of two major classes: the class LogLas, and the class LogCurve. The class LogLas handles the loading of LAS files. The class LogCurve is a sub-class of LogLas that allows custom operations on LAS curves. Once a LAS file is loaded as an object LogLas, all successive operations through the class LogCurve are applied to the current LAS LogLas object.

The class LogLas: Constructor

The class LogLas, loads into the computer memory a full LAS file. The single "Ruby style" constructor:

           logLas = lasFileFullPathstring

defines a variable called "logLas" as the holder of the LAS file found in a full directory or folder path text string. For example, if you want to load the example LAS file referred in the SW recipe, you will need open your license to open the example script:

[File] -> [Open_GLS_Script] -> [Open] -> [UserHomeDir] -> [GeolOil_DB] -> [GLS_examples] -> [sw_example.gls] -> [Run]

The source code lines to open the file into the computer are: (notice that all text to the right of the symbol "#" are comments ignored by the computer)

           userHomeDir = ENV['HOME'] + '/'                         # Sets userHomeDir as a string to hold the user home folder.
           dataBaseDir = userHomeDir + 'GeolOil_DB/'               # String variable dataBaseDir holds the sample data set.

           fileName = dataBaseDir + 'GLS_examples/sw_example.las'  # Variable fileName holds the full absolute file path.
           logLas   = fileName                          # Variable logLas of type LogLas, holds the LAS file.

The class LogLas: Methods

The class LogLas, has few fundamental methods:

  • append logCurve, mnemonicName, units, apiCode, description

    This instance method appends or adds the new processed curve called "logCurve" of type LogCurve to the list of curves in the current LogLas LAS object. It is added or appended to the end of the curve list. The user must provide all arguments of the new curve: its Mnemonic or short name without spaces, Units, API code (enter "" if unknown), and a short Description (The Description field accepts blank spaces). The following line of code appends a new water saturation curve called "sw_arch" to the list of curves for the current LAS file object:

    logLas.append sw_arch, "SW_ARCHIE", "V/V", "", "Archie Water Saturation"

  • export fileName

    This instance method exports or saves the current LogLas LAS object into a file called "fileName" in the current working directory or folder. A string (enclosed by '') with a full file path might be used. The following example exports or saves the LAS object to a complaint regular LAS 2.0 file named "sw_models.las"

    exportFile = userHomeDir + 'sw_models.las'
    logLas.export exportFile

  • moveCurve fromPosition, toPosition

    This instance method moves a curve of the current LogLas LAS object from the original integer count position "fromPosition", to the target position "toPosition". The curve numbering starts from 1, being this first curve always the logged Measured Depth. The following example moves the curve held in the position 6, to a new position 3:

    logLas.moveCurve 6, 3

The class LogCurve: Constructors

The class LogCurve holds into a variable, a LAS curve from the current LogLas LAS object. The class has two constructors. The first constructor without arguments, creates a bare empty curve, filled at all depths with missed values -999.25. The second constructor takes as the argument the integer position of the curve:

           phiCore =     # Creates a new, empty curve variable called "phiCore"
           phiDens = 17  # Loads the 17th curve of the LogLas object into a new variable "phiDens"

The class LogCurve: Methods List

The class LogCurve, has the following fundamental methods. Refer to the examples in the GLS cook-book to learn by practice. Click on each method to see more details.

Looping over all Depths

Sooner or later, the user will need to do custom operations and algorithms not covered by the class LogCurve. For these cases, a loop over all LogLas LAS depths will be needed. The instruction "filterByCurves" and its accompanying closing instruction "endDepthLoop" handles such a loop.

The loop must skip those depths for which computations involve missed or blank -999.25 values. Otherwise, mathematical computations using -999.25 will yield misleading results. The arguments to the right of the instruction filterByCurves are the curves for which it is wanted to skip -999.25 values.

For instance, let's suppose we have to compute a linear volume of shale indicator based on a Gamma Ray curve "gr". Since gr may be available only for some depths, we have to filter the loop to skip those depths where gr is missed (i.e it has -999.25 values). The following example will compute the indicator:

           GRclean =  24                                    # GR reading for clean sand bodies.
           GRshale = 173                                    # GR reading for shale bodies.
           vsh =                               # Initializes to -999.25 a vsh curve to store results.
           filterByCurves  gr                               # Missed gr values of -999.25 won't enter into the loop
              @vsh = (@gr - GRclean) / (GRshale - GRclean)  # Values of curves in depth use sigil @

Notice that "@gr" represents a scalar value of the LogCurve curve at a current depth, but "gr" represents the whole LogCurve curve object. That is, curve values inside a filterByCurves loop must be preceded by the sigil "@" so they are correctly referred as real number scalars, instead of objects.

Notice also that the curve vsh was not included in the filter parameter list to the right of filterByCurves. This is because vsh at the the time when vsh enters into the loop, has all its values as -999.25, since it was just initialized as a new curve. Should the curve vsh being filtered, the loop filterByCurves would not embrace any depths.

The following example shows a snippet of code to compute water saturation using the Simandoux model:

           filterByCurves rt, rw, rsh, phi, vsh
              glob_mult = 0.40 * @rw / (@phi * @phi)
              t1        = 5.0 * @phi * @phi / (@rw * @rt)
              t2        = @vsh * @vsh / (@rsh * @rsh)
              t3        = @vsh / @rsh
              @sw_sim   = glob_mult * (Math.sqrt(t1+t2) - t3)

The class LogCurve: Methods Details

The class LogCurve, has the following fundamental methods. Please refer to the examples in the GLS cook-book to learn by practice.

  • setOutliers string

    This instance method defines how to process outlier values. For example, let's assume that a "gr" Gamma Ray Curve with negative values is completely unacceptable and indicates a bad response of the tool. Then we must discard completely those readings and replace them with -999.25. Likewise, let's assume that high readings, say beyond 250.0 API units, are no reliable and should be replaced or trimmed to 250. Then, the following instruction will take care of that:

    gr.setOutliers "Reject 0.0 250.0 Trim"

    If we think that GR readings beyond 250.0 are right and say reflect very radioactive sandstones, we must accept those readings, but to reject negative values:

    gr.setOutliers "Reject 0.0 250.0 Accept"

  • setMissedDepths string

    This instance method sets a series of depths in the curve to hold -999.25 values. For example, if for some reason a "gr" Gamma Ray Curve holds erratic unreliable values from shallow measured depths of 0-175 and from 495 and deeper, we may clear and set those depths (only in the Gamma Ray Curve) by:

    gr.setMissedDepths "0-175, 495-"

  • setFilteredDepths string

    This instance method is the reverse of the method setMissedDepths. That is, it sets all depths to missed values -999.25, except the depths declared in the intervals. For example, the following instruction will only keep alive the values from 0.0 to 175.0 and from 495.0 and beyond. All remaining values from 175-495 will be set to -999.25.

    gr.setFilteredDepths "0-175, 495-"

  • setValue propertyValue, measuredDepth

    This instance method sets one value of a curve for the given depth. GeolOil GLS will replace any former curve value with the new one collocated at the closest possible depth bin of the LAS file. Let's suppose that a photo-electric factor curve (with a typical mnemonic of "PEF") has a bad reading at a depth of 2106.81 foot, and we want to replace just only a wrong value of 12 B/E to a dolomitic rock of 3.1 B/E, then the next instruction will do the job:

    pefCurve.setValue, 3.1, 2106.81

    If the current LAS file has a depth resolution, say of 0.25 foot, the updated pefCurve curve will now hold the value 3.1 at the depth bin of MD = 2106.75.

  • setValue propertyValue, string

    This overloaded instance method behaves similarly to the method "setValue realNumber, realNumber", but sets curve values for depth intervals. The method is ideal when it is desired to build a new synthetic curve from core descriptions or lab studies.

    For example, let's suppose that a formation consists of a series of marine environment lithologies anhydrite, limestome, salt, and dolomite, and we need to build a variable matrix sonic transit time curve, just to compute a careful sonic porosity curve by the Wyllie equation. Then the following lines of code will define a variable "matrixDTcurve" compressive matrix time curve:
               # Step 1: Define compressive travel transit times
               shaleDT     = 120.0  # Units: micro-seconds per foot. (us/ft)
               sandstoneDT =  53.0  # Units: micro-seconds per foot. (us/ft)
               limestoneDT =  47.6  # Units: micro-seconds per foot. (us/ft)
               dolomiteDT  =  43.5  # Units: micro-seconds per foot. (us/ft)
               anhydriteDT =  50.0  # Units: micro-seconds per foot. (us/ft)
               saltDT      =  66.7  # Units: micro-seconds per foot. (us/ft)
               # Step 2: Use the core description study to define lithology zones
               zone01 = "5450.0-5459.0"  # Anhydrite zone
               zone02 = "5459.0-5477.0"  # Limestone zone
               zone03 = "5477.0-5481.5"  # Dolomite  zone
               zone04 = "5481.5-5488.0"  # Limestone zone
               zone05 = "5488.0-5495.0"  # Anhydrite zone
               # Step 3: Build the target matrix curve from the core described lithology
               matrixDTcurve =                    # Starts with a bare -999.25 place-holder
               matrixDTcurve.setValue  shaleDT, "0.0-10000.0"  # Start with a default shale background
               matrixDTcurve.setValue  anhydriteDT, zone01
               matrixDTcurve.setValue  limestoneDT, zone02
               matrixDTcurve.setValue  dolomiteDT,  zone03
               matrixDTcurve.setValue  limestoneDT, zone04
               matrixDTcurve.setValue  anhydriteDT, zone05

  • copy logCurve

    This instance method makes a deep copy (or a full object clone) or a LogCurve object into another LogCurve object. For example if we want to copy the content of a curve called "gr1" into a curve already called "gr2", we must type:

    gr2.copy gr1

    Warning: Please notice than the instruction: gr2 = gr1 would yield a completely different behaviour: assigning the same memory address of gr1 to gr2. This could be a potential disaster, since all modifications done to either gr1 or gr2 would be identically repeated in each curve memory reference. The right instruction gr2.copy gr1 will allow to have each curve independent modifications after the copy.

    For scalar variables, like real numbers and strings, the operator equals "=" behaves as expected and each variable will have its independent life.

  • swap logCurve

    This instance method swaps or exchanges two logCurve objects. For example, the following instruction will swap the curves gr1 and gr2:

    gr2.swap gr1

  • overlayOver logCurve

    This instance method overlays two logCurve objects. To understand the method, imagine each curve like a transparent plastic slide sheet. The original, current, or "this" sheet curve object is placed at the top, and the argument or parameter sheet curve is placed under the original. Then we could only see the bottom curve sheet in those locations where the original curve is "transparent" or its values are "void holes" of -999.25 (non -999.25 are considered as "opaque"). Then the resulting image is captured in place in the original curve.

    As an example let's suppose a LAS file has two versions of Gamma Ray curves. We want to keep the curve "gr1" as the main original one, and complete its missed -999.25 values with information from the argument curve "gr2". Then following instruction will get the job done:

    gr1.overlayOver gr2

    We can illustrate the method through a numerical example:
               Measured Depth MD :   103.00  103.50  104.00  104.50  105.00   105.50
                             gr1 :    67.83   89.20 -999.25 -999.25   56.74  -999.25
                             gr2 :  -999.25   87.97 -999.25   66.48   49.89    38.61
             gr1.overlayOver gr2 :    67.83   89.20 -999.25   66.48   56.74    38.61
    And the resulting operation is stored in place into the original gr1 curve. However, what would happen if the curve gr2 is available on a different LAS file with a depth resolution of 0.50 ft, and our main LAS file has a higher resolution of 0.25 ft.? Then we will need to merge the curve gr2 onto the target LAS file and then unify the curves into a third one. The following lines of code shows how to do that:
               # Step 1: Export the curve gr2 from the low resolution 0.50 ft LAS file
               logLas = 'lasFile_half_foot.las'
               gr2 = 7        # Let's suppose GR is on column 7
               gr2.export  'gr2_data.txt'  # Export gr2 curve {(MD,GR)} from the low res LAS
               # Step 2: Now go to the main LAS file, import gr2, overlay curves and unify.
               highResLAS = 'lasFile_main_highRes'
               logLas = highResLAS
               grLowRes =     # LogCurve inherits LogLas: all curves refer to highResLAS
               grLowRes.readFile 'gr2_data.txt'
               grLowRes.fillGaps  0.50     # Recommended to avoid -999.25 gaps for the needed 0.25 ft res.
               gr1 =  5       # Let's suppose GR is on column 5
               grUnified =    # Will hold the unified GRs
               grUnified.copy gr1
               grUnified.overlayOver grLowRes

  • overlayUnder logCurve

    This instance method overlays two logCurve objects like the method overlayOver, but overlayUnder places the original or current curve under the argument or parameter curve. We can illustrate the method through a numerical example:
               Measured Depth MD :   103.00  103.50  104.00  104.50  105.00   105.50
                             gr2 :  -999.25   87.97 -999.25   66.48   49.89    38.61
                             gr1 :    67.83   89.20 -999.25 -999.25   56.74  -999.25
            gr1.overlayUnder gr2 :    67.83   87.97 -999.25   66.48   49.89    38.61
    And the resulting operation is stored in place into the original gr1 curve.

  • calibrate realNumber

    This instance method adds or subtracts a constant to a logCurve object. For example if "perm" represents an absolute horizontal permeability in mili-darcies that needs to be calibrated against lab core data by subtracting say 10 mD, the following instruction will modify the permeability in place:

    perm.calibrate -10.0

  • calibrate realNumber, realNumber

    This overloaded instance method applies a linear transformation of the type: y = a0 + a1*x, to the logCurve object x. The most common use of this method is when one needs to apply an unit conversion to a curve. For instance, let's suppose that a curve "poro" comes in percentage units (base 100.0), and it is needed to be converted to adimensional units (base 1.0 or V/V). Then, by identifying the parameters a0=0 and a1=0.01, the following instruction will make the units conversion in place into the same curve:

    poro.calibrate 0.0, 0.01

    If we need to retain the original porosity curve, and get a one converted, the following instruction will get the job done:
               phi =
               phi.copy poro
               phi.calibrate  0.0,  0.01
  • calibrate realNumber, realNumber, realNumber

    This overloaded instance method applies a quadratic transformation of the type: y = a0 + a1*x + a2*x*x, to the logCurve object x.

  • print

    This static method prints scalar values to the GLS output window device. This particular method does not need to be prepended by a logCurve object. See examples of its use in this recipe.

  • readFile fileName

    This instance method reads a plain text file formatted as two columns, the measured depth, and its signal at that depth: {(MD, Signal)}, and populates a new curve (any former values are erased to -999.25) with fileName data.

    The original depths from the fileName source can be irregularly spaced and randomly ordered. They don't have to match the depth pattern of the original LAS file curve. The method is commonly used to import core data as a new curve, and also to import a curve already exported from another source (like importing ASCII digitized curves as two columns: MD, curveValue) and do a merge. The following example loads core data into a new curve called "labPhi".
               labPhi =
               coreDataFile = 'corePorosityMdValue.txt'
               labPhi.readFile  coreDataFile
    GLS will be smart enough to skip file headers, -999.25 missed values, blank lines, and commented lines, which must start with the character "#". For instance, the content of the file corePorosityMdValue.txt could be:
                MD     PHI_Core
              FOOT          V/V
            # 1655.76      0.37  (Note: fractured plug measure could no be reliable)
              1410.08   -999.25
              1681.31      0.14
    In this example, only the pair (MD=1681.31; PHI_Core=0.14) will be uploaded, since one of the lines is commented, and the other one has a missed -999.25 value. If the original core data wasn't corrected for depth shift, the user should shift the depths to properly match the log MD with the methods: shift constantDepthShift, or shift string.

  • readFile fileName, columnMD, columnProperty

    This instance overloaded method behaves exactly like the same one parameter method "readFile fileName", with the difference that the method can process data coming from multi-column plain text files. The parameter columnMD specifies which column number stores the Measured Depth, and the parameter columnProperty specifies in which column the property is stored at the given depth.

    This method is ideal to import multi-column core lab data originally stored in Excel spreadsheets into the current logCurve object. Once the data is exported to a regular ASCII space separated values plain text file, GLS will be glad to import it without problems. Let's suppose we want to define a new curve called "grainDensity" to store core measured values of the rock grain density after the rock is crushed, from a file called "coreData.txt":
               MD      PHI_Core  GrainDensity    SW
               FOOT         V/V     GR/CC        V/V
               1681.31     0.14      2.67       0.32
               1677.54  -999.25      2.63       0.43
    Since the depth is in the first column, and the grain density is in third column, the following lines of code will populate the curve grainDensity:
               labPhi =
               coreDataFile = 'coreData.txt'
               labPhi.readFile  coreDataFile, 1, 2  # MD in column 1, PHI in column 2
               grainDensity =
               grainDensity.readFile  coreDataFile, 1, 3  # Grain density in column 3

  • readFileSignalMd fileName

    This instance method behaves exactly like the method readFile, with the difference that the data coming from the plain text file "fileName" is formatted as two columns {(Signal, MD)} instead of {(MD, Signal)}. This is useful when fileName data comes from digitized sources that outputs first the signal value as the first column, and the depth as the second column.

  • readFileOver fileName

    This instance method is similar to readFile. However, the {(MD, Signal)} values read from fileName are placed on top of the original curve values, opposed to the method readFile, which first erases all the original values to -999.25. The instruction:
               gr1.readFileOver fileName
    is equivalent to:
               newGR =       # New curve is initialized at all depths to -999.25
               newGR.readFile  fileName   # Now newGR contains data {(MD, GR)} from fileName.
               gr1.overlayUnder  newGR    # Original gr1 preserved, except for new fileName data.
    A typical application of the method is when an already populated curve is updated to pass through core lab data. For example, if "phie" is an effective porosity curve estimated from logs, and core lab porosity data {(Depth, CorePorosity)} is available in a fileName called "phiLabData.txt" the following instruction will enforce the computed curve phie to honour the core porosity values measured in the lab:
               phie.readFileOver  phiLabData.txt

  • readFileOver fileName, columnMD, columnProperty

    This instance overloaded method behaves like the same single parameter method, but allows input from multi-column data, specifying the column for the depth, and the column for the property.

  • readFileUnder fileName

    This instance method is similar to readFileOver, but only adds to the original curve the fileName data, if the curve content at its depth is missed (-999.25). That is, all the original curve values are preserved if they are available, and complemented with data from fileName if not, avoiding any data collision conflicts or replacements. The instruction:
               gr1.readFileUnder fileName
    is equivalent to:
               newGR =       # New curve is initialized at all depths to -999.25
               newGR.readFile  fileName   # Now newGR contains data {(MD, GR)} from fileName.
               gr1.overlayOver  newGR     # Original gr1 preserved, complemented with fileName data.

  • readFileUnder fileName, columnMD, columnProperty

    This instance overloaded method behaves like the same single parameter method, but allows input from multi-column data, specifying the column for the depth, and the column for the property.

  • shift constantDepthShift

    This instance method shifts all the depths of the current LogCurve object by a constant value. This is useful to calibrate depths from a LAS file that was assembled from curves coming from different source tools not properly depth matched. Each curve can be individually shifted to be deeper (with a positive shift), or to be shallower (using a negative shift parameter).

    A more common application is to match core lab depth data. Comparing the Core-Gamma against the logged Gamma Ray curve, a constant shift parameter can be inferred. Let's suppose we have the following original core lab porosity plain text file, (with irregularly spaced depths) called "phiCoreData.txt":
               Depth   CorePorosity(%)
               100.06    13.54
                87.84    19.06
               105.42    21.13
    Now, if we have a LAS file with depth steps, say of 0.50 foot, and want to shift the core data to be 3.5 foot shallower to match log depths, the following instructions will merge and shift the core porosity data by appending a new LogCurve with the core information to the current LAS file:
               phiCore =               # New LogCurve place holder for core data.
               phiCore.readFile  'phiCoreData.txt'  # Read the irregularly spaced core data.
               phiCore.calibrate  0.00,  0.01       # Convert % units to fractional V/V units.
               phiCore.shift     -3.5               # Shift upwards core depths 3.5 foot.
    GeolOil GLS will compute and collocate automatically each new shifted core depth to the closest possible depth bin of the regularly spaced LAS file. That is, the original irregular core depth of 100.06 foot, will be finally collocated into the regular LAS depth bin of MD=96.50, since the target LAS file has a depth resolution of 0.50 foot.

  • shift stringWithVariableDepthShift

    This overloaded instance method behaves similar to the single parameter method "shift constantDepthShift". However, it allows variable deformation shifts, rather than a constant displacement shift. The string must contain a mapping series for each original depth and its target depth to be collocated in the string format:

    "originalDepth1-targetDepth1, originalDepth2-targetDepth2, ..., etc."

    For instance, let's suppose we have some core porosity data and it has been correlated that the core depth of 5627.7 foot should be mapped as log measured depth of 5620.1 foot, core depth of 5630.3 as 5626.4, and 5506.2 as 5500.8. Then the following instructions will take care of all the appropriate ordering and smooth shifts:
               phiCore = LogCurve .new
               phiCore.readFile  'corePorosityData.txt'
               phiCore.shift  "5627.7-5620.1, 5630.3-5626.4, 5506.2-5500.8"

  • interpolateSpline

    This instance method fills all the gaps (values -999.25) of a LogCurve object through natural splines. It works very well when the original curve is stable and not very noisy or spiky. The user should always plot the curve and judge the result. If the original curve is somewhat unstable, a classical linear interpolation with the method "interpolateLines", or a blended method like "interpolate 0.50" could yield better results.

    The methods interpolateSpline, interpolateAkima, interpolateLines, and interpolate are typically used when the original data comes from a digitized source, like log blue prints or legacy old log raster images. The following example interpolates by natural splines, a digitized curve from a text file recorded as regular two columns plain text file {(MD, Signal)}
               digitizedFile = 'digitizedGR.txt'   # Text file with two columns of data (MD, Value).
               digGR =                # Curve to hold the digitized data.
               digGR.readFile digitizedFile        # Load the digitized data into the new curve.
               digGR.interpolateSpline             # Interpolate blank gaps in MD.
  • interpolateAkima

    This instance method behaves exactly the same as interpolateSpline. However the Hiroshi Akima splines are used to interpolate the missing -999.25 instead of natural splines. Akima splines might yield (some times not) a less spiky behaviour than natural splines. Example:


  • interpolateLines

    This instance method fills all the gaps (values -999.25) of a LogCurve object with straight lines. It is the most conservative and traditional method of interpolation. Example:


  • interpolate linearBlendingProportion

    This instance method fills all the gaps (values -999.25) of a LogCurve object with a linear combination of straight lines and a spline interpolation. It usually gives good results and allows a precise control from the user. It uses a blending parameter between 0.0-1.0 to control the weight between the linear interpolation and the spline interpolation. The closer the blending parameter to 1.0, the more weight is given to the linear interpolation. The following example interpolates a curve, giving 60% of the weight to linear interpolation and 40% to splines.

    digGR.interpolate 0.60

  • fillGaps depthTolerance

    This instance method fills all the gaps (values -999.25) of a LogCurve object with linear interpolations. The gap parameter specifies how the linear interpolation is allowed to span. For example, if we want to fill all gaps of less than 2.50 foot and keep with -999.25 those gaps of more than 2.5 foot (or meters, depending on your LAS file), the following instruction will do the job:

    digGR.fillGaps 2.5

    Among several applications of the method, it is strongly recommended to be used when merging curves from different LAS file resolutions. For instance, let's suppose than we want to capture a curve from a LAS file of a low depth resolution of 1.0 foot, and merge it with the curves of the target LAS file with a high resolution of say 0.25 foot.

    After the ordinary merge process, there will plenty of -999.25 gaps each 0.25 foot, since the low resolution LAS files does not hold information each 0.25 foot. Then the following example will make the imported merged low resolution curve usable after the merge:

    impGR.fillGaps 1.0

  • export fileName

    This instance method exports or saves a LogCurve into a file. The data is exported as a plain text file with two columns: the logged measured depth, and its curve signal: {(MD, Signal)}. Once a curve is exported out of a LAS file, it could be imported into a different LogLas LAS object to merge several curves. The following example exports a computed effective porosity curve called "phie" into a file:

    phie.export "effectivePorosity.txt"

Content, and web design © 2012-2019 GeolOil LLC. You are welcome to refer them. Please give us fair credits. Creative Commons License