MastHEad

 


Syntax of Using k_units Data Type


 

This page reviews several aspects related to syntax approaches for manipulating variables of data type k_units.  If you have not already done so, please review The k_units Data Type help page before reading this page.

This help page focuses on manipulating tabular array data stored in the k_units data type. As you will see demonstrated in the examples presented here, Kornucopia provides you with several flexible syntax options to work with tabular data including referencing columns and/or rows by either indices or names, as well as some other flexible syntax options related to defining and using Units, ColNames, and RowNames Properties of entities holding a k_units data type. The help page starts out very simple and demonstrates various syntax concepts as it builds upon the starting example. The general presentation of the information uses small sections of code (commands) followed by some results. Feel free to copy the code sections into MATLAB to follow-along with live calculations if you like. Sometimes additional statements or highlights are added (outside of MATLAB) to either the commands and/or the displayed results for clarity of explaining a particular point.

Please remember, as noted previously, in Kornucopia the term string refers to the single-quoted MATLAB char-array such as A('density') and NOT the double-quoted MATLAB string such as A("density").  In general, the Kornucopia syntax does not utilize MATLAB's double-quoted string data type, only the single-quoted char-array data type (relative to character strings).

Sub-sections on this page:

Listed below are two other syntax-related help pages located elsewhere in the Kornucopia help system:

Create a tabular array to study

To begin, consider creating a table of material properties for three materials, aluminum, copper, and glass. Through various websites and books, we find properties for elastic modulus (E), Poisson's ratio (nu), linear coefficient of thermal expansion (CTE), and volumetric mass density (dens).

The six commands below do the following:

  1. Set the Active Units Preference. Using this command is optional, but it is a good practice to place such a command at the top of a m-file script so that you can control the units that computed results are returned in. If you do not specify this Units Preference command, then the last such Units Preference command stated within the current MATLAB session will be active. If you never issued the command, then the 'SI_partial' preference will be active because it is the installed default.

  2. The variable matlsData is just a standard MATLAB 3x4 array of type double  holding the material property data as just numbers.  Nothing special here.

  3. The variable matls uses the function k_units to create a tabular array of data type k_units  that holds the numerical values, associated units (associated to the columns), column names, and row names. In the four set of units, the second set, '', is the most common way to enter a unit as dimensionless. You could have also placed a '1' as the unit also.

  4. The display function is the MATLAB command to display a variable to the MATLAB Command Window. Had we not put a semicolon at the end of the matls variable definition, MATLAB would have automatically echoed the variable's contents to the MATLAB Command Window and the display command would not be needed.

  5. The k_summary function returns a nice compact summary of the Properties of the variable matls.

  6. The last command, matls.Props.RowNames, retrieves the Props.RowNames Property from the variable matls and displays it to the Command Window since no semicolon was at the end of the command.

 

k_unitsPreferenceActivate('SI_partial');

matlsData = [

    72, 0.33, 23.5, 2700
    118, 0.33, 16.9, 8960
    69, 0.24, 9.2, 2500

    ];

matls = k_units(matlsData, {'GPa', '', '1e-6*(m/m)/degC', 'kg/m^3'}, ...
   
'ColNames', {'E', 'nu', 'CTE', 'dens'}, ...
    
'RowNames', {'Aluminum'; 'Copper'; 'Glass'});

display(matls)
k_summary(matls)
matls.Props.RowNames

 

In the MATLAB Command Window you see the following returned:

Using alternative string-list syntax

From the summary and additional output of matls.Props.RowNames you can clearly see that the Units and ColNames Properties are stored as 1x4 cell-strings and that the RowNames Property is a 3x1 cell-string. These three Properties always hold their contents as cell-strings, unless they are empty, in which case their contents are stored as {}. The syntax used in the above example to define the contents of these Properties utilized traditional MATLAB syntax, namely cell-strings. Kornucopia offers another option to specify these three Properties, it is termed a string-list. A string-list is simply a char string that uses either a comma or semicolon to separate internal sub-strings. Because Kornucopia does not allow either comma or semicolon characters in Units, ColNames, and RowNames, these two characters can be used as delimiters which enables a handy syntax short-cut.

The code shown below defines the matls_a variable using string-lists (highlighted in yellow). This will be interpreted the same as the code shown above that used cell-strings to define the Units, ColNames, and RowNames Properties of matls. It is further noted that since the RowNames Property is a vector, Kornucopia allows you to use either a comma or semicolon in its string-list definition syntax; Kornucopia will ultimately place it into the Property as a 3x1 cell-string.

 

matls_a = k_units(matlsData, 'GPa, '', 1e-6*(m/m)/degC, kg/m^3', ...
   
'ColNames', 'E, nu, CTE, dens', ...
    
'RowNames', 'Aluminum, Copper, Glass')

k_summary(matls_a)

disp(' ')
disp('Check if matls is same as matls_a')
isequal(matls, matls_a)

 

Demonstrating other common techniques of creating tabular variable with k_units data type

Another common approach to create a tabular variable is to specify only the numeric data and units first, and then to add additional meta-information Properties later using dot syntax. The meta-info Properties, all stored under the Property name Props, include the sub-Properties of Description, ColNames, ColComments, ColUserData, RowNames, RowComments, RowUserData, UserData, and DimensionNames. The use of any of these meta-info Properties is optional, but typically quite helpful in making your variables more capable to hold various forms of information you deem important. It is noted that the use of the Units Property (a primary Property) is also optional, although highly recommended. If you do not specify any Units Property, then all columns are assumed to have dimensionless units.

