Showing posts with label LibreOffice Basic. Show all posts
Showing posts with label LibreOffice Basic. Show all posts

Sunday, July 16, 2017


--- Crosstabulation ---

2016

Until I figured out how to create stub and banner tables with LibreOffice pivot tables, I was considering making a tabling routine for DANSYSX but that would be redundant, so my focus for crosstabulation became just the 2-way and 3-way statistics programs. Since they are involved and, from scratch, it will give me a chance to talk about my structured approach to developing Calc routines.

XTab is a big (!) matrix function, but it's a sequential function, meaning that it has many procedures that are completed before the next process is started. My first task is to outline the processes with comments. A comment in LibreOffice Basic begins with a single apostrophe. Here, I include a header:

FUNCTION XTab(optional InMat,optional InType, optional CellCont as string,
_)

(It's not complete, because I'll be adding parameters as different procedures require them.) Then, comes a descriptive comment:

'Performs a complete analysis on crosstabulation data
'from a raw data table or a crosstabulation of data.
'The input is the data and the output is a matrix containing
'a stub-and-banner table of cell data followed by
'a table of statistics. InType=1 (Crosstab), 2 (raw data)
'CellCont is a binary string designated data to be displayed in
'cells of output: 10 places: counts, exp counts, row%, column%,
'Total%, residuals, stand. residuals, adj, residuals, chi square,
'likelihood chi square, odds, compare column proportions (Bonferroni).

and a declarations and initialization section:

'Declarations and initialization. If InMat isn't specified,
'InMat=MatArray1. If InType isn't specified, InType=1 (crosstab).
'If CellCont isn't specified, CellCont="100000000000"

DIM OutMat(), Xi(), Yj(), OutR as integer, OutC as integer
DIM I AS INTEGER, J AS INTEGER, K AS INTEGER, L AS INTEGER

That will also grow as I add code. I'll declare other variables in alphabetical order, inserting each as I use them, so I can keep up with names that I've used. Double declarations and accidentally using the same variable for multiple purposes without knowing it can be major headaches.

Then, I know that I'll have to crosstabulate data if a raw data table is fed to the function, so I annotate that section:

'Crosstabulation, if needed.


Redim OutMat(), Xi(1,2), Yj(1,2)

Thinking ahead, I know I'll have to redimension the output matrix when I know what the stub-and-banner part of the display will look like. Everything below that will be pretty straightforward and I can add size as I finish each section. Each section is independent of the ones below it, so I can test each one as I finish it. I have a test data on the Report sheet of DANSYSX that I will use to progressively test each section.

The final section will just display the OutMat matrix which, by that time, will already be constructed by the procedures I have coded. OutMat is a convention I use for all macros that display a matrix.

I will have the crosstabulation saved in MatArray3, just to increase it's utility and make it compatible with the matrix functions I've programmed for DANSYS and DANSYSX.

I use a lot of spreadsheet functions in the program and that's a little tricky. The topic isn't covered in the programming guide, but you can find information online.

To use a spreadsheet function in a Basic routine, you must first declare an object variable to hold the function. You only have to declare it once since you can't use more than one spreadsheet function at a time. I use the same name, svc, and declare it as an object variable:

DIM svc AS OBJECT

Then the variable has to be set as a function variable. That only has to be done once, also.

svc=createUnoService("com.sun.star.sheet.FunctionAccess")

Uno is the object manipulation language used by LibreOffice. This statement invokes a Uno service called FunctionAccess.

After the variable is set, it can be used to call spreadsheet functions such as :

PCS=svc.CallFunction("CHIDIST",Array(CSQW,dfCS))

This statement calls the CHIDIST spreadsheet function. CHIDIST evaluates a chi square value at a specified degrees of freedom value. The CallFunction method for a function variable requires the name of the function as a string (in quotes) followed by the information to be transferred to the function in an array. The function requires a value and degrees of freedom (if you look the function up in the Calc Function Wizard, you can see the structure of the function) to be passed as an array. Even if you pass an array to a spreadsheet function, you have to use the Array statement followed by the name of the array in parentheses. You can see that here:

XMIN=SVC.CALLFUNCTION("MIN",ARRAY(Eij))

in a statement that finds the minimum value in the array Eij.

To get the return value, a variable, PCS, is set equal to the function variable.

One of the aggravating things about LibreOffice Basic is you can't return a matrix like this, which is why I went about programming a complete matrix language for DANSYS.

Displaying the results is easy. I just set the function to the output matrix, like:

XTab=OutMat.

There was some debugging that had to be done, but you can see the final product when I post DANSYSX in the Therian Timeline, here:

http://www.theriantimeline.com/excursions/labbooks

Note: DANSYSX version 1.0 is available now on the timeline.



