Matrix handling Python API

Hi

I have received a large .txt file containing imposed displacements for nodes. The .txt file is formed as a matrix.

I would like to import each single row of this .txt file and run a load case inside a loop with the given displacements, but as far as I know, this is not possible in SoFiSTiK?

Instead I’m trying to use the Python API in order to get the same effect. But I’m not quite sure how to implement the python file into SoFiSTiK as it seems to require a separate process which prevents using it inside a loop as loops can’t run across several processes.

I have made a small sample file to show my idea:
SoFiSTiK file:

+PROG template
head Create initial data file
<TEXT,FILE=Initial_Matrix.txt>
35,64,85,48
12,53,84,1
10,55,99,2
</TEXT>
END

+PROG template
head Create looping integer to run through matrix in Python
loop#i 3
<TEXT,FILE=loop.txt>
#i
</TEXT>

$ PYTHON FILE SHOULD GO HERE
$+sys python Python_File.py

$#INCLUDE Python_output.dat
$prt#list
endloop
END

Python file:

import csv

# Open File containing loop integer
f = open("loop.txt", 'r')

nums = f.readlines()
nums = int(nums[0])

# Open file containing imposed displacements
with open('Initial_Matrix.txt') as csv_file:
    reader = csv.reader(csv_file, delimiter=',')
    rows = list(reader)
data = rows[nums]

# Write file with SoFiSTiK syntax
with open('Python_Output.dat', mode='w') as f:
    print('let#List ',",".join(data), file=f, sep='')

The general idea is that SoFiSTiK creates a text file containing a looping integer which is imported into Python which then extract the given row from Initial_Matrix.txt based on that integer and spits out a new text file with proper SoFiSTiK syntax which is the #INCLUDE back into SoFiSTiK.

This way I can apply the imposed displacement inside a loop and don’t have to store a separate variable for each matrix row and in turn manually apply a said variable for several load cases.

Is this the way to go, or is there a simpler method?

In general:

  • #include reads the text file as soon as you hit the calculate button
    I.e. you can not generate a text file during calculation and include in a later step (unless you calculate thee generating part first and THEN calculate the reading part)
  • A workaround for this is using +Apply (just like the csm module) if you need it.
    However +Apply does not include text snippets but entire programs (like template, aqua, etc)

For your specific problem (correct me if I am mistaken):
The input text file you have received is static
I.e. you receive the text file and then you start your calculations. You might get an updated version in the future, but you don’t alter it during calculation

Therefore I would propose the following (simplest IMO):

  • In python read all the rows you are interested in and generate a new text file with if conditions for each row:
    if (row==1); let#list 35,64,85,48 $ Or perhaps you start the counter at 0, coding loops is cleaner
    elseif (row==2); let#list 12,53,84,1
    elseif (row==3); let#list 10,55,99,2
    endif
  • Run the python file BEFORE calculation begins
  • Include the python output file in your loop

Hi sfr, thank you for replying!

you are correct that the input text file is somewhat static.

I’m not sure I quite understand the method you’re describing. Do you want me to create a new text file for each row?

The reason why I want to do it this way is because sofistik doesn’t allow matrix operations or “parameterization” of variable names.

So instead of having to write:

let#row1 35,64,85,48
let#row2 12,53,84,1
let#row3 10,55,99,2
LC 1
	loop#i 4
		NODE NO #i TYPE WYY #Row1(#i)
	endloop
LC 2
	loop#i 4
		NODE NO #i TYPE WYY #Row2(#i)
	endloop
LC 3
	loop#i 4
		NODE NO #i TYPE WYY #Row3(#i)
	endloop

I would like python to save each row in the variable #list and update that variable continuously so I can squeeze it all into a loop instead:

loop#j 3
! PYTHON REDEFINES #LIST = ROW(#J)
	LC #j
		loop#i 4
			NODE NO #i TYPE WYY #List(#i)
		endloop
endloop

No, you recreate the text file in “cadinp” code.

Basically you make python generate the following cadinp command in a text file (call it py.txt):
if (row==0)
Let#list 35,64,85,48
elseif (row==1)
Let#list 12,53,81,1
elseif (row==2)
Let#list 10,55,99,2
endif

And now you can simplify your teddy code to:
Loop#row 3
#Include py.txt $ This pastes the above text and the if construct defines list to be the chosen row
Loop#node 4
Node No #node type wyy #list(#node)
EndLoop
EndLoop

In case you noticed, I started the “rowcounter” at zero to correspond with the loopvariabel.
Makes the code prettier :grin:

Basically you have pasted the entire if construct into the code and for every loop you are redefining your #list variable to correspond to the correct row.

Your gut reaction to try and define several variables with numbers (list1, list2, list3) or text files (list1.txt, list2.txt, list3.txt) is easily implemented in python, C++, etc.
In cadinp this approach is hard since there is no string manipulation of variables (i.e. you cannot generate let#listxx on the fly without a lot of workarounds)

Also the #include works similar to preprocessor directives in C or C++. The calculation program (wps.exe.) parses your teddy file and literally pastes the block/text at the include insertion point.
You can see this if you check the report and turn on the cadinp code.

1 Like

ah I see. So if I understand you correctly, you want me to manually run python when I receive a new input file, which then creates a .dat file.

As such, there’s no actual dynamic connection between python and Sofistik?

EDIT:
I just got it. Here’s what I ended up with

SoFiSTiK:

+prog template urs:19.1 $ Create initial data file
<TEXT,FILE=Initial_Matrix.txt>
35,64,85,43
12,53,84,99
10,55,99,1337
</TEXT>
END

+sys python Python_File.py
+apply Python_output.dat

+prog template urs:19.2 $ Import python output

loop#row 3
    #INCLUDE Python_output.dat
    prt#list
endloop
END

Python:

import csv

# Write file with SoFiSTiK syntax
with open('Initial_Matrix.txt', 'r') as csv_file, open('Python_Output.dat', mode='w') as f:
    reader = csv.reader(csv_file, delimiter=',')
    rows = list(reader)
    
    for i in range(0,len(rows)): 
        if i == 0:
            if1 = 'if'
        elif i > 0 & i < len(rows):
            if1 = 'elseif'
        
        str_rows = ",".join(rows[i])
        f.write(f"{if1} (#row == {i})\nlet#List {str_rows}\n")
    f.write('endif\n')

Exactly.
Nicely done!