Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

RU 🇷🇺

...

Table of Contents

Overview

Sometimes you need to map a Block Model field that isn't coded in the original block model. Custom variables allow us to write complex logic in a readable and reusable way, which can then be mixed with other inline formulas in the block model mappings.

Table of Contents

Creating a custom variable

  1. Go to Setup > Block Model > Edit > Reservable Model Generator.

  2. Click Custom Variables to open the script editor.

  3. Clear all text from the code editor.

  4. Replace it with the sample code below.

...

Dry Tonnes

Code Block
languagec#
using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Linq; 
using Alastri.Scripting; 
using Alastri.BlockModel.Engine.CustomVariables;

public class DryTonnes : IDoubleCustomVariable
{
    public double GetNumber(CustomVariablesContext context)
    {
        double density = context.N("DENSITY");

        double volume = context.N("XINC")*context.N("YINC")*context.N("ZINC");
        
        return (density > 0 ? density * volume : 0);
        
    }
}

Grade Bins

...

languagec#

...

Block Model. For example, Ore and Waste definitions can be more complex and multiple grades and types inputs are required. In this case, to define different properties use Custom Variable functionality implemented through the Script Editor.

Scripting language is C#.

Inline formula vs Custom Variable

To define different properties you can write an inline formula using “IF” statement, such as If N(“Fe”) is greater than 60 return “hg” otherwise if N(“Fe”) is greater than 58 return “mg” otherwise if N(“Fe”) is greater than 57,5 return “lg1” otherwise if N(“Fe”) is greater than 56 return “lg2” otherwise if N(“Fe”) is greater than 50 return “minw” otherwise call it “waste”.

...

But such linear formulas can be too long and confusing. Therefore, if there are multiple properties and parcel types that need to be separated, it is recommended to use Custom Variables.

Custom variables allow us to write complex logic in a readable and reusable way, which can then be mixed with other inline formulas in the block model mappings.

...

Creating Custom Variable

  1. Go to Setup > Block Model > Edit > Reservable Model Generator.

  2. Click Custom Variables to open the Script Editor.

  3. Clear all text from the Script Editor.

  4. Replace it with one of the sample codes listed below.

  5. In the list of available variables on the right you will see a new variable CustomT("Parcel") in bold, map it to the Parcel field.

...

Custom Variables Examples

Dry Tonnes

Code Block
languagec#
using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Linq; 
using Alastri.Scripting; 
using Alastri.BlockModel.Engine.CustomVariables;