Thursday, July 6, 2017


--- DANSYSX: Complex cubic equations ---

2016

Both LibreOffice Calc and DANSYS are able to do basic complex arithmetic, but I want DANSYSX to be able to do advanced complex mathematics. There are a number of algorithms on Jean Pierre Moreau's excellent website (http://jean-pierre.moreau.pagesperso-orange.fr/index.html) that I want to adapt to this purpose.

The first is an algorithm to determine the roots of a cubic equation with complex coefficients. It uses the cubic formula (like the quadratic formula, but for cubic functions):

z(1...3) = cube root (-q/2 + square root(q^2/4 + p^3/27)) + cube root(-q/2 - square root(q^2/4 + p^3/27))

where: p = (c/a) - (b^2/3a^2)
           q = (2b^3/27a^3) + (d/a) - (bc)/3a^2)

I want the complex mathematics programs to be in their own folder so I use Tools>Macros>Organize Macros>LibreOffice Basic>Organizer to create the new folder, Complex under DANSYSX.ods>Standard. To begin programming, I open the LibreOffice Basic Macros dialog and select DANSYSX.ods>Standard>Complex and click the Edit button.

I am more comfortable programming in an environment where indexes run from 1 to whatever (instead of beginning at 0), so I add the Option Base 1 to the top of the editor Window, and change the name of the blank macro from Main to CompCube. I will also want the Macro to be a function so I change the header from Sub to Function and the result looks like this:

REM  *****  BASIC  *****

Option Base 1
Function CompCube

End Function

Now, everything is ready for me to start programming. I have to decide what I want my program to look like - the inputs and outputs. And I want to add a descriptive comment to the top of the code.

Looking at the Moreau algorithm, I see that I will need to input four complex numbers and I want to stick to the LibreOffice convention of designating complex numbers as strings of the form a±bi, so I want to pass four strings to the function. I will also want to output three complex numbers because a cubic equation will have up to three unique roots. So I'll start with the following header:

REM  *****  BASIC  *****

Option Base 1
Function CompCube(Z1 AS STRING, Z2 AS STRING, Z3 AS STRING, Z4 AS STRING)
'The following code determines the roots of a cubic equation
'defined by four complex coefficients:
'a z^3 + b z^2 + c z + d = 0
'The algorithm is an adaptation of one (CRoot3) from the Jean Pierre Moreau
'website: http://jean-pierre.moreau.pagesperso-orange.fr/Basic/croot3_bas.txt
'Several subroutines are used to perform elementary complex
'calculations and are shared with other DANSYSX programs.
'The global variables CMPN 1, 2, ..., 6 are also used to
'transfer complex variables between subroutines.

End Function

Most of the code will be in upper caps. This is not required by LibreOffice Basic, but I have found that I catch errors a lot quicker if everything is capitalized.

The Function statement names the program CompCube. The "Comp" part is a convention I established in DANSYS. Functions that deal with complex numbers begin with "Comp". The variables passed to the function (also called "parameters" or "arguments") are defined in the parentheses following the function name.

One of the flaws in LibreOffice Basic (all programming languages have weak spots) is that you can't transfer matrix values to and from functions and subroutines. (When I say "can't", I mean that I looked in all the resources I could find and tried to figure it out and couldn't - there may be a way but it remains a mystery to me.) I will have to transfer two value matrices (representing complex numbers), so I will store each value in a 1x2 array called CMPN1, CMPN2, to CMPN6. These arrays are defined as global variables (variables that can be used by all routines in DANSYSX and that won't lose their values between calls) which as designated in the Structures folder as, for instance:

Global CMPN1(1,2)

This generates a global array that has 1 row and 2 columns.

Global variables have to be defined outside of any function or subroutine. I collect them in the Structures folder.

I will also have to output three strings representing complex numbers so, to call this function from a spreadsheet, the user will select a 3x1 range of cells and type "=CompCube()' Inside the parentheses will either be four strings or the addresses of four cells containing strings.

I have written programs before that take complex numbers and have saved the code to do it in a KeyNote file of other handy code snippets that I use over and over. I will do that in the initialization section of the program.

Now, I go through the original code and figure out all the variables that I will need and declare them. At first guess, I come up with:

DIM A(2), B(2), C(2), D(2), U(2), V(2), W(2)
DIM DET(2), P(2), Q(2), SDET(2), U1(2), U2(2)
DIM V1(2), V2(2), ZT1(2), ZT2(2), Z(9,2)

The DIM statement is simple. It is just DIM followed by a list of variables separated by commas.

All the variables but the last are arrays with two places. Those are complex numbers.

Using my tried and true complex number decoders in the initialization section to load A, B, C, and D from Z1, Z2, Z3, and Z4, I wrote:

DIM A(2), B(2), C(2), D(2), U(2), V(2), W(2)
DIM DET(2), P(2), Q(2), SDET(2), U1(2), U2(2)
DIM V1(2), V2(2) ZT1(2), ZT2(2), Z(9,2)
DIM I AS INTEGER, ST AS STRING
DIM OutMat(3,1) AS STRING

OutMat is a convention I use to output arrays. I load all the data I want to display into an array and I set the function equal to that array. In this case, I'll be displaying three complex number strings - 3 rows and 1 column.

Notice that the last two variables are explicitly specified to be integers. LibreOffice allows you to specify variables as INTEGER (between values of -32768 and 32767), LONG (integers between -2147483648 and 2147483647), SINGLE (floating-point values between 3.402823E38 and 1.401298R-45), DOUBLE (floating-point values between 1.79769313486232E308 and 4.94065645841247E-324), CURRENCY (64 bit numbers displayed as fixed decimal numbers with 15 non-whole and 4 decimal places ranging from -922337203685477.5808 to 922337203685477.5807), STRING (character strings holding up to 65,535 characters), BOOLEAN (stores TRUE/1 or FALSE/0), and DATE (date and time values stores in an internal format.) Constants can also be declared using the keyword CONST (instead of DIM).

Variable types can also be specified by tagging a declaration symbol to the end of a variable name as follows:

Single !
Double #
Currency @
String $
For instance, DIM Money@ would declare the variable Money as the type Currency.

ST=""
FOR I=1 TO LEN(Z1)
IF MID(Z1,I,1)="+" OR MID(Z1,I,1)="-" THEN
A(1)=VAL(ST)
ST=""
END IF
ST=ST & MID(Z1,I,1)
NEXT I
A(2)=VAL(ST)

ST=""
FOR I=1 TO LEN(Z2)
IF MID(Z2,I,1)="+" OR MID(Z2,I,1)="-" THEN
B(1)=VAL(ST)
ST=""
END IF
ST=ST & MID(Z2,I,1)
NEXT I
B(2)=VAL(ST)

ST=""
FOR I=1 TO LEN(Z3)
IF MID(Z3,I,1)="+" OR MID(Z3,I,1)="-" THEN
C(1)=VAL(ST)
ST=""
END IF
ST=ST & MID(Z3,I,1)
NEXT I
C(2)=VAL(ST)

ST=""
FOR I=1 TO LEN(Z4)
IF MID(Z4,I,1)="+" OR MID(Z4,I,1)="-" THEN
D(1)=VAL(ST)
ST=""
END IF
ST=ST & MID(Z4,I,1)
NEXT I
D(2)=VAL(ST)

MSGBOX A(1)
MSGBOX A(2)
MSGBOX B(1)
MSGBOX B(2)
MSGBOX C(1)
MSGBOX C(2)
MSGBOX D(1)
MSGBOX D(2)

Each block of code beginning with ST works like this: First a blank string is generated in the string variable ST. Then, the following loop looks at each character of an input string Z. If the character is a + or a -, the first cell of a two place array is loaded with the numerical value of whatever is in ST and ST is emptied. If the character is anything else, it is tagged onto the right end of the string in ST and the next character comes up for review.  This goes on until each character of the input string (LEN(Z) is the number of characters in string Z) has been considered, then the numerical value of whatever is left in ST is loaded into the second cell of the two place variable.

The MID(Z,I,1) extracts 1 character from the ith position of string Z.

MSGBOX is a command that causes the following to be displayed in a message box. In this case, I use it to make sure that the input values are being transferred to the complex value arrays. They work so I'm happy. The MSGBOX commands will be deleted.

The next step requires that I add a subroutine that performs complex division and one that performs complex multiplication. I design these so that they work on the complex numbers stored in CMPN1 and CMPN2 and return an answer in CMPN3. You notice that the CMPN global variables act like a stack of registers for complex numbers. Here are CDIV and CMUL:

SUB CDIV()
'Subroutine to divide two complex numbers.
DIM CC(2), D AS DOUBLE, Z(2)

'Determine the value of the denominator
D=CMPN2(1,1)*CMPN2(1,1)+CMPN2(1,2)*CMPN2(1,2)
IF D<1e-15 THEN
MSGBOX "Complex divisiion by 0"
EXIT SUB
ELSE
CC(1)=CMPN2(1,1)
CC(2)=-CMPN2(1,2)
CMPN4(1,1)=CMPN1(1,1)
CMPN4(1,2)=CMPN1(1,2)
CMPN5(1,1)=CMPN2(1,1)
CMPN5(1,2)=CMPN2(1,2)
CMPN2(1,1)=CC(1)
CMPN2(1,2)=CC(2)
CMUL
Z(1)=CMPN3(1,1)
Z(2)=CMPN3(1,2)
CMPN2(1,1)=CMPN5(1,1)
CMPN2(1,2)=CMPN5(1,2)
CMPN3(1,1)=CMPN3(1,1)/D
CMPN3(1,2)=CMPN3(1,2)/D
END IF
END SUB