The code and resulting output below demonstrates the common syntax approach of defining just the numeric data and units first, and then adding some meta-info Props with separate commands next. It is noted that string-list syntax is being demonstrated for adding the content for the ColNames and RowNames Properties. We could have equivalently used cell-strings, the choice is yours.  

 

% Define just the numbers and units
matls_b = k_units(matlsData, 'GPa, '', 1e-6*(m/m)/degC, kg/m^3');
display(matls_b)

% Add an overall description
matls_b.Props.Description = 'Some materials to study';
display(matls_b)

% Add column names
matls_b.Props.ColNames = 'E, nu, CTE, dens';
display(matls_b)

% Add row names
matls_b.Props.RowNames = 'Aluminum, Copper, Glass';
display(matls_b)

 

In the output that is shown below, the orange boxes separate the different stages of the output and the yellow highlights show what is new at each additional stage.

 

The option of using string-list syntax is only allowed for the three Properties of Units, ColNames and RowNames. The other Properties do not support this optional syntax because a comma or semicolon are valid parts of inputs within their contents. To demonstrate this, we will add a some content to a few other Properties of matls_a. First we add a note into Props.UserData regarding the source of the data.

 

% Place a 2x1 cell array in UserData to describe the source of the values.
matls_a.Props.UserData = {...
   
'Unless noted otherwise in a column comment, source of values:'; ...
    
'"Roarks Formulas for Stress and Strain", 7th Ed, Table 2.1'};

display(matls_a)
k_summary(matls_a)
matls_a.Props.UserData

 

Items of note:

  1. The "**Note" that now appears under the tabular display is stating that you are NOT looking at all of the variable's contents. This is because we have placed something into the Props.UserData Property. Since this particular Property can hold essentially anything you place into it (a cell array, a struct, a string or scalar, another entity of k_units data type, or literally just about anything!), there is no universal way to show its contents in the tabular display. So, this note is generated by Kornucopia to at least make you aware of its existence.

  2. In the  k_summary output, a brief note is also used to state that something is in the Props.UserData Property.

  3. Doing a direct evaluation of matls_a.Props.UserData is the best way to see the contents of this particular Property.

Below we show several valid syntax approaches to add a specific column comment into the fourth column holding density values. To show the equivalency of the approaches, we make several copies of the original matls_a variable and then add the comment to the various copies using different syntax, and then compare the results using the MATLAB isequal function.

 

matls2 = matls_a; matls3 = matls_a; matls4 = matls_a; matls5 = matls_a;

% Add a comment to the 4th column.
txt = 'Source: Various web cites, not Roarks';

% The four approaches below are equivalent
matls2.Props.ColComments('dens') = txt;
matls3.Props.ColComments('dens') = {txt};
matls4.Props.ColComments{1,4} = txt;
matls5.Props.ColComments(1,4) = {txt};

isequal(matls2, matls3), isequal(matls2, matls4), isequal(matls2, matls5)

% Since they are all the same, just display matls2
display(matls2)
k_summary(matls2)
k_varViewer(matls2)

 

The following is returned.

Items of note:

  1. The syntax options of using either a column name or numeric indices produced the same results and provides you flexibility. If you are doing something in a for-loop, you might use numeric indices. If you are just making one or a few entries, you might use column names or indices - you have the choice.

  2. The **Note that still appears was triggered to display by the fact that both the Props.UserData and Props.ColComments are non-empty. In fact, if these or Props.RowComments are non-empty, then this notice appears under the tabular display stating you are not looking at all of the variable's contents.

  3. The display output from the function k_summary nicely shows the added column comment as does the more interactive Kornucopia variable viewer (k_varViewer).

Depicted below is one version of syntax that is not allowed and causes an error. Comparing the invalid syntax shown below and the valid syntax shown above, you see the difference is that the referencing to the column name needs to be done at the end of the command (after the dot components) and not near the beginning of the command (before the dot components).

 

matls6 = matls_a;

% This syntax is NOT allowed and gives error
matls6('dens').Props.ColComments = txt;

 

Syntax for deleting Property content

Sometimes you might desire to delete contents of a Property. This is done as shown below where we delete the entire UserData Property contents and the ColComments content for the density column.

 

matls2.Props.UserData = [];
matls2.Props.ColComments{'dens'} = [];

display(matls2)
k_summary(matls2)

 

Items of note:

  1. The entire UserData Property contents was deleted by the first command, while only the content of the 'dens' column's ColComments was deleted by the second command. We could have alternatively used numeric index syntax to reference the 'dens' column.

  2. Since we only deleted a specific columns' ColComments content, the Props.ColComments  content now shows a cell array of four empty strings as apposed to just {}. Had we deleted all the ColComments by using syntax like  matls2.Props.ColComments = [];, then the Props.ColComments content would show as just {}. With either approach, Kornucopia now does not include the "**Note" about content not being displayed because all meaningful content is now displayed.

Syntax to reference into Kornucopia's tabular arrays

The next set of syntax demonstrations show various ways to reference single values as well as columns or rows of values from a variable or entity of data type k_units.

The commands below show several equivalent ways to access a single value from a tabular array.

 

% All the following are equivalent syntax
v = matls2(2,1)
v = matls2(2, 'E')
v = matls2('Copper', 'E')
v = matls2('Copper', 1)

 