public class ParcelDryTonnes : ITextCustomVariableIDoubleCustomVariable
{
    public stringdouble GetTextGetNumber(CustomVariablesContext context)
    {
        double fedensity = context.N("feDENSITY");

        double volume   = context.N("XINC")*context.N("YINC")*context.N("ZINC");
   if(fe > 60)   
    return "hg";   return (density > 0 ? density else* if(fevolume >: 580);
  return "mg";     
   else if(fe > 57.5) return "lg1";
        else if(fe > 56)   return "lg2";  }
}

Grade bins

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Alastri.Scripting;
using Alastri.BlockModel.Engine.CustomVariables;

public class Parcel : ITextCustomVariable
{
    public string GetText(CustomVariablesContext context)
    {
        elsedouble if(fe > 50)= context.N("fe");
  return "minw";     
   else     if(fe > 60)        return "whg";
        }
}

Multiple Grade Bins

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Alastri.Scripting;
using Alastri.BlockModel.Engine.CustomVariables;

public class Parcel : ITextCustomVariable
{
    public string GetText(CustomVariablesContext context)
    {else if(fe > 58)   return "mg";
        else if(fe > 57.5) return "lg1";
        else if(fe > 56)   return "lg2"; 
        doubleelse if(fe = context.N("fe") > 50)   return "minw";
        doubleelse al = context.N("al");         string  geology =return context.T("geologyw");

    }
}

Multiple Grade Bins

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Alastri.Scripting;
 string fe_binusing Alastri.BlockModel.Engine.CustomVariables;

public class Parcel : ITextCustomVariable
{
    public string GetText(CustomVariablesContext context)
  if(fe > 60){
        double {fe = context.N("fe");
        double   fe_binal = context.N("60al");
        }string geology = context.T("geology");
     else if(fe > 55 
        string fe_bin;
        
        if(fe > 60) 
        {
             fe_bin = "60";
          fe = Math.Floor(fe); 
            fe_bin = fe.ToString("#,##0");
        }
        else
        {
           fe_bin = "50";
        }

        string al_bin;

        if(al < 3)
        {
}
        else if(fe > 55) 
        {
            fe = Math.Floor(fe); 
            fe_bin = fe.ToString("#,##0");
        }
        else
        {
           fe_bin = "50";
        }

        string al_bin;

        if(al < 3)
        {
           al_bin = "3";
        }
        else if(al < 6)
        {
           al = Math.Ceiling(al);
           al_bin = al.ToString("#,##0");
        }
        else
        {
          al_bin = "9";
        }

        string geoClass = "1";
        if(geology.Equals("detrital", StringComparison.OrdinalIgnoreCase))
        {
           geoClass = "2";
        }

        return fe_bin + "_" + al_bin + "_" + geoClass;
    }
}

Ore Ratio

Rapid Reserver block model fields cannot report Stripping Ratio, because Stripping Ratio is not a sum or weight average type field. Instead, we can report ore ratio, which is the weight average of ore tonnes over total tonnes.

To do this we can set up a weight-averaged field called OreRatio as a child of "dryTonnes" or "wetTonnes" (whichever you want to report). This field will report a "1" for ore and a "0" for waste. The weight average of the 1s and 0s across a blast becomes the ore ratio. 

Ore Ratio code

Code Block
languagec#
using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Linq; 
using Alastri.Scripting; 
using Alastri.BlockModel.Engine.CustomVariables;

public class OreRatio : IDoubleCustomVariable
{
    List<string> ores = new List<string>(){ "hg", "mg", "lg" }; //all lower case
     
    al_binpublic = "3";
double GetNumber(CustomVariablesContext context)
    {
  }      string matType  else if(al < 6)
        {= context.T("mattype").ToLower(); //material type field from original block model

        bool isOre   al= ores.Any(ore => MathmatType.CeilStartsWith(alore));
      
    al_bin = al.ToString("#,##0");  
      }  if(isOre) return 1;
    else    else return 0;
  {  }
}

Multiple Custom Variables

To create multiple custom variables, a class implementing the IDoubleCustomVariable or ITextCustomVariable interface needs to be created for each variable. These classes need to be listed under one another in the Custom Variables Script Editor, as shown in the examples below.

Multiple Custom Variables

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Text;
using  al_bin = "9";
        }

        string geoClass = "1";
        if(geology.Equals("detrital", StringComparison.OrdinalIgnoreCase))System.Linq;
using Alastri.Scripting;
using Alastri.BlockModel.Engine.CustomVariables;

public class Parcel : ITextCustomVariable
{
    public string GetText(CustomVariablesContext context)
    {
        {double fe = context.N("fe");
        geoClass
= "2";       if(fe > }

60)        return fe_bin + "_hg" +;
al_bin + "_" + geoClass;    else }
}

Ore Ratio

Rapid Reserver block model fields cannot report Stripping Ratio, because Stripping Ratio is not a sum or weight average type field. Instead, we can report ore ratio, which is the weight average of ore tonnes over total tonnes.

To do this we can set up a weight-averaged field called OreRatio as a child of "dryTonnes" or "wetTonnes" (whichever you want to report). This field will report a "1" for ore and a "0" for waste. The weight average of the 1s and 0s across a blast becomes the ore ratio. 

Ore Ratio

Code Block
languagec#
using System; 
using System.Collections.Generic; 
using System.Text; 
using System.Linq; 
using Alastri.Scripting; 
using Alastri.BlockModel.Engine.CustomVariables;

public class OreRatio : IDoubleCustomVariable
{
    List<string> ores = new List<string>(){ "hg", "mg", "lg" }; //all lower case
     