SUB CMUL()
'DETERMINE THE PRODUCT OF CMPN1 AND CMPN2
'SAVE THE RESULT IN CMPN3
CMPN3(1,1)=CMPN1(1,1)*CMPN2(1,1)-CMPN1(1,2)*CMPN2(1,2)
CMPN3(1,2)=CMPN1(1,1)*CMPN2(1,2)+CMPN1(1,2)*CMPN2(1,1)
END SUB

You will recognize most of the arithmetic operators. * is LibreOffice's multiplication operator (instead of x) and an exponent is denoted by the symbol ^. For instance, x squared would look like: x^2.

The difference between a function and a sub (or subroutine) is that the function returns a value and a sub does something but does not output a value. For instance, CMUL calculates the product of two complex numbers and loads them into a global array, but does not return the product to the program that called the sub. That program has to get the value from the global array. Here's the part of CompCube that uses CDIV and retrieves the result:

CMPN1(1,1)=C(1)
CMPN1(1,2)=C(2)
CMPN2(1,1)=A(1)
CMPN2(1,2)=A(2)
CDIV
U(1)=CMPN3(1,1)
U(2)=CMPN3(1,2)
MSGBOX U(1)
MSGBOX U(2)

This divides the complex number in C by the one in A. In order for CDIV to work C has to be loaded into CMPN1 and A has to be loaded into CMPN2. To call CDIV, I simply type CDIV. Then to get the result back as U, I load CMPN3 into U. Again, the MSGBOX commands display the result and, after I'm satisfied that CDIV works, I'll delete them. That's called "debugging". I prefer to debug as I go.

Now, to complete the calculation of P:

CMPN1(1,1)=B(1)
CMPN1(1,2)=B(2)
CMPN2(1,1)=B(1)
CMPN2(1,2)=B(2)
CMUL
V(1)=CMPN3(1,1)
V(2)=CMPN3(1,2)

CMPN1(1,1)=A(1)
CMPN1(1,2)=A(2)
CMPN2(1,1)=A(1)
CMPN2(1,2)=A(2)
CMUL
W(1)=CMPN3(1,1)
W(2)=CMPN3(1,2)

W(1)=3*W(1)
W(2)=3*W(2)

CMPN1(1,1)=V(1)
CMPN1(1,2)=V(2)
CMPN2(1,1)=W(1)
CMPN2(1,2)=W(2)
CDIV
ZT1(1)=CMPN3(1,1)
ZT1(2)=CMPN3(1,2)
P(1)=U(1)-ZT1(1)
P(2)=U(2)-ZT1(2)

You should be able to follow that. It's the same process I used to calculate c/a. It checks out, so I can continue.

The next piece of the puzzle is Q. It's a straight forward but long calculation (you see why they just teach the quadratic formula in high school and leave the cubic and quartic monsters for folks that are too curious for their own good.) That section of code looks like:

'Calculate q=2b^3/27a^3 + d/a - bc/3a^2
CMPN1(1,1)=B(1)
CMPN1(1,2)=B(2)
CMPN2(1,1)=V(1)
CMPN2(1,2)=V(2)
CMUL
W(1)=CMPN3(1,1)
W(2)=CMPN3(1,2)

W(1)=2*W(1)
W(2)=2*W(2)

CMPN1(1,1)=A(1)
CMPN1(1,2)=A(2)
CMPN2(1,1)=A(1)
CMPN2(1,2)=A(2)
CMUL
U(1)=CMPN3(1,1)
U(2)=CMPN3(1,2)

CMPN1(1,1)=U(1)
CMPN1(1,2)=U(2)
CMPN2(1,1)=A(1)
CMPN2(1,2)=A(2)
CMUL
V(1)=CMPN3(1,1)
V(2)=CMPN3(1,2)

V(1)=27*V(1)
V(2)=27*V(2)

CMPN1(1,1)=W(1)
CMPN1(1,2)=W(2)
CMPN2(1,1)=V(1)
CMPN2(1,2)=V(2)
CDIV
Q(1)=CMPN3(1,1)
Q(2)=CMPN3(1,2)

CMPN1(1,1)=D(1)
CMPN1(1,2)=D(2)
CMPN2(1,1)=A(1)
CMPN2(1,2)=A(2)
CDIV
U(1)=CMPN3(1,1)
U(2)=CMPN3(1,2)