The outputs above show like "mini" tables because the starting variable, matls2, has column and row names.   

Note that all the syntax options shown above explicitly specify both the row and column location, either by index number or name, or some combination of these. One syntax that is not allowed with k_units data type is to specify just a singular numeric index, what MATLAB calls linear indexing. The only type of single indexing allowed with k_units  data type is the use of one or more column names to reference one or more columns. This is demonstrated in the syntax below. Note that in the first three blocks of code below, the various commands are equivalent and thus the results are identical.  The last set of commands shown below demonstrate referencing multiple rows, including repeating a row in the reference.

 

% Specify a single column (all of the following are equivalent)
D = matls2('nu');
Da = matls2(:, 'nu');
Db = matls2(:, 2);

display(['The isequal checks result: ', num2str(isequal(D, Da) & isequal(D, Db))])
display(D)

% Specify two columns
D = matls2('E, nu');
Da = matls2(:, 'E, nu');
Db = matls2(:, 1:2);

display(['The isequal checks result: ', num2str(isequal(D, Da) & isequal(D, Db))])
display(D)

% Specify three columns, but in different order.
D = matls2('dens, E, CTE');
Da = matls2(:, 'dens, E, CTE');
Db = matls2(:, [4, 1, 3]);

display(['The isequal checks result: ', num2str(isequal(D, Da) & isequal(D, Db))])
display(D)

% Specify a few rows, including repeating one twice
manyRows = matls2('Glass, Aluminum, Copper, Copper', :);
display(manyRows)

 

Items of note:

  1. In each code section above that is referencing entire columns, the first version of the command uses a single indexing argument that only references the column name or names of interest by string or string-list. No entry for a row specification is provided. This is the only type of single indexing allowed in a k_units data type. This is based on the fact that k_units tabular arrays are column-oriented and it is very common to specify commands to reference entire columns. If you attempt to specify a single numeric index in an entity of data type k_units, Kornucopia will issue a descriptive error that guides you to the proper syntax.

  2. The third section of code also shows how we can easily rearrange the columns of the array.

  3. The last section shows how to re-arrange rows of the array. This example also shows if you repeat a row name that Kornucopia will automatically augment the repeated name with a number to ensure that all row names are unique. The same behavior would occur with column names as well.

Flexible matching for row and column names

By default, Kornucopia uses strict string matching behavior when assessing references to column and row names in data types of k_units. Strict means that the check is case sensitive and must be an exact match. If a strict match fails, Kornucopia automatically determines if a more forgiving (flexible) check would succeed. The flexible match technology using the following algorithm when a strict match fails:

  1. Starting with the original strings being compared, all spaces from both strings are removed. The string comparison is re-tried.

  2. If the above does not result in a match, then starting with the original strings, all underscores, _, are removed, and the string comparison is re-tried.

  3. If the above does not result in a match, then starting with the original strings, the comparison is re-tried ignoring character case.

  4. If the above does not result in a match, then starting with the original strings, all spaces and underscores are removed and the comparison is re-tried ignoring character case.

If no match occurs at this point, then the comparison is considered not a match and the appropriate response occurs. If at any time through the flexible algorithm a match is found, then the algorithm terminates and the internal code deems the original comparison a match.

From our previous calculations consider the following reference to the column dens, where we accidentally reference it using the string 'Dens' (note the capital D).

density = manyRows('Dens')

 

Kornucopia's flexible matching algorithm creates a popup that lets you interactively decide what to do.

From the popup you decide if you want to use a strict match or a flexible match. For each choice, you have 3 options:

  1. The choice is applied on for that specific instance.

  2. The choice is applied for the remainder of your Kornucopia session. If you pick this option, Kornucopia internally sets, for you, the following command k_set('flexibleColandRowNameMatching', 'on'). This will remain in effect until you either exit your MATLAB session, change the setting via another use of k_set with this ADV option, or you use the k_clearKornMem function.

  3. The choice is applied for all Kornucopia sessions.  If you pick this option, Kornucopia internally sets, for you, the following command k_set('flexibleColandRowNameMatching', 'on') for your current session and it also places this command in your Kornucopia afterKornucopiaStart.m file so that for future uses of Kornucopia will have the command issued at the start of your session.

For our example demonstration, we chose to allow the flexible match for this instance.  Depicted below is the output for the new variable density.

Note that Kornucopia effectively corrected our typo of 'Dens', and substituted in the correct column name, 'dens', which then allowed the intended column to be assigned to the new variable density.

Syntax for modifying values in Kornucopia's tabular arrays

Below we show several ways to modify values in a variable of data type k_units.  We first copy the matls2 variable and then work with the new copy.  To help make it easy to identify the values we replace, all the new values will be negative (yes, the new values are nonsensical, we are simply showing some syntax techniques). Detailed explanations of what the various commands are doing is presented after the output presentation.

 

matlsRev = matls2;
matlsRev.Props.Description = 'BEFORE modifications';
display(matlsRev)

% 1) Define a few units variables
k_unitsVariables('lbf, in, snail')

% 2) Change modulus values of Aluminum and Glass
matlsRev('Aluminum, Glass', 'E') = [-1.5; -1.4] * 1e7 * lbf/in^2;

% 3) Change a few CTE values
matlsRev(1,3) = '-30*1e-6*(mm/mm)/degC';
matlsRev.Data(3, 'CTE') = -5;