    public double GetNumber(CustomVariablesContext context)
    {
        string matType = context.T("mattype").ToLower(); //material type field from original block model

        bool isOre = ores.Any(ore => matType.StartsWith(ore)); 
        
        if(isOre) return 1;if(fe > 58)   return "mg";
        else if(fe > 57.5) return "lg1";
        else if(fe > 56)   return "lg2"; 
        else if(fe > 50)   return "minw";
        else               return "w";
    }
}

public class DryTonnes : IDoubleCustomVariable
{
    public double GetNumber(CustomVariablesContext context)
    {
        double density = context.N("DENSITY");

        double volume = context.N("XINC")*context.N("YINC")*context.N("ZINC");
        
        return (density > 0 ? density * volume : 0);
        
    }
}

Multiple variables with shared logic

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Alastri.Scripting;
using Alastri.BlockModel.Engine.CustomVariables;
 
//block model 1 has a "parcel1" variable
public class Parcel1 : ITextCustomVariable
{
    public string GetText(CustomVariablesContext context)
    {
        string elseparcel return 0;
    }
}

Multiple Custom Variables

To create multiple custom variables, a class implementing the IDoubleCustomVariable or ITextCustomVariable interface needs to be created for each variable. These classes need to be listed under one another in the Custom Variables Script Editor, as shown in the examples below.

Multiple Custom Variables

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Alastri.Scripting;
using Alastri.BlockModel.Engine.CustomVariables;

public class Parcel= context.T("mtype"); //block model 1 field
        return ParcelResolver.Parcel(parcel);
    }
}

//block model 2 has a "parcel2" variable
public class Parcel2 : ITextCustomVariable
{
    public string GetText(CustomVariablesContext context)
    {
        doublestring feparcel = context.NT("feioretype"); // block model 2 field
            if(fe > 60)return ParcelResolver.Parcel(parcel);
    }
}

//parcel logic return "hg";
        else if(fe > 58)   return "mg";
        else if(fe > 57.5) return "lg1";is wrapped up in the static ParcelResolver class
public static class ParcelResolver 
{
    private static List<string> _oreList = new List<string> 
    { 
        else if(fe > 56)   return"hg", "hg1", "bl1", "mg", "lg", "lg1", "lg2";, "mw"
    };
   else if(fe
> 50)   return "minw";
  public static string Parcel(string parcel) 
    else{
             if(_oreList.Contains(parcel.ToLower())) return "w";
    }
}

public class DryTonnes : IDoubleCustomVariable
{ore"; 
        else public double GetNumber(CustomVariablesContext context)return "waste";
    }
 {
        double density = context.N("DENSITY");

        double volume = context.N("XINC")*context.N("YINC")*context.N("ZINC");
        
        return (density > 0 ? density * volume : 0);}

Access Custom Variable from another Custom Variable

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Alastri.Scripting;
using Alastri.BlockModel.Engine.CustomVariables;

public class Parcel : ITextCustomVariable
{
       public string GetText(CustomVariablesContext context)
       {
              double fe = context.N("fe");              
              if(fe > }
}

Multiple variables with shared logic

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Alastri.Scripting;
using Alastri.BlockModel.Engine.CustomVariables;
 
//block model 1 has a "parcel1" variable
public class Parcel1 : ITextCustomVariable
{
    public string GetText(CustomVariablesContext context)60) 
                 return "hg";
              else if(fe > 58) 
      {         string parcel =return context.T("mtypemg"); //block model 1 field
  ;
              else if(fe > 57.5) 
    return ParcelResolver.Parcel(parcel);     } }  //block model 2 has areturn "parcel2lg1";