Q(1)=Q(1)+U(1)
Q(2)=Q(2)+U(2)

CMPN1(1,1)=B(1)
CMPN1(1,2)=B(2)
CMPN2(1,1)=C(1)
CMPN2(1,2)=C(2)
CMUL
U(1)=CMPN3(1,1)
U(2)=CMPN3(1,2)

CMPN1(1,1)=A(1)
CMPN1(1,2)=A(2)
CMPN2(1,1)=A(1)
CMPN2(1,2)=A(2)
CMUL
V(1)=CMPN3(1,1)
V(2)=CMPN3(1,2)

V(1)=3*V(1)
V(2)=3*V(2)

CMPN1(1,1)=U(1)
CMPN1(1,2)=U(2)
CMPN2(1,1)=V(1)
CMPN2(1,2)=V(2)
CDIV
W(1)=CMPN3(1,1)
W(2)=CMPN3(1,2)

Q(1)=Q(1)-W(1)
Q(2)=Q(2)-W(2)

It's very repetitive and allows me to copy, paste,and modify most of it from earlier code. Then there is det:


'Calculate DET = q^2/4 + p^3/27

CMPN1(1,1)=Q(1)
CMPN1(1,2)=Q(2)
CMPN2(1,1)=Q(1)
CMPN2(1,2)=Q(2)
CMUL
U(1)=CMPN3(1,1)
U(2)=CMPN3(1,2)
U(1)=U(1)/4
U(2)=U(2)/4

CMPN1(1,1)=P(1)
CMPN1(1,2)=P(2)
CMPN2(1,1)=P(1)
CMPN2(1,2)=P(2)
CMUL
V(1)=CMPN3(1,1)
V(2)=CMPN3(1,2)

CMPN1(1,1)=P(1)
CMPN1(1,2)=P(2)
CMPN2(1,1)=V(1)
CMPN2(1,2)=V(2)
CMUL
W(1)=CMPN3(1,1)
W(2)=CMPN3(1,2)
W(1)=W(1)/27
W(2)=W(2)/27

DET(1)=U(1)+W(1)
DET(2)=U(2)+W(2)

Now, I need the square root of DET, and for that, I need the following subroutine that calculates the square root of the complex number stored in CMPN1.

SUB CSQRT()
'Determine the square root of CMPN1
'Save the result in CMPN3.
DIM R AS DOUBLE
R=SQR(CMPN1(1,1)*CMPN1(1,1)+CMPN1(1,2)*CMPN1(1,2))
CMPN3(1,1)=SQR((R+CMPN1(1,1))/2)
CMPN3(1,2)=SQR((R-CMPN1(1,1))/2)
IF CMPN1(1,2)<0 THEN CMPN3(1,2)=-CMPN3(1,2)
END SUB

The result is stored in CMPN3. SQR is the LibreOffice command to take the square root of the value in parentheses following it.

Now, the section of CompCube that calculates the square root of DET is:

'Calculate the square root of DET

CMPN1(1,1)=DET(1)
CMPN2(1,2)=DET(2)
CSQRT
SDET(1)=CMPN3(1,1)
SDET(2)=CMPN3(1,2)

V(1)=-Q(1)/2+SDET(1)
V(2)=-Q(2)/2+SDET(2)

If you look back up at the cubic formula, you will see where those last two statements came from.

So, I need the three cube roots of V and to get that, I need a subroutine to calculate the cube root of CMPN1 and store the results in CMPN2, CMPN3, and CMPN4, and that subroutine will need a function to determine the phase of CMPN1.

Here's the function CPHASE:

FUNCTION CPHASE(NUM,DENOM)
'Returns the phase of a complex number between -pi and pi
DIM PPI AS DOUBLE, CP AS DOUBLE
PPI=4*ATN(1)
IF ABS(DENOM)<1e-15 THEN
IF NUM<0 THEN
CPHASE=-PP1/2
ELSE
CPHASE=PPI/2
END IF
ELSE
CP=ATN(NUM/DENOM)
IF DENOM<0 THEN
CPHASE=CPHASE+PPI
ELSE
CPHASE=CP
END IF
END IF
END FUNCTION

Notice that the header says "FUNCTION" instead of "SUB". This code takes two arguments: NUM and DENOM, and returns a value. The value is determined by the "CPHASE=" statements. When you set a function's name equal to a value, you are outputing the value. To call the function, you set some variable equal to the function name (You'll see that below). ATN is a LibreOffice Basic function that calculates the arctangent of some number. ABS is the function that returns the absolute value of a number. This function is basically a decisiion tree that determines what value CPHASE will take.