% 4) Change the entire column of density
newDens = -[100, 200, 300]' * snail/in^3;
newDens = newDens.convert('snail/in^3');
newDens.Props.ColNames = 'crazy Dens';
matlsRev('dens') = newDens;

% 5) Change Poisson ratio values for Glass and Aluminum
matlsRev('Aluminum', 'nu') = k_units(-22, '');
matlsRev('Glass', 'nu') = -0.15;

% 6) Change the entire row of Copper properties
matlsRev('Copper', :) = k_units(-[1,2,3,4], 'MPa, '', 1e-6/degC, kg/m^3');

matlsRev.Props.Description = 'AFTER modifications';
display(matlsRev)

 

Detailed explanations of commands and results from above code are described below. Note the numbered list below references the numbered code sections above.

  1. The k_unitsVariables function is a handy way to quickly make several MATLAB variables appear in the current workspace that represent specific units. The user can then use these variables with natural math statement in calculations as needed.

  2. Several syntax items to note:

    1. On the left hand side of the =, the command is referencing the rows named Aluminum and Glass via a string list, and the column named E as a string.

    2. On the right hand side of the =, a 2x1 array is multiplied by 1e7 (just a number), then multiplied by the variable lbf, and then divided by the variable in raised to the second power. The variables lbf and in came from the k_unitsVariables function use of section 1. Notice that the units of the values specified are lbf/in^2, yet the column these values are being placed into has units of GPa. Kornucopia automatically handles this, checking and then converting the units and associated values of the 2x1 array as it is placed into the  matlsRev variable.  Note that in code section 4, an entire column is replaced, for which the units of the new column replace original column's units.

  3. Two commands are utilized.

    1. The first command in this section demonstrates using row and column indices to reference what is being modified. On the right hand side, a string is supplied to define the new value and unit. Kornucopia automatically knows how to convert this single string entry into a k_units data type because strings are a Kornucopia-compatible data type.  As stated in explanation 2 above, Kornucopia handles all the units issues too.

    2. The second command demonstrates the syntax to directly replace a numeric value within the Data Property. In this case, the value on right hand side is just a number, no units are supplied. The number is then placed directly into the matlsRev.Data Property "as-is".

  4. This set of commands creates a completely new density vector which then replaces the previous density column.

    1. The first command creates the data values and units of the new density column vector. The variable, newDens, is not created using the k_units constructor function, but is instead created by simple MATLAB math commands; a column vector is multiplied by a variable named snail and then divided by a variable named in which is cubed.  The result of this math is a column vector of data type k_units because we did math with one or more entities of data type k_units (the snail and in variables).  The actual output units of this calculation are not snail/in^3 as you might expect, but are instead kg/m^3. This is because our Active Units Preference is 'SI_partial' .  This was set at the beginning of this example near the top of this help page. You can always use the k_unitsPreferenceActive command to see what is currently active, and you can change the Active Units Preference at any time via the k_unitsPreferenceActivate function.  This demonstrates a few important concepts:

      1. Whenever you do math, the units of the result are always converted to the units specified in the Active Units Preference.  Link to learn more about Units Preferences.

      2. Had we used the k_units constructor function instead of math, such as k_units(-[100, 200, 300]', 'snail/in^3'), then the result would be in units of snail/in^3 and no conversion to the Active Units Preference would occur. Regardless of the approach, Kornucopia will handle all the units compatibility and conversion issues for you.  Either approach works equally well.

    2. The second command forces a units conversion of the newDens variable to the units of snail/in^3.

    3. This command defines the column name 'crazy Dens' to be assigned to the variable newDens.

    4. This replaces the entire fourth column, 'dens', with the newDens vector. Because a column vector with a number of rows equal to the number of rows of matlsRev is being assigned, Kornucopia views this as a complete column replacement as opposed to a partial array replacement (which is what was demonstrated in sections 2 and 3).  When there is a complete column replacement  (or complete replacement of multiple columns), Kornucopia replaces, in its entirety, the following Properties associated to the columns being replaced: Data, Units, Props.ColNames, Props.ColUserData, and Props.ColComments. One exception to this rule is that if the new column (or columns) coming in do not have column names, then the original column names of the original variable are kept. As a result of the full column replacement, the output of matlsRev shown AFTER modification has the new column name and units.  

  5. This set of commands shows two approaches for changing values of a dimensionless quantity, Poisson's ratio (column name nu).

    1. The first command, changing the nu value for Aluminum, used the formal approach of supplying an entity of data type k_units, with dimensionless units, via the k_units constructor function.

    2. The second command, changing the Glass nu value, simply supplied a numeric value of -0.15.  Similar to the use of a string for explanation 3i above, numeric values are a Kornucopia-compatible data type. Thus, Kornucopia internally converts the numeric value of -0.15 into a k_units data type that has dimensionless units, and then places the result into the matlsRev variable.  To be clear, you can "get away" with using just a number here because nu has dimensionless units associated with it. As shown for the CTE replacements previously, to use just numbers during replacements within entities that have units which are not dimensionless, you would need to use the .Data Property syntax.

  6. This command changes the entire Copper row in the variable matlsRev.  The new row is specified via a k_units constructor command. Other approaches to supply a row could be used of course. Since the number of columns on the right hand side of the = is the same as the number of columns in matlsRev, Kornucopia views this as a complete row replacement as opposed to a partial array replacement (which is what was demonstrated in sections 2 and 3). When there is a complete row replacement  (or complete replacement of multiple rows), Kornucopia replaces, in its entirety, the following Properties associated to the rows being replaced: Data, Props.RowNames, Props.RowUserData, and Props.RowComments.  One exception to this rule is that if the new row (or rows) coming in do not have row names, then the original row names of the original variable are kept. As for the units, the Units Property is associated to the columns, and so the values of the Data being replaced are converted appropriately for the Units of the columns already existing in matlsRev. For the specific example demonstrated, note the following relative to the final output of matlsRev AFTER modification:

    1. The row name 'Copper' has remained even though the new replacement had no row-name specified. Had the new row had a name, it would have replaced the copper name.  

    2. The new modulus value of -1*MPa was supplied, but it was converted to the matlsRev units of GPa, and thus the value appears in the final output as -0.001000.

    3. Nothing exciting happens to the second and third column values (nu, and CTE) for the replacement row.

    4. The newly supplied density value of -4*kg/m^3 is automatically converted to the units of snail/in^3 because at the time this command is issued, the matlsRev fourth column is already in these units.

Displayed below are a couple of examples of the types error messages that will occur if you attempt to modify a value in a k_units data type using incompatible units.  The demonstrations were directly evaluated in the MATLAB command window (the >> is just the command cursor). As you see, Kornucopia protects you from making units-related errors and also gives possible suggestions to help address the issue.

 

Kornucopia rules for LHS assignment from RHS quantities

Kornucopia has rules on how various Properties of entities of data type k_units  are mapped from the right hand side (RHS) of a command or equation to the left hand side (LHS) of the equals sign in the command. The rules and information below include discussion and examples of when to use top-level smooth brace indexing, and when top-level LHS curly brace indexing is beneficial.

Kornucopia rules for LHS assignment from RHS quantities

  1. If the LHS entity does not exist, then the complete result from the RHS is used to create the new LHS entity.

  2. If the LHS entity already exists, then the mapping of RHS results into the existing LHS result depends on whether the calculation yields partial or full column replacements in the existing LHS entity.

    1. Partial column replacement - If the command or calculation being performed is on only a portion of the rows of the existing LHS column or columns, then Kornucopia views this as a partial column replacement. In this case, only the particular Data Property values in the LHS will be replaced by the new RHS values.  In doing this, Kornucopia automatically handles all the units-related issues. It checks for units compatibility between the RHS Units and the LHS Units. If compatibility is satisfied, then Kornucopia automatically converts the RHS results to be consistent with the LHS Units and then places the RHS converted data into the LHS Data Property.  No other Props from the RHS are transferred over to the LHS entity.  These rules and behavior apply whether you use LHS top-level smooth braces or curly braces.

    2. Full column replacement -  If the command or calculation being performed is such that  ALL of the rows for one or more existing LHS columns will be replaced, then Kornucopia views this as a full column replacement. The rules of how Kornucopia handles full column replacement depend on if LHS top-level smooth or curly indexing is utilized.

      1. Smooth brace indexing - Kornucopia views the command as a request to literally replace the entire column or columns of the LHS with the RHS result. This means that the Data, Units, Props.ColNames, Props.ColComments, and Props.ColUserData from the RHS result will be placed into the LHS entity. This is typically the indexing syntax that most users utilize.

      2. Curly brace indexing - Kornucopia views the commands as a request to only replace the Data portion of the column or columns of the LHS with the converted data of the RHS. This means that the Units,  Props.ColNames, Props.ColComments, and Props.ColUserData from the LHS are to be left "as-is". Just like in the partial column replacement described above, Kornucopia automatically handles all the units-related issues of placing the RHS results into the LHS Data Property.

      3. No indexing specified - In this case, Kornucopia interprets the commands as a request to completely replace the LHS entity with the RHS result.

For completeness, it is noted that Kornucopia does NOT allow top-level RHS curly brace indexing.  If you attempt to use top-level RHS curly brace indexing, Kornucopia will provide an appropriate error message. To be clear, top-level smooth brace indexing can be used on either the LHS or RHS. Top-level curly brace indexing can only be used on the LHS.

The block of code below and the results that follow it demonstrate Kornucopia's rules for LHS assignment from RHS quantities, as well as showing the use of both smooth and curly LHS indexing.

 

home

% Copy matls variable into new variable "A"
A1a = matls; A1b = matls;
A2a = matls; A2b = matls;

% Create a new variable "B" that has "crazy" values for E and density
newE = k_units(-[10, 20, 30]', 'ksi', 'ColNames', 'crazy E');
B = [newE, newDens];

disp('Displaying "starting" arrays')
display(A1a) % Note: A1b, A2a, A2b are identical to A1a
display(B)

% Full column replacements using smooth and then curly LHS indexing
disp(' '), disp('Full column replacement with LHS smooth brace indexing')
A1a('E, dens') = B
disp(' '), disp('Full column replacement with LHS curly brace indexing')
A2a{'E, dens'} = B

% Partial column replacements, only rows 1 and 2
disp(' '), disp('Partial column replacement with LHS smooth brace indexing')
A1b(1:2, 'E, dens') = B(1:2, :)
disp(' '), disp('Partial column replacement with LHS curly brace indexing')
A2b{1:2, 'E, dens'} = B(1:2, :)

 

In the results displayed below, the orange box is highlighting the starting arrays. In the code above, the basic calculation being performed is to put array B into the first and last column locations of array A1a (and its other copies of A1b, A2a, and A2b). These results are grouped with the green boxes (for full column replacement examples) and blue boxes (for partial column replacement examples).

The block of code below demonstrates how properly using LHS curly braces can easily keep the original units of a variable when math is performed. In this example, a 3-column variable of data type k_units is loaded from the Kornucopia example library. The units of the data are msec, kG, and mm, respectively. The intent of the calculation is to "rezero" the data by subtracting the 1st row from all the data. This subtraction action is doing math and so the Kornucopia rules of math are applied. In particular, the units of the output from any math operation are converted to the current k_unitsPreferenceActive value (which demonstrated below is 'SI_partial'). This means that with normal Kornucopia syntax (denoted below with the formula beginning with sensors_a), our re-zeroing action will cause the new result to have its units converted to the current Preference which is different from the incoming units. This may be exactly what you want, or maybe it is not. If you want to keep the original units, you can either follow the math calculation with an additional line using the .convert() Method, or you can simply use LHS curly braces. Based on the rules of how Kornucopia uses LHS curly braces (described before the previous example), the math calculation will be done but only the .Data values will be updated (with appropriate units conversions done internally automatically). All the previous Units and other .Props properties will remain unchanged.

 

home

% Load a Kornucopia example mat-file containing a variable "sensors"
% The variable has 3 columns of data
k_examplesOpen('ballPlateImpact_PRaccelAndLaser.mat', ...
   'loadMat', 'on');

disp('Sensors variable as loaded')
sensors

% display the current active Units Preference
disp(' ')
k_unitsPreferenceActive

% Rezero the data array by subtracting the 1st row from all the data
% Use two approaches, one as usual, and one with LHS curly braces

sensors_a = sensors - sensors(1,:) % This is a typical approach

% Use LHS curly braces to easily keepy the data's units as they
% originally were prior to the Math being done below.

sensors_b = sensors; % Initialize sensors_b as a k_units array.
sensors_b{:,:} = sensors_b - sensors_b(1,:) % curly brace approach.

% One additional example, just rezeros 2 of the 3 columns, again
% using LHS curly braces to keep original units.

sensors_c = sensors; % Initialize sensors_c as a k_units array.
colsToChange = 'Time, Keyence Laser';
sensors_c{colsToChange} = sensors_c(colsToChange) - sensors_c(1, colsToChange)

 

Below is the result when usual coding syntax is utilized. Because Math was done, the units were converted to the current k_unitsPreferenceActive value.

Depicted below are the results that occur when appropriate LHS curly brace syntax is utilized; the units of the variable are kept as they originally were.

Important Notes:

The result depicted below is for the case of using LHS curly braces to only process a few columns, instead of all the columns.

Performing math that references columns

We now return back to the original matls2 variable and perform a simple thermal stress analysis. The calculations below combine a variety of syntax techniques shown previously.

 

k_unitsVariables('degC')

deltaT = -50*degC;

% Free growth thermal shrinkage
strain = deltaT * matls2('CTE');
strain.Props.ColNames = 'Free therm strain';
strain.Props.ColComments = {'This is strain due to unconstrained thermal growth'};
strain = strain.convert('%');

% Thermal stress for constrained strain
stress = -strain .* matls2('E');
stress.Props.ColNames = 'Constrained therm stress';
stress.Props.ColComments = {'Stress due to constrained strain & temperature change'};
stress = stress.convert('MPa');

display(strain), display(stress)

% Append the strain and stress onto the matls2
matls2 = [matls2, strain, stress];
display(matls2)

k_varViewer(matls2)

 

Syntax techniques for referencing a range of columns using column names

The code below loads a data set from the Kornucopia Examples Library that contains a variable named raw that contains transient results from a rotating cam simulation. We use this example to demonstrate a few additional syntax options for working with tabular arrays of data. The lines of commands below first clean-up the MATLAB workspace by clearing all the previous variables and then the example file is loaded and displayed, followed by a trimming action using the function k_trimIndices whose output is applied to the variable raw.

 

clear variables
home

% Get a specific data example from Kornucopia Examples Library
k_examplesOpen('quasi-static_cam.mat', 'loadMat', 'on')
display(raw)

% Trim the variable raw, keeping from 3*msec to end of data
idx = k_trimIndices(raw('Time'), '3*msec', []);
raw = raw(idx,:);
disp('After trimming, raw is shown below:')
display(raw)

 

Items to note:

  1. The command idx = k_trimIndices(raw('Time'), '3*msec',  []);  is interpreted as follows:

    1. The first argument was fed the 'Time' column from the variable raw.

    2. The second argument is looking for the starting trimming value to be supplied as a k_units data type or Kornucopia-compatible data type.  The value supplied, the string '3*msec', was a Kornucopia-compatible data type and thus it was automatically internally converted to a k _units data type representing 3*msec.  

    3. The third argument holds the ending trimming value. Supplying an empty array, [], tells the function to trim to the end of the data vector.

    4. The function returned to the variable idx, a logical array of the same length as the original 'Time' column from the variable raw. All rows of the 'Time' column of raw that are within the trimming values are true (denoted by the logical 1) in the idx variable, while all other rows are false  (denoted by the logical 0).

  2. The command raw = raw(idx,:);  is interpreted as follows:

    1. The right hand side is using standard MATLAB logical array indexing where the idx variable is applied to the row indices of raw and the : is interpreted by MATLAB to be all columns of raw.  The result is placed back into the variable raw, ultimately trimming the tabular array to now start at Time = 3*msec.

Syntax for specifying a range of column or row names

Kornucopia offers a unique index referencing technique that allows you to easily specify a range of column names (or row names) by using a string syntax based on one of the following forms:

The commands shown below demonstrate the use of this kind of technique as well as more traditional numerical indexing techniques.

 

% 1) Get energies in columns ALLWK through ALLSE
A = raw(
'ALLWK : ALLSE');
Aa = raw(:,
'ALLWK : ALLSE');
Ab = raw(:, 5:8);

display([
'The isequal checks result: ', num2str(isequal(A, Aa) & isequal(A, Ab))])
display(A)

% 2) Get the last row of energies in columns ALLWK through ALLSE
B = raw(end,
'ALLWK : ALLSE');
Bb = raw(end, 5:8);