variable  public  class  Parcel2  :  ITextCustomVariable  {  else if(fe >  public56) string
GetText(CustomVariablesContext context)     {         string parcel =return context.T("ioretypelg2");
//  block  model  2  field      else if(fe >  50) 
return ParcelResolver.Parcel(parcel);     } }  //parcel logic is wrapped up in the static ParcelResolver class
public static class ParcelResolver 
{
 return "minw";
              else 
 private static List<string> _oreList = new List<string>      {    return "w";
       "hg", "hg1", "bl1", "mg", "lg", "lg1", "lg2", "mw"
    };
    
    public static string Parcel(string parcel) 
    {
        if(_oreList.Contains(parcel.ToLower())) return "ore"; 
        else return "waste";
    }
}

Access Custom Variable from another Custom Variable

Code Block
languagec#
using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using Alastri.Scripting;
using Alastri.BlockModel.Engine.CustomVariables;

public class Parcel : ITextCustomVariable
{ }
}

public class Recovery : IDoubleCustomVariable
{
     public double GetNumber(CustomVariablesContext context)
     {
           Parcel parcel = new Parcel(); //Create an instance of the Parcel object
           string p = parcel.GetText(context); //Call the GetText() method, store the result in variable 'p'

           double volume = context.N("XINC")*context.N("YINC")*context.N("ZINC");

           if(p == "hglp" ) 
               return volume * 1.20;
         public  string else GetText(CustomVariablesContext context)if(p == "hg")
         {      return volume * 1.20;
          double else feif(p == context.N("femg");
               return volume * 1.20;
            if(fe > 60) 
                 return "hg"         else if(p == "lg1")
               return volume * 1.20;
              else else if(fep > 58) 
                 return "mg";== "lg2")
               return volume * 0.85;
           else 
               return 0;
      else}
if(fe > 57.5) 
                 return "lg1";
              else if(fe > 56) }

Access Custom Variable values from another Custom Variable

Code Block
// Reading Factors from one variable into a second variable
// Update the ROM Recon Factors below 

public class reconFactorVariable_1
{
       double fe_F  = 1.0; 
       double al_F  =  1.0;
   return "lg2";    
          else if(fe >
50)    public double GetReconValue(CustomVariablesContext context, string code)
    {     return "minw";               else 
         if (code.Equals("fe_F",StringComparison.OrdinalIgnoreCase))
       return "w"fe_F;
       } }  public class Recovery : IDoubleCustomVariable
{
     public double GetNumber(CustomVariablesContext context)
     {
           Parcel parcel = new Parcel(); //Create an instance of the Parcel object
           string p = parcel.GetText(context); //Call the GetText() method, store the result in variable 'p'

           double volume = context.N("XINC")*context.N("YINC")*context.N("ZINC");

           if(p == "hglp" ) 
               return volume * 1.20;
           else if(p == "hg")
               return volume * 1.20;
           else if(p == "mg")
               return volume * 1.20;
           else if(p == "lg1")
               return volume * 1.20;
           else if(p == "lg2")
               return volume * 0.85;
           else 
               return 0;
      }
}
if (code.Equals("al_F",StringComparison.OrdinalIgnoreCase))
       return al_F;
       
       return 1;      
    } 
}


public class fe_R_Variable_2 : IDoubleCustomVariable
{
    public double GetNumber(CustomVariablesContext context)
    {
        var rf = new reconFactorVariable_1();
        double grade = context.N("i_fe");
        grade = grade * rf.GetReconValue(context,"fe_F");
        return grade;
    }
}

public class al_R_Variable_2 : IDoubleCustomVariable
{
    public double GetNumber(CustomVariablesContext context)
    {
        var rf = new reconFactorVariable_1();
        double grade = context.N("i_al");
        grade = grade * rf.GetReconValue(context,"al_F");
        return grade;
    }
}

Variable validation and Error list resolution

Check if the statement is correct by pressing the Compile button at the top left corner of the Script Editor.

...

If you did a mistake, the Compile Error window will pop up, showing row and column of an error with a short description of its type.

...

Read the error message description and correct the lines specified.

If there are no errors during compilation, the custom variables should be listed in the Variables panel.

When the Errors panel is blank, and all relevant fields are mapped, press Generate to build the Reserve Model.

...