If DENOM is less than 1E-15 (or 1 x 10-15), then if NUM is negative CPHASE is -pi/2, else CPHASE is pi/2. If DENOM is more than 1E-15, then if DENOM is negative, CPHASE is the arctangent of NUM/DENOM + pi, otherwise, it is just the arctangent of NUM/DENOM.

Here's the subroutine CCUBRT, which calculates the cube roots of a complex number (there will be three cube roots).

SUB CCUBRT()
'Calculates the three cube roots of CMPN1.
'The results are stored in CMPN2, CMPN3, and CMPN4
DIM PPI AS DOUBLE, R AS DOUBLE, T AS DOUBLE, TT AS DOUBLE

PPI=4*ATN(1)
R=SQR(CMPN1(1,1)*CMPN1(1,1)+CMPN1(1,2)*CMPN1(1,2))
R=R^(1/3)
T=CPHASE(CMPN1(1,2),CMPN1(1,1))
T=T/3
CMPN2(1,1)=R*COS(T)
CMPN2(1,2)=R*SIN(T)
TT=T-(2*PPI/3)
CMPN3(1,1)=R*COS(TT)
CMPN3(1,2)=R*SIN(TT)
TT=T+(2*PPI/3)
CMPN4(1,1)=R*COS(TT)
CMPN4(1,2)=R*SIN(TT)
END SUB

The statement that calls CPHASE is:

T=CPHASE(CMPN1(1,2),CMPN1(1,1))

It feeds the values stored in CMPN1 to the function and transfers the result to the variable T.

The part of CompCube that calculates the cubed roots of V is:

CMPN1(1,1)=V(1)
CMPN1(1,2)=V(2)
CCUBRT
U(1)=CMPN2(1,1)
U(2)=CMPN2(1,2)
U1(1)=CMPN3(1,1)
U1(2)=CMPN3(1,2)
U2(1)=CMPN4(1,1)
U2(2)=CMPN4(1,2)

U, U1, and U2 stores the resulting cubed roots.

And the part that calculates the cube roots of W is almost exactly the same except for the variables used:

W(1)=-Q(1)/2-SDET(1)
W(2)=-Q(2)/2-SDET(2)

CMPN1(1,1)=W(1)
CMPN1(1,2)=W(2)
CCUBRT
V(1)=CMPN2(1,1)
V(2)=CMPN2(1,2)
V1(1)=CMPN3(1,1)
V1(2)=CMPN3(1,2)
V2(1)=CMPN4(1,1)
V2(2)=CMPN4(1,2)

The next section collects all the possible roots of the equation. Only three will be appropriate, but the nine possible roots will all have to be tested.

'Find all possible solutions to the formula.

Z(1,1)=U(1)+V(1)
Z(1,2)=U(2)+V(2)
Z(2,1)=U(1)+V1(1)
Z(2,2)=U(2)+V1(2)
Z(3,1)=U(1)+V2(1)
Z(3,2)=U(2)+V2(2)
Z(4,1)=U1(1)+V(1)
Z(4,2)=U1(2)+V(2)
Z(5,1)=U1(1)+V1(1)
Z(5,2)=U1(2)+V1(2)
Z(6,1)=U1(1)+V2(1)
Z(6,2)=U1(2)+V2(2)
Z(7,1)=U2(1)+V(1)
Z(7,2)=U2(2)+V(2)
Z(8,1)=U2(1)+V1(1)
Z(8,2)=U2(2)+V1(2)
Z(9,1)=U2(1)+V2(1)
Z(9,2)=U2(2)+V2(2)

To test these values, I have to use them to evaluate the cubic equation. I use another subroutine for that, and that subroutine requires a function to return the absolute value of the complex number. I also realized that I needed to save the complex coefficients for this routine so I went back and did that in MatArray1, a global array I use to transfer matrices around.

'Load equation into MatArray1
REDIM MatArray1(4,2)
MatArray1(1,1)=A(1)
MatArray1(1,2)=A(2)
MatArray1(2,1)=B(1)
MatArray1(2,2)=B(2)
MatArray1(3,1)=C(1)
MatArray1(3,2)=C(2)
MatArray1(4,1)=D(1)
MatArray1(4,2)=D(1)

The sizes of dynamic arrays like the global MatArrays can be changed any time using the REDIM (redimension) command.

Here are the test routine and absolute value function:

FUNCTION CABS(ARG1, ARG2)
'This function returns the absolute value of a complex number
'ARG1+ARG2 i
CABS=SQR(ARG1*ARG1+AGR2*ARG2)
END FUNCTION

SUB CTEST()
'This subroutine tests a solution set of a cubic equation for appropriate
'values.
'The output is z1=a z^3 + b x^2 + c z + d
'In other words, it evaluates the original cubic equation.
'It gets the equation from MatArray1.
'The value of the complex variable is in CMPN4.
DIM U(2), V(2), W(2), Z1(2)