display([
'The isequal checks result: ', num2str(isequal(B, Bb))])
display(B)

% 3) Get all the energies (Columns ALLAE through to the end)
C = raw(
'ALLAE :');
Cb = raw(:, 4:end);

display([
'The isequal checks result: ', num2str(isequal(C, Cb))])
display(C)

% 4) Get the Time through UR3 columns
D = raw(
'Time : UR3');
Da = raw(
': UR3');
Da2 = raw(:,
': UR3');
Db = raw(:, 1:3);

display([
'The isequal checks result: ', ...
   num2str(isequal(D, Da) & isequal(D, Da2) & isequal(D, Db))])
display(D)

 

Guidance on using k_units data type in "for & while loops" and initializing variables

This section provides some guidance on how to properly use entities of data type k_units in for-loops and while-loops, including the important topic of initializing variables.

For the demonstration, an example file from the Kornucopia Examples Library containing a 6x1 cell array and data sets is loaded. As depicted in the displayed output below, each element of the cell array holds a tabular array of data type k_units.

 

clear variables
close all
home

% Get a specific data example from Kornucopia Examples Library
k_examplesOpen('multiCurves.mat', 'loadMat', 'on')

nSets = numel(multiCurves)

display(multiCurves)

disp(' ')
disp('Displaying multiCurves{1}')
disp(multiCurves{1})

 

The goal of the analysis is to loop through the six data sets and compute the max from the time and load columns, placing the result into a single non-cell array.  Below we show two separate approaches to achieve the desired result:

  1. The traditional MATLAB approach using just numbers, totally avoiding the use of the k_units data type.

  2. Using arrays with k_units data type.

 

1) Using just numbers

Below we initialize a variable A_maxes, using the MATLAB function NaN to have 6 rows and 2 columns. We use the function NaN instead of zeros so that we can later detect if we accidentally did not fill something in (it will have NaN values in it). The variable A_maxes, is of data type double because nan returns this data type.  A_maxes will ultimately hold the max values of Time and Load computed from each data set stored in multiCurves.  Since A_maxes is of data type double, the calculation inside the for-loop uses the .Data Property to extract just the numbers from mutliCurves. Thus, the calculations are done solely on data type double quantities.

 

% Initialize variable to hold max vals for cols 1 & 2 (Time & Load)
A_maxes = NaN(nSets, 2);

% Loop to compute max values
for z = 1 : nSets
   justNumbers = multiCurves{z,1}.Data;
   A_maxes(z, :) = max(justNumbers(:, 1:2));
end

display(A_maxes)

 

As shown above, the returned values are simply held in a type double array with no description, no column names, and no units; just the numbers (a rather dangerous approach ... just ask NASA).

 