CMPN1(1,1)=MatArray1(1,1)
CMPN1(1,2)=MatArray1(1,2)
CMPN1(2,1)=CMPN4(1,1)
CMPN1(2,2)=CMPN4(1,2)
CMUL
U(1)=CMPN3(1,1)
U(2)=CMPN3(1,2)

CMPN1(1,1)=CMPN4(1,1)
CMPN1(1,2)=CMPN4(1,2)
CMPN1(2,1)=U(1)
CMPN1(2,2)=U(2)
CMUL
V(1)=CMPN3(1,1)
V(2)=CMPN3(1,2)

CMPN1(1,1)=CMPN4(1,1)
CMPN1(1,2)=CMPN4(1,2)
CMPN1(2,1)=V(1)
CMPN1(2,2)=V(2)
CMUL
W(1)=CMPN3(1,1)
W(2)=CMPN3(1,2)

Z1(1)=W(1)
Z1(2)=W(2)

CMPN1(1,1)=MatArray1(2,1)
CMPN1(1,2)=MatArray1(2,2)
CMPN1(2,1)=CMPN4(1,1)
CMPN1(2,2)=CMPN4(1,2)
CMUL
U(1)=CMPN3(1,1)
U(2)=CMPN3(1,2)

CMPN1(1,1)=U(1)
CMPN1(1,2)=U(2)
CMPN1(2,1)=CMPN4(1,1)
CMPN1(2,2)=CMPN4(1,2)
CMUL
V(1)=CMPN3(1,1)
V(2)=CMPN3(1,2)

Z1(1)=Z1(1)+V(1)
Z1(2)=Z1(2)+V(2)

CMPN1(1,1)=MatArray1(3,1)
CMPN1(1,2)=MatArray1(3,2)
CMPN1(2,1)=CMPN4(1,1)
CMPN1(2,2)=CMPN4(1,2)
CMUL
U(1)=CMPN3(1,1)
U(2)=CMPN3(1,2)

Z1(1)=Z1(1)+U(1)+MatArray1(4,1)
Z1(2)=Z1(2)+U(2)+MatArray1(4,2)

CMPN3(1)=Z1(1)
CMPN3(2)=Z1(2)

END SUB

After all that I had some small bugs (small bugs = disaster) and had to do some fine tuning. Debugging got rid of the bugs and my test subroutine was too sensitive for the tiny round off errors I produced so I had to change the test criterion:

IF X<0.001 THEN
to the more liberal:

IF X<10 THEN

Now, it works, and it work within input precision.

I'll be programming a lot more and it will be available at the Timeline. The basics of LibreOffice Basic are here and they should give you enough to figure out what I'm doing in future programs. I have a large, complex project coming up that will give me a chance to share some of my problem solving techniques with you.






Thursday, June 8, 2017


--- Getting started in programming ---

2016

There are still plenty of reasons to program computers.

Most software products have a limited bag of tricks. The developer has tried to figure all the features that a typical user would want from their program but there is no truly typical user. Sooner or later, everyone is going to gaze dreamily into the distance and say, "I sure wish this program could....(fill in the blank.)" The answer is to be able to program the computer to do what you want it to do.

A lifelong learner has much more reason to program. Programming a concept into a computer - teaching the computer to do what you're learning how to do - requires that you take the concept apart (analysis) and then put it back together in a form a computer will understand. You get to see the inner workings of the concept. How can you help but to learn?

There are many very accessible languages available to the beginner.

In fact, BASIC stands for Beginner's All-purpose Symbolic Instruction Code. It is designed to be easy to understand and easy to use. Even better, most of the office productivity suites like Microsoft Office, LibreOffice, and WPS uses a version of BASIC as a macro language. If one of these suites doesn't do something you want it to do, you can teach it to do the task and it will happily oblige you.

I like LibreOffice Basic because LibreOffice is a free, full powered office suite and, if you are using it to look at my Excursion documents, which are LibreOffice documents, then you're already set for programming with LibreOffice Basic.

There are programs that are designed to be fun and useful.

Scratch is a programming language consisting of blocks that you snap together. It was originally designed to manipulate graphic images in a learning environment, but with Custom Blocks, you can program it to perform more typical computer tasks.

If you want to play around with Scratch, you can find it here:

https://wiki.scratch.mit.edu/wiki/Scratch_Wiki_Home

Two languages that provide instant feedback as you program are Python and LiveCode. Both show you what your statements do as you type them in.

Python is a very popular, but fairly typical language. It seems too be very easy to  learn using supplied tutorials and user guides. You can find it here:

https://www.python.org