2) Using arrays with k_units data type.

Three separate approaches using k_units data type are shown below. The first one is an incorrect approach that yields an error (a common mistake that new users of Kornucopia might make). The second and third approaches are valid and proper.

First, the incorrect approach. In this approach, the user initializes A_maxes to zeros (classic MATLAB user thinking) similar to what was done on the "just the numbers" approach above (although here the more classic zeros approach is used instead of the  NaN approach, although either will cause an error in the block below). Thus A_maxes is initialized as a data type double and NOT as a data type k_units. Then in the for-loop, when they try to replace values in the A_maxes variable with k_units data type values from tmp (which comes from multiCurves), MATLAB throws an error as is shown after the code.

 

% Doing it the WRONG way - This will lead to an error!

% This improperly initializes A_maxes to type double, NOT k_units
A_maxes = zeros(nSets, 2);

% Loop to compute max values
for z = 1 : nSets
   tmp = multiCurves{z,1};
   A_maxes(z, :) = max(tmp('Time, Load'));
end

 

Below is the error thrown by MATLAB. Please note that this error might have different wording depending on the version of MATLAB you are running.

The variable A_maxes can only hold numbers because it was initialized that way, as a double. It is not possible to place a k_units data type into a double. You can only place the .Data Property content into a double (as was done with the just-numbers approach earlier).

Presented below is a correct approach in which A_maxes is initialized as a k_units data type. As shown we still use the MATLAB NaN function to create the numeric data portion of the initial tabular array of type k_units, but we also have to add compatible units too. In the example shown, we have set the load units to be N, which is different from the actual units in the multiCurves load columns, which is gf.  This is fine because Kornucopia will handle all the needed units compatibility checks and conversions.

 

% Doing it a CORRECT way

% Properly initializing as a k_units data type
A_maxes = k_units(NaN(nSets, 2), 's, N');

% Loop to compute max values
for z = 1 : nSets
   tmp = multiCurves{z,1};
   A_maxes(z, :) = max(tmp('Time, Load'));
end

display(A_maxes)

% Adding some ColNames
A_maxes.Props.ColNames = 'maxTime, maxLoad';
display(A_maxes)

 

The resulting A_maxes variable shown above is now very clear and we know what the units are, as well as the column names!

One final flavor of this example is presented below. It is a slight variation from the previous approach. In the set of commands shown below, the units used in initializing the A_maxes variable are taken directly from the first data set of the multiCurves variable. This is in comparison to our manual entry of the units during initialization in the demonstration above. Taking the units directly from the incoming data can, in some case be viewed as more robust because if a different data set is analyzed and it has, for example no units, the code will still work properly (excluding the part at the very end where we do a units conversion to a specific set of units because the user does not like gf units and prefers N as units).  This could have been made even more generic by converting to a Units Preference instead, such as A_maxes = A_maxes.convert('mm_N_s');.  Please note that the initialization is done in two different, but equivalent ways in the code that is demonstrated below. This was done to help you understand clearly all the items going into the approach.

 

% Alternate initialization approach

% Initialization broken into pieces
vals = NaN(nSets, 2);
units = multiCurves{1}('Time, Load').Units;

A_maxes = k_units(vals, units);
A_maxes.Props.ColNames = 'maxTime, maxLoad';

% Alternate "All in one" command
A_maxes = k_units(NaN(nSets, 2), multiCurves{1}('Time, Load').Units, ...
   
'ColNames', 'maxTime, maxLoad');

display(vals), display(units), display(A_maxes)


% Loop to compute max values
for z = 1 : nSets
   tmp = multiCurves{z,1};
   A_maxes(z, :) = max(tmp('Time, Load'));
end

display(A_maxes)

A_maxes = A_maxes.convert('s, N');

disp(' ')
disp('After converting to N for Load')
display(A_maxes)

 

Depicted below are the variables after initialization, but before the for-loop has run.

Below is A_maxes after the for-loop has run and then again after the load is converted to N from gf.

To learn more about initializing variables to be of data type k_units, click here.

Holding data column-wise vs row-wise

As you have no doubt observed in the various examples demonstrated, Kornucopia's k_units data type holds data column-wise, meaning in particular that Units are associated to the Data by columns. Holding and working with tabular data in a column-wise manner is a very natural approach, especially when you have data with many rows (hundreds or much more).  

In contrast, several traditional MATLAB work-flows create data arrays that are row-wise oriented.

The following produces a 1x100 array of data type double with values ranging from 1 to 100 in default increments of 1.

X = 1:100  

The following first produces a 1x6 array of data type double with 6 data values ranging linearly from 1 to 25. This array is then squared and the two results are combined into a single variable that is a 2x6 array of data type double.

 

t = linspace(1, 25, 6)
y = t .^ 2
ty = [t;y]

 

Attempting to use such row-wise calculations in Kornucopia will typically lead to highly inefficient calculations or units-related errors. The commands, results, and additional comments shown below demonstrate this.

 

k_unitsVariables('s')

t = linspace(1, 25, 6)*s
y = t .^ 2

disp(' ')
disp('Trying to stack t and y will produce an units-related error')
disp(' ')
ty = [t;y]

 

Items to note:

  1. The use of k_unitsVariables('s') placed the variable s into the MATLAB workspace so we could easily apply units to the vector t defined from the linspace function.

  2. Both row vectors t and y have units in each column. At this point there is nothing technically wrong with the calculations.

  3. When we attempt to stack t on top of y via, ty = [t;y], this creates a problem.  Kornucopia associates Units by column only, so when it tries to place the y variable under t in an array, the software goes column by column and attempts to convert the Units of y into the Units used by t. This is obviously not possible and thus an error is thrown.  

  4. An important point about computing efficiency: Because t (and thus y) are row-oriented variables, any time any math or similar calculation is done with such variables, Kornucopia will, in general, need to do Units-related operations on the columns of these variables. Such Units-related operations are computationally expensive.  If your data has a very large number of columns, say thousands, then you will notice a significant slow down in computational speed. The simple example with six columns has negligible impact of speed.

Presented below is the recommended Kornucopia way to perform such a calculation.

 

t = k_vectorByPoints('1*s', '25*s', 6);
y = t .^ 2;
ty = [t, y]

 

Items to note:

  1. Instead of using the linspace function to generate a vector a values of time, we use the k_vectorByPoints function. This function can be utilized a few different ways (using Kornucopia-compatible data types was demonstrated in this demonstration). The function returns a column-oriented vector of 6 values ranging from 1*s to 25*s.

  2. The variable y is a column-oriented vector since t was this way.

  3. Combining t and y into a common array is done with horizontal concatenation.

  4. Using the column-oriented variables results in fewer columns of units and thus a more efficient calculation since the Units Engine is called fewer times.  It is noted that in such a small data array, you would not notice the speed difference. However with arrays that have thousands of data values (or much more), the speed difference between column-oriented (preferred) and row-oriented is tremendous.

  5. If you desire to use commands such as linspace or the MATLAB colon syntax like X = 1:100, you should consider transposing their results to generate column-oriented vectors before using them with Kornucopia variables and functions.

This ends the help page on Syntax of Using k_units Data Type.

Listed below are two other syntax-related help pages located elsewhere in the Kornucopia help system that you may find helpful.