A modern version of a cool, old language (it was way before it's time back in the 70s) is LiveCode, an update of a language called HyperCard. It's also an object oriented language that will manipulate graphic objects, but it does much, much more. It's intended to be able to work with many platforms. Look at it here:

https://livecode.com

A similar such language also designed to be multiplatform (you can write programs to be used with many different systems - they're even working to make their product compatible with smartphone operating systems) is Xojo and here is their website:

http://www.xojo.com

In future articles, I will be giving you some tips for programming in LibreOffice Basic, primarily because it is very accessible, and most of the work I'm doing now is in that language, but, certainly, if you decide you like programming, look at these other languages and you might even want to spread out and play with some of the many (many!) other languages like C, FORTRAN, or (Whoa!) the assembly language for your processor.


Thursday, March 16, 2017


--- Open Source and Freeware ---

2016

I use some software that I have bought and I have nothing against commercial software but I will rarely recommend it here.

It is possible to completely equip a computer using free downloads. If you need an operating system, there's Linux and if you need a graphical user interface, there's Ubuntu. For web browsing there are programs like Google Chrome and Firefox. LibreOffice is as good an office suite as Microsoft Office (and sometimes better). There is good freeware for just about anything you want to do and I want you to be able to do the things I write about even if you can't buy software, so I will be talking about freeware and inexpensive software here.

Although there is some commercial software that I have real problems with. When I became a vocational evaluator, I set out to automate my department and, of course, the fact that most office productivity suites have macro languages that allow users to program them to do specific tasks made a good office suite essential. The facility I worked for used Microsoft Office and I was happy with that for many years until they came out with Vista.

With Vista, Microsoft no longer supported their Visual Basic for Applications, which was, of course, the language of my evaluation software. There was no more upgrading for me and I lived in terror that a virus or lightening strike would destroy my computer (Well, not actually "terror". I'm made of sterner stuff than that.)

So, I have had a grudge against Microsoft ever since. My next office productivity suite was OpenOffice, which is a free download, by the way. I was more or less happy with that although it was rather unstable. You could place a number of picture files in a document, close it, and, when you reopen it, all the pictures have gone. Also, large and complex files get crankier and crankier. They tend to crash.

Looking at their forum for help in dealing with idiosyncracies, I ran into regulars who insulted people looking for help and maintained that users should not program macros in spreadsheets (my favorite part of a productivity suite).  So, I was less and less happy with OpenOffice until one day I downloaded an upgrade which wouldn't install. When I uninstalled the old version as recommended by the user forum, the new version still wouldn't install, so I no longer had a working office suite and I was in the middle of a complex programming job.

Looking for alternatives, I found that a group of disgruntled programmers split away from OpenOffice when it was bought out by Oracle and they formed LibreOffice.

I was so happy with LibreOffice that I am now going to recommend it.

LibreOffice has retained some of the instablilities of OpenOffice but the people at LibreOffice actively support the software so that much of the glitchiness has been worked out of it. Upgrades occasionally introduce new problems but they tend to be ironed out in the next upgrade. Also, most upgrades are perceptibly upgrades. I can tell that something has been done to improve the program.

The major components of LibreOffice are:
Writer, the word processor
Calc, the spreadsheet
Impress, the presentation editor
Draw, the graphics editor
Base, the database
There is also a mathematics formula editor. Many LibreOffice users also produce extensions for the package.

The primary macro language is LibreOffice's version of Visual Basic which is more powerful than the classical BASIC in that it allows the user to manipulate most of the  elements of LibreOffice but it's weaker in that it's a subset of BASIC that has dropped some  of the core commands of the BASIC language. For instance, there is no Data....Read structure. I missed that one so much that I programmed a function into DANSYS that would do pretty much the same thing. Otherwise, it's a real pain to load specific values into a matrix from code.

Generally, I enjoy using LibreOffice. It can be obtained from the LibreOffice website: https://www.libreoffice.org

A warning: LibreOffice does not have an offline help file. It has to be downloaded separately at the LibreOffice website. And the last upgrade would not access it. Hopefully the next upgrade will address that. Our Internet is glitchy and I'd like to be able to look at helpfiles when the Web is down.

There's some confusion about open source and freeware. Open source isn't freeware (some of it is and some of it isn't). "Open source" just means that the code for the software is open to the user so they can modify it to suit their needs. "Freeware" means that the software can be downloaded and used for free. There are a variety of licenses that designate what you are legally able to do with any particular program.

Often, it is considered a common courtesy that, if you like a freeware program and will continue to use it, and can afford it, that you make a donation to the creator. Some creators specifically say that they are not after donations. For instance, my statistics package is something I dreamed up for my own use and decided to make available. I don't want donations. If a creator wants donations, they generally make that plain in the description of the product.

My favorite source for freeware programs is SourceForge (https://sourceforge.net).