├── .gitignore ├── LICENSE.txt ├── MANIFEST.in ├── README.md ├── itasca ├── __init__.py ├── main.py └── meshConvert.py ├── setup.py └── tests ├── __init__.py ├── create-test-data.f3dat ├── flac3d_socket_test.f3dat ├── flac_udec_socket_test.dat ├── gent.f3prj ├── itasca_tests.py ├── p2pClient_test.py ├── p2pServer_test.py ├── pfc_bridge_server.p3dat ├── pfc_bridge_test.py ├── test_fish_file_read.py ├── test_flac3d_connection.py ├── test_flac_connection.py ├── test_three_dec_connection.py ├── test_udec_connection.py ├── testdata.fish └── three_dec_socket_test.dat /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | build/ 3 | dist/ 4 | itasca.egg-info/ 5 | *.egg-info 6 | bin.fish 7 | errorlog.dmp 8 | errorlog.txt 9 | pp_temp.fis 10 | pp_tempfile.dat 11 | testdata.fish 12 | MANIFEST 13 | venv/ 14 | *.swp 15 | local_test/ 16 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Unless otherwise specified by LICENSE.txt files in individual 2 | directories, all code is 3 | 4 | Copyright (C) 2016 Jason Furtney 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without 8 | modification, are permitted provided that the following conditions are 9 | met: 10 | 11 | 1. Redistributions of source code must retain the above copyright 12 | notice, this list of conditions and the following disclaimer. 13 | 2. Redistributions in binary form must reproduce the above copyright 14 | notice, this list of conditions and the following disclaimer in 15 | the documentation and/or other materials provided with the 16 | distribution. 17 | 3. Neither the name of itasca-python nor the names of its 18 | contributors may be used to endorse or promote products derived 19 | from this software without specific prior written permission. 20 | 21 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 22 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 23 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 24 | DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 25 | INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 26 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 27 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 28 | HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 29 | STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 30 | IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 31 | POSSIBILITY OF SUCH DAMAGE. 32 | -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | recursive-include itasca *.py 2 | recursive-include tests *.py 3 | tests/bin.fish 4 | tests/gent.f3prj 5 | recursive-include tests *.f3dat 6 | recursive-include tests *.dat 7 | recursive-include tests *.p3dat 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Python connectivity for Itasca software. 2 | 3 | This library implements a connection via sockets between Python and 4 | the numerical modeling software from Itasca Consulting Group. 5 | Functions are provided to read and write files in the Itasca FISH 6 | binary format. 7 | 8 | www.itascacg.com/software 9 | 10 | *FLAC*, *FLAC3D*, *PFC2D*, *PFC3D*, *UDEC* & *3DEC* 11 | 12 | The Python interpreter is now embedded within *FLAC3D* and *PFC3D* see: 13 | - http://www.itascacg.com/python-and-pfc 14 | - https://www.itascacg.com/software/flac3d/videos/using-python-in-flac3d-6 15 | 16 | In the Python interpreter inside *FLAC3D* and *PFC3D* the functionality of this module is 17 | available in the `itasca.util` module. 18 | 19 | 20 | *Note:* If the used Itasca code includes a python installation the use of the p2pLinkClient/p2pLinkServer classes (python to python socket link) is recommended. For Itascsa codes without python use the TCP Socket with Itasca Fish. 21 | 22 | ## Installation 23 | 24 | Via pip: 25 | 26 | ```python 27 | pip install itasca 28 | ``` 29 | 30 | or from source: 31 | ```python 32 | python setup.py install 33 | ``` 34 | 35 | ## Requirements 36 | 37 | `numpy` >= 1.0.2 38 | 39 | ## Usage 40 | 41 | The Itasca python module can be used after the installation such as any other 42 | python module. The `client` is running outside the Itasca code and the `server` 43 | is running within the Itasca code. The `client` is a python script using this 44 | module. For the `server` can be started as python or fish function. See the 45 | accordings sections below for further details. 46 | 47 | ### Python to Python socket link 48 | 49 | Simple TCP socket client and server classes are provided to link two 50 | Python programs. `str`, `int`, `float`, and NumPy arrays can be sent 51 | over the link. The classes `p2pLinkServer` and `p2pLinkClient` are 52 | demonstrated below. 53 | 54 | An example of the server side of the connection is given below. 55 | 56 | ```python 57 | from itasca import p2pLinkServer 58 | import numpy as np 59 | 60 | with p2pLinkServer() as s: 61 | s.start() 62 | 63 | while True: 64 | a = s.read_data() 65 | if type(a) is int and a ==-1: 66 | print("done") 67 | break 68 | print(f"got {a}") 69 | if type(a) is np.ndarray: 70 | print(a.shape) 71 | ``` 72 | 73 | Finally, an example of the client side of the connection is given. 74 | 75 | ```python 76 | from itasca import p2pLinkClient 77 | import numpy as np 78 | 79 | with p2pLinkClient() as s: 80 | s.connect("localhost") 81 | s.send_data("James") 82 | s.send_data(np.array([1,2,3])) 83 | 84 | adata = np.random.random((1000,1000)) 85 | print(adata) 86 | s.send_data(adata) 87 | 88 | for i in range(10): 89 | print(f"sent {i}") 90 | s.send_data(i) 91 | 92 | s.send_data(-1) 93 | ``` 94 | 95 | ### TCP socket connection to all Itasca codes using FISH 96 | 97 | The classes `FLAC3D_Connection`, `PFC3D_Connection`, 98 | `FLAC_Connection`, `UDEC_Connection` and `threeDEC_Connection` allow 99 | Python to connect to an Itasca code and exchange information with FISH 100 | programs. The data types are converted between FISH and Python. `int`, 101 | `float`, `str`, and length 2 and 3 vectors are supported. 102 | 103 | The following is an example of the Python side of a connection. 104 | 105 | ```python 106 | from itasca import FLAC3D_Connection 107 | 108 | flac3d = FLAC3D_Connection() 109 | flac3d.start("flac3d_socket_test.f3dat") 110 | flac3d.connect() 111 | 112 | flac3d.send(99.9) 113 | flac3d.send([1,2]) 114 | flac3d.send([1,2,3]) 115 | flac3d.send("James") 116 | 117 | for i in range(10): 118 | print(f"sending {i}...") 119 | flac3d.send(i) 120 | print(f"sent {i}") 121 | value = flac3d.receive() 122 | print(f"got {value} from FLAC3D") 123 | 124 | flac3d.send(-1) 125 | flac3d.end() 126 | flac3d.shutdown() 127 | ``` 128 | 129 | On the Itasca code side a simple server loop reads these values and 130 | performs some action. Below is an example that is execute be the 131 | script above (see *tests/flac3d_socket_test.f3dat*): 132 | 133 | ``` 134 | ;; this is the FLAC3D side of the FLAC3D/Python coupling example. 135 | 136 | ;; FLAC3D is started by the Python program. When FLAC3D is started it 137 | ;; is given this input file as a command line argument. To start the 138 | ;; coupling example run this file by clicking the green button. The 139 | ;; open_socket FISH function opens a connection to the Python 140 | ;; program. FLAC3D then waits for the Python program to write a FISH 141 | ;; parameter. 1000.1 is added to the value and it is sent back to the 142 | ;; Python program. When FLAC3D receives a value of -1 from Python it 143 | ;; exits the read/write loop. 144 | 145 | 146 | def open_socket 147 | array data(1) 148 | s = socket.open(0,1,3333) 149 | 150 | loop i(0, 1000) 151 | oo = io.out('reading') 152 | oo = socket.read(data, 1, 1) 153 | oo = io.out(string.build("got %1 from python server", data(1))) 154 | 155 | if type(data(1)) = 1 then 156 | if data(1)=-1 then 157 | oo = io.out('breaking out of read/write loop') 158 | exit 159 | endif 160 | data(1) = data(1) + 1000.1 161 | endif 162 | 163 | oo = socket.write(data, 1, 1) 164 | end_loop 165 | end 166 | [ open_socket ] 167 | 168 | def close_socket 169 | oo = socket.close(1) 170 | oo = io.out('closed socket connection') 171 | end 172 | [ close_socket ] 173 | ``` 174 | 175 | ### Executable path 176 | 177 | The module uses the default installation path to search for the executables of 178 | the software code. The default executable path can be modified, example: 179 | 180 | ```python 181 | from itasca import FLAC3D_Connection 182 | f3d = FLAC3D_Connection() 183 | f3d.executable_name = "C:\\my\\custom\\path\\FLAC3D700\\exe64\\flac3d700_gui.exe" 184 | f3d.start('my_script') 185 | ... 186 | ``` 187 | 188 | ### Fish binary format reader 189 | 190 | The classes `FishBinaryReader` and `FishBinaryWriter` allow Python to 191 | read and write FISH binary data. The following is an example of FLAC3D 192 | writing FISH binary data. 193 | 194 | ``` 195 | def genIOtestdata 196 | array a(1) 197 | fp = open('testdata.fish', 1, 0) 198 | a(1) = 1 199 | oo = write(a, 1) 200 | a(1) = 99.987 201 | oo = write(a, 1) 202 | a(1) = 'James' 203 | oo = write(a, 1) 204 | a(1) = vector(99.5, 89.3) 205 | oo = write(a, 1) 206 | a(1) = vector(7,8,9) 207 | oo = write(a, 1) 208 | oo = close 209 | end 210 | @genIOtestdata 211 | ``` 212 | 213 | This data can be read from Python 214 | 215 | ```python 216 | from itasca import FishBinaryReader 217 | fish_file = FishBinaryReader("testdata.fish") 218 | ``` 219 | 220 | Either one entry at a time: 221 | 222 | ```python 223 | assert fish_file.read() == 1 224 | assert fish_file.read() == 99.987 225 | assert fish_file.read() == "James" 226 | assert fish_file.read() == [99.5, 89.3] 227 | assert fish_file.read() == [7.0, 8.0, 9.0] 228 | ``` 229 | Or all entries can be read into a list or `numpy` array 230 | 231 | ```python 232 | 233 | FishBinaryReader("testdata2.fish").aslist() # convert data to list 234 | [1, 2, 3, 4, 5, 6, 7] 235 | FishBinaryReader("testdata2.fish").asarray() # convert to NumPy array 236 | array([[1, 2, 3, 4, 5, 6, 7]) 237 | ``` 238 | 239 | Similarly FISH binary data files be written from Python. 240 | 241 | ```python 242 | FishBinaryWriter("t.fis", [12.23, 1, 33.0203, 1234.4]) 243 | ``` 244 | 245 | Special classes are provided for *UDEC* which uses a different integer 246 | size: `UDECFishBinaryReader`, and `UDECFishBinaryWriter` 247 | 248 | -------------------------------------------------------------------------------- /itasca/__init__.py: -------------------------------------------------------------------------------- 1 | """Python connectivity for Itasca software. 2 | 3 | This library implements a connection via sockets between Python and 4 | the numerical modeling software from Itasca Consulting Group. 5 | Functions are provided to read and write files in the Itasca FISH 6 | binary format. 7 | 8 | itascacg.com/software 9 | 10 | FLAC, FLAC3D, PFC2D, PFC3D, UDEC & 3DEC 11 | 12 | See https://github.com/jkfurtney/itasca-python for more information. 13 | """ 14 | 15 | __version__ = "2020.07.12" 16 | 17 | from .main import FLAC3D_Connection 18 | from .main import PFC3D_Connection 19 | from .main import PFC2D_Connection 20 | from .main import FishBinaryReader 21 | from .main import FishBinaryWriter 22 | from .main import FLAC_Connection 23 | from .main import UDEC_Connection 24 | from .main import ThreeDEC_Connection 25 | from .main import UDECFishBinaryReader 26 | from .main import UDECFishBinaryWriter 27 | from .main import p2pLinkClient, p2pLinkServer 28 | -------------------------------------------------------------------------------- /itasca/main.py: -------------------------------------------------------------------------------- 1 | """Python connectivity for Itasca software. 2 | 3 | This library implements a connection via sockets between Python and 4 | the numerical modeling software from Itasca Consulting Group. 5 | 6 | itascacg.com/software 7 | 8 | FLAC, FLAC3D, PFC2D, PFC3D, UDEC & 3DEC""" 9 | 10 | from __future__ import print_function 11 | import json 12 | import struct 13 | import socket 14 | import select 15 | import time 16 | import subprocess 17 | import os 18 | import numpy as np 19 | 20 | class _ItascaFishSocketServer(object): 21 | """Low level details of the Itasca FISH socket communication""" 22 | def __init__(self, fish_socket_id=0): 23 | assert type(fish_socket_id) is int and 0 <= fish_socket_id < 6 24 | self.port = 3333 + fish_socket_id 25 | 26 | def start(self): 27 | """() -> None. 28 | Open the low level socket connection. Blocks but allows the Python thread 29 | scheduler to run. 30 | """ 31 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 32 | self.socket.bind(("", self.port)) 33 | self.socket.listen(1) 34 | while True: 35 | connected, _, _ = select.select([self.socket], [], [], 0.0) 36 | if connected: break 37 | else: time.sleep(1e-8) 38 | self.conn, addr = self.socket.accept() 39 | print('socket connection established by', addr) 40 | 41 | def send_data(self, value): 42 | """(value: any) -> None. 43 | Send value to Itasca software. value must be int, float, length two list 44 | of doubles, length three list of doubles or a string. 45 | """ 46 | while True: 47 | _, write_ready, _ = select.select([], [self.conn], [], 0.0) 48 | if write_ready: break 49 | else: time.sleep(1e-8) 50 | 51 | if type(value) == int: 52 | self.conn.sendall(struct.pack("i", 1)) 53 | self.conn.sendall(struct.pack("i", value)) 54 | 55 | elif type(value) == float: 56 | self.conn.sendall(struct.pack("i", 2)) 57 | self.conn.sendall(struct.pack("d", value)) 58 | 59 | elif type(value) == list and len(value)==2: 60 | float_list = [float(x) for x in value] 61 | self.conn.sendall(struct.pack("i", 5)) 62 | self.conn.sendall(struct.pack("dd", float_list[0], float_list[1])) 63 | 64 | elif type(value) == list and len(value)==3: 65 | float_list = [float(x) for x in value] 66 | self.conn.sendall(struct.pack("i", 6)) 67 | self.conn.sendall(struct.pack("ddd", float_list[0], 68 | float_list[1], float_list[2])) 69 | elif type(value) == str: 70 | length = len(value) 71 | self.conn.sendall(struct.pack("ii", 3, length)) 72 | buffer_length = 4*(1+(length-1)/4) # this may be the wrong buffer length? 73 | format_string = "%is" % buffer_length 74 | value += " "*int(buffer_length - length) 75 | self.conn.sendall(struct.pack(format_string, value.encode('utf-8'))) 76 | else: 77 | raise Exception("unknown type in send_data") 78 | 79 | def wait_for_data(self): 80 | """() -> None. 81 | Block until data is available. This call allows the Python thread scheduler 82 | to run. 83 | """ 84 | while True: 85 | input_ready, _, _ = select.select([self.conn],[],[], 0.0) 86 | if input_ready: return 87 | else: time.sleep(1e-8) 88 | 89 | def read_type(self, type_string): 90 | """(type: str) -> any. 91 | This method should not be called directly. Use the read_data method. 92 | """ 93 | byte_count = struct.calcsize(type_string) 94 | bytes_read = 0 95 | data = b'' 96 | self.wait_for_data() 97 | while bytes_read < byte_count: 98 | data_in = self.conn.recv(byte_count - bytes_read) 99 | data += data_in 100 | bytes_read += len(data) 101 | assert len(data)==byte_count, "bad packet data" 102 | return data 103 | 104 | def read_data(self): 105 | """() -> any. 106 | Read the next item from the socket connection. 107 | """ 108 | raw_data = self.read_type("i") 109 | type_code, = struct.unpack("i", raw_data) 110 | if type_code == 1: # int 111 | raw_data = self.read_type("i") 112 | value, = struct.unpack("i", raw_data) 113 | return value 114 | 115 | elif type_code == 2: # float 116 | raw_data = self.read_type("d") 117 | value, = struct.unpack("d", raw_data) 118 | return value 119 | 120 | elif type_code == 3: # string 121 | length_data = self.read_type("i") 122 | length, = struct.unpack("i", length_data) 123 | buffer_length = length + ((4 * (1 + length // 4) - length) % 4) 124 | format_string = "%is" % buffer_length 125 | data = self.read_type(format_string) 126 | return data[:length].decode("utf-8") 127 | 128 | elif type_code == 5: # V2 129 | raw_data = self.read_type("dd") 130 | value0, value1 = struct.unpack("dd", raw_data) 131 | return [value0, value1] 132 | 133 | elif type_code == 6: # V3 134 | raw_data = self.read_type("ddd") 135 | value0, value1, value3 = struct.unpack("ddd", raw_data) 136 | return [value0, value1, value3] 137 | 138 | assert False, "Data read type error" 139 | 140 | def get_handshake(self): 141 | """() -> int. 142 | Read the handshake packet from the socket. 143 | """ 144 | 145 | raw_data = self.read_type("i") 146 | value, = struct.unpack("i", raw_data) 147 | print("handshake got: ", value) 148 | return value 149 | 150 | def close(self): 151 | """() -> None. 152 | Close the active socket connection. 153 | """ 154 | self.conn.close() 155 | 156 | 157 | class _ItascaSoftwareConnection(object): 158 | """Base class for communicating via FISH sockets with an Itasca program. This 159 | class spawns a new instance of the Itasca software and initializes the socket 160 | communication. 161 | """ 162 | 163 | def __init__(self, fish_socket_id=0): 164 | """(fish_socket_id=0: int) -> Instance. Constructor.""" 165 | self.executable_name = None 166 | self.server = _ItascaFishSocketServer(fish_socket_id) 167 | self.iteration = 0 168 | self.global_time = 0 169 | self.fishcode = 178278912 170 | 171 | def start(self, datafile_name): 172 | """(datafile_name: str) -> None. 173 | Launch Itasca software in a separate process, open the specified data file. 174 | The green execute button must be pressed in the Itasca software to start 175 | the calculation. 176 | """ 177 | if os.access(datafile_name, os.R_OK): 178 | args = f'"{self.executable_name}" call {datafile_name}' 179 | self.process = subprocess.Popen(args) 180 | 181 | else: 182 | raise ValueError("The file {} is not readable".format(datafile_name)) 183 | 184 | def connect(self): 185 | """() -> None. 186 | Connect to Itasca software, read fishcode to confirm connection. Call 187 | this function to establish the socket connection after calling the start 188 | method to launch the code. 189 | """ 190 | assert self.process 191 | self.server.start() 192 | value = self.server.get_handshake() 193 | print("got handshake packet") 194 | assert value == self.fishcode 195 | print("connection OK") 196 | 197 | def send(self, data): 198 | """(data: any) -> None. 199 | Send an item to the Itasca code. 200 | """ 201 | self.server.send_data(data) 202 | 203 | def receive(self): 204 | """() -> any. 205 | Read an item from the Itasca code. 206 | """ 207 | return self.server.read_data() 208 | 209 | def end(self): 210 | """() -> None. 211 | Close the socket connection. 212 | """ 213 | self.server.close() 214 | 215 | def shutdown(self): 216 | """()-> None. 217 | Shutdown running softwarecode. 218 | """ 219 | self.process.kill() 220 | 221 | class FLAC3D_Connection(_ItascaSoftwareConnection): 222 | """Launch and connect to FLAC3D.""" 223 | def __init__(self, fish_socket_id=0): 224 | """(fish_socket_id=0: int) -> Instance. Constructor.""" 225 | _ItascaSoftwareConnection.__init__(self, fish_socket_id) 226 | self.executable_name = "C:\\Program Files\\Itasca\\FLAC3D700\\exe64\\flac3d700_gui.exe" 227 | 228 | class PFC3D_Connection(_ItascaSoftwareConnection): 229 | """Launch and connect to PFC3D.""" 230 | def __init__(self, fish_socket_id=0): 231 | """(fish_socket_id=0: int) -> Instance. Constructor.""" 232 | _ItascaSoftwareConnection.__init__(self, fish_socket_id) 233 | self.executable_name = "C:\\Program Files\\Itasca\\PFC600\\exe64\\pfc3d600_gui.exe" 234 | 235 | class PFC2D_Connection(_ItascaSoftwareConnection): 236 | """Launch and connect to PFC2D.""" 237 | def __init__(self, fish_socket_id=0): 238 | """(fish_socket_id=0: int) -> Instance. Constructor.""" 239 | _ItascaSoftwareConnection.__init__(self, fish_socket_id) 240 | self.executable_name = "C:\\Program Files\\Itasca\\PFC600\\exe64\\pfc2d600_gui.exe" 241 | 242 | 243 | class FLAC_Connection(_ItascaSoftwareConnection): 244 | """Launch and connect to FLAC. """ 245 | def __init__(self, fish_socket_id=0): 246 | """(fish_socket_id=0: int) -> Instance. Constructor.""" 247 | _ItascaSoftwareConnection.__init__(self, fish_socket_id) 248 | self.executable_name = "C:\\Program Files\\Itasca\\FLAC800\\exe64\\flac800_64.exe" 249 | 250 | def connect(self): 251 | """() -> None. 252 | Call this function to connect to FLAC once it has been started manually. 253 | """ 254 | self.process=True 255 | _ItascaSoftwareConnection.connect(self) 256 | 257 | class UDEC_Connection(_ItascaSoftwareConnection): 258 | """Launch and connect to UDEC. """ 259 | def __init__(self, fish_socket_id=0): 260 | """(fish_socket_id=0: int) -> Instance. Constructor.""" 261 | _ItascaSoftwareConnection.__init__(self, fish_socket_id) 262 | self.executable_name = "C:\\Program Files\\Itasca\\UDEC700\\Exe64\\udec700_gui.exe" 263 | 264 | def connect(self): 265 | """() -> None. 266 | Call this function to connect to UDEC once it has been started manually. 267 | """ 268 | self.process=True 269 | _ItascaSoftwareConnection.connect(self) 270 | 271 | class ThreeDEC_Connection(_ItascaSoftwareConnection): 272 | """Launch and connect to 3DEC.""" 273 | def __init__(self, fish_socket_id=0): 274 | """(fish_socket_id=0: int) -> Instance. Constructor.""" 275 | _ItascaSoftwareConnection.__init__(self, fish_socket_id) 276 | self.executable_name = "C:\\Program Files\\Itasca\\3DEC520\\exe64\\3dec_dp520_gui_64.exe" 277 | 278 | 279 | class FishBinaryReader(object): 280 | """Read structured FISH binary files. 281 | Call the constructor with the structured FISH filename and call 282 | read() to read individual values. This class also supports 283 | iteration. Return values are converted to python types. Supports 284 | int, float, string, bool, v2 and v3. 285 | 286 | >>> fish_file = FishBinaryReader('my_fish_data.fish') 287 | >>> for val in fish_file: 288 | ... print val 289 | 42 290 | "this is a string" 291 | [1.0,2.0,3.0] 292 | """ 293 | def __init__(self, filename): 294 | """(filename: str) -> FishBinaryReader object. """ 295 | self.file = open(filename, "rb") 296 | fishcode = self._read_int() 297 | assert fishcode == 178278912, "invalid FISH binary file" 298 | 299 | def _read_int(self): 300 | data = self.file.read(struct.calcsize('i')) 301 | value, = struct.unpack("i", data) 302 | return value 303 | 304 | def _read_double(self): 305 | data = self.file.read(struct.calcsize('d')) 306 | value, = struct.unpack("d", data) 307 | return value 308 | 309 | def read(self): 310 | """() -> any. 311 | Read and return a value (converted to a Python type) from the .fish 312 | binary file. 313 | """ 314 | type_code = self._read_int() 315 | 316 | if type_code == 1: # int 317 | return self._read_int() 318 | 319 | if type_code == 8: # bool 320 | value = self._read_int() 321 | return_value = True if value else False 322 | return return_value 323 | 324 | if type_code == 2: # float 325 | return self._read_double() 326 | 327 | if type_code == 3: 328 | length = self._read_int() 329 | buffer_length = 4*(1+(length-1)/4) # this may be wrong 330 | format_string = "%is" % buffer_length 331 | data = self.file.read(struct.calcsize(format_string)) 332 | return data[:length].decode("utf-8") 333 | 334 | if type_code == 5: # v2 335 | return [self._read_double(), self._read_double()] 336 | 337 | if type_code == 6: # v3 338 | return [self._read_double(), self._read_double(), 339 | self._read_double()] 340 | 341 | def __iter__(self): 342 | self.file.seek(0) # return to the begining of the file 343 | self._read_int() # pop the magic number off 344 | return self 345 | 346 | def __next__(self): 347 | """() -> any. 348 | Get the next item from the FISH binary file. 349 | """ 350 | try: 351 | return self.read() 352 | except: 353 | raise StopIteration 354 | 355 | next = __next__ # alias for Python 2 support. 356 | 357 | def aslist(self): 358 | """() -> [any]. 359 | Return fish file contents as a Python list. 360 | """ 361 | return [x for x in self] 362 | 363 | def asarray(self): 364 | """() -> numpy array. 365 | Return fish file contents as a numpy array. Types must be homogeneous. 366 | """ 367 | return np.array(self.aslist()) 368 | 369 | class UDECFishBinaryReader(FishBinaryReader): 370 | "Special version of FishBinarReader for files generated by UDEC." 371 | def _read_int(self): 372 | data = self.file.read(struct.calcsize('i')) 373 | value, = struct.unpack("i", data) 374 | data = self.file.read(struct.calcsize('i')) # read the dummy data off 375 | return value 376 | 377 | class FishBinaryWriter(object): 378 | """Write fish binary data. data can be any iterable (array, list, etc.). 379 | example: FishBinaryWriter("t.fis", [12.23, 1, 33.0203, 1234.4]) 380 | """ 381 | def __init__(self, filename, data): 382 | """(filename: str, data: iterable) -> FishBinaryWriter instance.""" 383 | with open(filename, "wb") as f: 384 | self._write_int(f,178278912) 385 | for datum in data: 386 | if type(datum) is float: 387 | self._write_int(f, 2) 388 | self._write_double(f, datum) 389 | elif type(datum) is int: 390 | self._write_int(f, 1) 391 | self._write_int(f, datum) 392 | else: 393 | raise TypeError( 394 | "Currently unsupported type for Fish binary write ") 395 | 396 | def _write_int(self, f, datum): 397 | f.write(struct.pack("i", datum)) 398 | 399 | def _write_double(self, f, datum): 400 | f.write(struct.pack("d", datum)) 401 | 402 | class UDECFishBinaryWriter(FishBinaryWriter): 403 | """Fish Binary writer for UDEC (which has 8 byte ints)""" 404 | def _write_int(self, f, datum): 405 | f.write(struct.pack("i", datum)) 406 | f.write(struct.pack("i", 0)) 407 | 408 | ###################################################################### 409 | # p2pLink below here 410 | ###################################################################### 411 | 412 | class _fileSocketAdapter(object): 413 | """This object is an adapter which allows np.save and np.load to write 414 | directly to the socket. This object appears to be a file object but does 415 | reading and writing over a socket connection. 416 | """ 417 | def __init__(self, s): 418 | """(s: _baseSocket) -> None. 419 | Constructor. 420 | """ 421 | self.s=s 422 | self.first = True 423 | self.offset = 0 424 | 425 | def write(self, data): 426 | """(data: str) -> None. 427 | Write bytes to stream. 428 | """ 429 | self.s._sendall(data) 430 | 431 | def read(self, byte_count): 432 | """(byte_count: int) -> str. 433 | Read bytes from stream. 434 | """ 435 | bytes_read = 0 436 | data = b'' 437 | # this is a hack because we have to support seek for np.load 438 | if self.offset: 439 | assert self.offset <= byte_count 440 | assert self.offset==6 441 | assert not self.first 442 | data += self.buff 443 | bytes_read += self.offset 444 | self.offset = 0 445 | 446 | while bytes_read < byte_count: 447 | self.s.wait_for_data() 448 | data_in = self.s.conn.recv(min(4096, byte_count-bytes_read)) 449 | data = b"".join([data, data_in]) 450 | bytes_read += len(data_in) 451 | 452 | # this is a hack because we have to support seek for np.load 453 | if self.first and byte_count==6: 454 | self.buff = data 455 | self.first = False 456 | return data 457 | 458 | def readline(self): 459 | """() -> str. 460 | Read a line from the stream.""" 461 | data='' 462 | while True: 463 | self.s.wait_for_data() 464 | byte = self.s.conn.recv(1) 465 | if byte == '\n': 466 | return data 467 | else: 468 | data += byte 469 | return data 470 | 471 | def seek(self, a0, a1): 472 | """(offset: int, mode: int) -> None. 473 | This is a hack to support np.load and np.save talking over sockets. 474 | """ 475 | assert a1 == 1 476 | assert a0 == -6 477 | assert len(self.buff)==6 478 | self.offset = 6 479 | 480 | class _socketBase(object): 481 | code = 12345 482 | 483 | def _sendall(self, data): 484 | """(bytes: str) -> None. 485 | Low level socket send, do not call this function directly. 486 | """ 487 | nbytes = len(data) 488 | sent = 0 489 | while sent < nbytes: 490 | self._wait_for_write() 491 | sent += self.conn.send(data[sent:]) 492 | 493 | def _wait_for_write(self): 494 | """() -> None. 495 | Block until socket is write ready but let thread scheduler run. 496 | """ 497 | while True: 498 | _, write_ready, _ = select.select([], [self.conn], [], 0.0) 499 | if write_ready: break 500 | else: time.sleep(1e-8) 501 | 502 | def send_data(self, value): 503 | """(value: any) -> None. 504 | Send value. value must be a number, a string or a NumPy array. 505 | """ 506 | if type(value) == int: 507 | self._sendall(struct.pack("i", 1)) 508 | self._sendall(struct.pack("i", value)) 509 | 510 | elif type(value) == float: 511 | self._sendall(struct.pack("i", 2)) 512 | self._sendall(struct.pack("d", value)) 513 | 514 | elif type(value) == list and len(value)==2: 515 | float_list = [float(x) for x in value] 516 | self._sendall(struct.pack("i", 5)) 517 | self._sendall(struct.pack("dd", float_list[0], float_list[1])) 518 | 519 | elif type(value) == list and len(value)==3: 520 | float_list = [float(x) for x in value] 521 | self._sendall(struct.pack("i", 6)) 522 | self._sendall(struct.pack("ddd", float_list[0], 523 | float_list[1], float_list[2])) 524 | 525 | elif type(value) == str: 526 | length = len(value) 527 | self._sendall(struct.pack("ii", 3, length)) 528 | buffer_length = 4*(1+(length-1)/4) 529 | format_string = "%is" % buffer_length 530 | value += " "*int(buffer_length - length) 531 | self._sendall(struct.pack(format_string, value.encode("utf-8"))) 532 | 533 | elif type(value) == np.ndarray: 534 | self._sendall(struct.pack("i", 7)) 535 | np.save(_fileSocketAdapter(self), value) 536 | 537 | elif type(value) == dict: 538 | length = len(value) 539 | data = json.dumps(value).encode("utf-8") 540 | self._sendall(struct.pack("ii", 8, len(data))) 541 | self._sendall(data) 542 | 543 | else: 544 | raise Exception("unknown type in send_data") 545 | 546 | def wait_for_data(self): 547 | """() -> None. 548 | Block until data is available. This call allows the Python thread 549 | scheduler to run. 550 | """ 551 | while True: 552 | input_ready, _, _ = select.select([self.conn],[],[], 0.0) 553 | if input_ready: return 554 | else: time.sleep(1e-8) 555 | 556 | def read_type(self, type_string, array_bytes=None): 557 | """(type: str) -> any. 558 | This method should not be called directly. Use the read_data method. 559 | """ 560 | if array_bytes is None: 561 | byte_count = struct.calcsize(type_string) 562 | 563 | else: 564 | byte_count = array_bytes 565 | 566 | bytes_read = 0 567 | data = b'' 568 | while bytes_read < byte_count: 569 | self.wait_for_data() 570 | data_in = self.conn.recv(min(4096,byte_count - bytes_read)) 571 | data = b"".join([data, data_in]) 572 | bytes_read += len(data_in) 573 | assert len(data)==byte_count, "bad packet data" 574 | return data 575 | 576 | def read_data(self): 577 | """() -> any. 578 | Read the next item from the socket connection. 579 | """ 580 | raw_data = self.read_type("i") 581 | type_code, = struct.unpack("i", raw_data) 582 | 583 | if type_code == 1: # int 584 | raw_data = self.read_type("i") 585 | value, = struct.unpack("i", raw_data) 586 | return value 587 | 588 | elif type_code == 2: # float 589 | raw_data = self.read_type("d") 590 | value, = struct.unpack("d", raw_data) 591 | return value 592 | 593 | elif type_code == 3: # string 594 | length_data = self.read_type("i") 595 | length, = struct.unpack("i", length_data) 596 | buffer_length = (4*(1+(length-1)/4)) 597 | format_string = "%is" % buffer_length 598 | data = self.read_type(format_string) 599 | return data[:length].decode("utf-8") 600 | 601 | elif type_code == 5: # V2 602 | raw_data = self.read_type("dd") 603 | value0, value1 = struct.unpack("dd", raw_data) 604 | return [value0, value1] 605 | 606 | elif type_code == 6: # V3 607 | raw_data = self.read_type("ddd") 608 | value0, value1, value3 = struct.unpack("ddd", raw_data) 609 | return [value0, value1, value3] 610 | 611 | elif type_code == 7: # NumPy array: 612 | a = np.load(_fileSocketAdapter(self)) 613 | return a 614 | 615 | elif type_code == 8: # python dict 616 | raw_data = self.read_type("i") 617 | length, = struct.unpack("i", raw_data) 618 | data = self.read_type(None, length) 619 | return json.loads(data) 620 | 621 | assert False, "Data read type error" 622 | 623 | def close(self): 624 | """() -> None. 625 | Close the active socket connection. 626 | """ 627 | if hasattr(self, "conn"): 628 | self.conn.shutdown(socket.SHUT_RDWR) 629 | self.conn.close() 630 | 631 | else: 632 | self.socket.shutdown(socket.SHUT_RDWR) 633 | self.socket.close() 634 | 635 | def __enter__(self): 636 | return self 637 | 638 | def __exit__(self, eType, eValue, eTrace): 639 | print("cleaning up socket") 640 | self.close() 641 | 642 | class p2pLinkServer(_socketBase): 643 | """Python to Python socket link server. Send and receive numbers, strings 644 | and NumPy arrays between Python instances.""" 645 | def __init__(self, port=5000): 646 | """(port=5000) -> None. Create a Python to Python socket server. Call 647 | the start() method to open the connection.""" 648 | assert type(port) is int 649 | self.port = port 650 | 651 | def start(self): 652 | """() -> None. Open the socket connection. Blocks but allows the 653 | Python thread scheduler to run. 654 | """ 655 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 656 | self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) 657 | self.socket.bind(("", self.port)) 658 | self.socket.listen(1) 659 | 660 | while True: 661 | connected, _, _ = select.select([self.socket], [], [], 0.0) 662 | if connected: 663 | break 664 | 665 | else: 666 | time.sleep(1e-8) 667 | 668 | self.conn, addr = self.socket.accept() 669 | assert self.read_data() == _socketBase.code 670 | print("got code") 671 | 672 | class p2pLinkClient(_socketBase): 673 | """Python to Python socket link client. Send and receive numbers, strings 674 | and NumPy arrays between Python instances.""" 675 | def __init__(self,port=5000): 676 | """(port=5000) -> None. Create a Python to Python socket link client. 677 | Call the start() method to open the connection."" 678 | """ 679 | assert type(port) is int 680 | self.port = port 681 | def connect(self, machine): 682 | """(machine: str) -> None. Connect to a Python to Python link server. 683 | """ 684 | self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 685 | self.socket.connect((machine,self.port)) 686 | self.conn = self.socket 687 | self.send_data(_socketBase.code) 688 | print("sent code") 689 | -------------------------------------------------------------------------------- /itasca/meshConvert.py: -------------------------------------------------------------------------------- 1 | import itasca as it 2 | gmsh_template = r"""$MeshFormat 3 | 2.2 0 8 4 | $EndMeshFormat 5 | $Nodes 6 | {} 7 | {}$EndNodes 8 | $Elements 9 | {} 10 | {}$EndElements 11 | """ 12 | flac3d_brick_to_gmsh = [2,4,7,5,0,1,6,3] 13 | flac3d_wedge_to_gmsh = [5,2,4,3,0,1] 14 | flac3d_tet_to_gmsh = [0,2,3,1] 15 | flac3d_pyramid_to_gmsh = [2,0,1,4,3] 16 | 17 | 18 | def FLAC3D_to_gmsh(filename="tmp.gmsh"): 19 | """ 20 | Convert the current FLAC3D model in to the Gmsh format. Returns a mesh filename. 21 | """ 22 | 23 | gp_id_to_index = { gp.id() : i+1 for i,gp in enumerate(it.gridpoint.list())} 24 | 25 | gp_data = "" 26 | for i,gp in enumerate(it.gridpoint.list()): 27 | gp_data +="{} {} {} {}\n".format(i+1, *gp.pos()) 28 | 29 | element_data = "" 30 | for i,z in enumerate(it.zone.list()): 31 | element_type = None 32 | gp_list = [gp_id_to_index[gp.id()] for gp in z.gridpoints()] 33 | 34 | if z.type() == "brick": 35 | element_type = 5 36 | gp_list = [gp_list[j] for j in flac3d_brick_to_gmsh] 37 | elif z.type() == "wedge": 38 | element_type = 6 39 | gp_list = [gp_list[j] for j in flac3d_wedge_to_gmsh] 40 | elif z.type() == "pyramid": 41 | gp_list = [gp_list[j] for j in flac3d_pyramid_to_gmsh] 42 | element_type = 7 43 | elif z.type() == "tetra": 44 | element_type = 4 45 | gp_list = [gp_list[j] for j in flac3d_tet_to_gmsh] 46 | elif z.type() == "dbrick": 47 | raise ValueError("Degenerate bricks are not supported.") 48 | 49 | element_data += "{} {} 2 99 2 {}\n".format(i+1, element_type, " ".join(map(str,gp_list))) 50 | 51 | with open(filename, "w") as f: 52 | print(gmsh_template.format(it.gridpoint.count(), gp_data, it.zone.count(), element_data), file=f) 53 | return filename 54 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | long_description = """ 2 | Python connectivity for Itasca software. 3 | 4 | This library implements a connection via sockets between Python and 5 | the numerical modeling software from Itasca Consulting Group. 6 | Functions are provided to read and write files in the Itasca FISH 7 | binary format. 8 | 9 | www.itascacg.com/software 10 | 11 | FLAC, FLAC3D, PFC2D, PFC3D, UDEC & 3DEC 12 | 13 | See https://github.com/jkfurtney/itasca-python for more information. 14 | """ 15 | 16 | from setuptools import setup 17 | setup( 18 | name = 'itasca', 19 | packages = ['itasca'], # this must be the same as the name above 20 | version = "2020.07.12", 21 | description = "Python connectivity for Itasca software", 22 | long_description = long_description, 23 | author = 'Jason Furtney', 24 | requires = ['numpy'], 25 | author_email = 'jkfurtney@gmail.com', 26 | url = "https://github.com/jkfurtney/itasca-python", 27 | keywords = 'Itasca,FLAC,FLAC3D,PFC,UDEC,3DEC,PFC2D,PFC3D,FISH'.split(","), 28 | license = "BSD", 29 | classifiers = [ 30 | 'Programming Language :: Python :: 2', 31 | 'Programming Language :: Python :: 3', 32 | "Development Status :: 5 - Production/Stable", 33 | "License :: OSI Approved :: BSD License", 34 | 'Topic :: Scientific/Engineering :: Interface Engine/Protocol Translator', 35 | "Intended Audience :: Science/Research" 36 | ], 37 | ) 38 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itascaconsulting/itasca-python/91c766abc64e4940605b454ef96ad694926b61d7/tests/__init__.py -------------------------------------------------------------------------------- /tests/create-test-data.f3dat: -------------------------------------------------------------------------------- 1 | new 2 | 3 | def genIOtestdata 4 | array a(1) 5 | fp = open('testdata.fish', 1, 0) 6 | a(1) = 1 7 | oo = write(a, 1) 8 | a(1) = 99.987 9 | oo = write(a, 1) 10 | a(1) = 'James' 11 | oo = write(a, 1) 12 | a(1) = vector(99.5, 89.3) 13 | oo = write(a, 1) 14 | a(1) = vector(7,8,9) 15 | oo = write(a, 1) 16 | oo = close 17 | end 18 | @genIOtestdata 19 | 20 | -------------------------------------------------------------------------------- /tests/flac3d_socket_test.f3dat: -------------------------------------------------------------------------------- 1 | ;; this is the FLAC3D side of the FLAC3D/Python coupling example. 2 | 3 | ;; FLAC3D is started by the Python program. When FLAC3D is started it 4 | ;; is given this input file as a command line argument. To start the 5 | ;; coupling example run this file by clicking the green button. The 6 | ;; open_socket FISH function opens a connection to the Python 7 | ;; program. FLAC3D then waits for the Python program to write a FISH 8 | ;; parameter. 1000.1 is added to the value and it is sent back to the 9 | ;; Python program. When FLAC3D receives a value of -1 from Python it 10 | ;; exits the read/write loop. 11 | 12 | 13 | 14 | def open_socket 15 | array data(1) 16 | s = socket.open(0,1,3333) 17 | 18 | loop i(0, 1000) 19 | oo = io.out('reading') 20 | oo = socket.read(data, 1, 1) 21 | oo = io.out(string.build("got %1 from python server", data(1))) 22 | 23 | if type(data(1)) = 1 then 24 | if data(1)=-1 then 25 | oo = io.out('breaking out of read/write loop') 26 | exit 27 | endif 28 | data(1) = data(1) + 1000.1 29 | endif 30 | 31 | oo = socket.write(data, 1, 1) 32 | end_loop 33 | end 34 | [ open_socket ] 35 | 36 | def close_socket 37 | oo = socket.close(1) 38 | oo = io.out('closed socket connection') 39 | end 40 | [ close_socket ] 41 | -------------------------------------------------------------------------------- /tests/flac_udec_socket_test.dat: -------------------------------------------------------------------------------- 1 | ;; This is the FLAC or UDEC side of the Python coupling example. 2 | 3 | ;; This example demonstrates low level reading and writing of fish 4 | ;; parameters (int, float or string) across a socket. 5 | 6 | ;; To run this example: 7 | 8 | ;; (i) The python program test_udec_connection.py or 9 | ;; test_flac_connection.py should be run first to start the socket 10 | ;; server that UDEC or FLAC will connect to. 11 | 12 | ;; (ii) FLAC (or UDEC) must be started manually and this file needs to be 13 | ;; called. 14 | 15 | ;; The open_socket FISH function opens a connection to the 16 | ;; Python program. FLAC then waits for the Python program to write a 17 | ;; FISH parameter. 1 is added to the value and it is sent back to the 18 | ;; Python program. When FLAC receives a value of -1 from Python it 19 | ;; exits the read/write loop. 20 | 21 | new 22 | 23 | def open_socket 24 | array data(1) 25 | s = sopen(0,0) 26 | loop i(0, 1000) 27 | oo = sread(data, 1, 0) 28 | oo = out('got ' + string(data(1)) + ' from python server') 29 | if type(data(1)) = 1 then 30 | if data(1)=-1 then 31 | oo=out('breaking out of read/write loop') 32 | exit 33 | endif 34 | data(1) = data(1) + 1 35 | endif 36 | oo=swrite(data, 1, 0) 37 | end_loop 38 | end 39 | open_socket 40 | 41 | def close_socket 42 | oo=sclose(0) 43 | oo=out('closed socket connection') 44 | end 45 | close_socket 46 | -------------------------------------------------------------------------------- /tests/gent.f3prj: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itascaconsulting/itasca-python/91c766abc64e4940605b454ef96ad694926b61d7/tests/gent.f3prj -------------------------------------------------------------------------------- /tests/itasca_tests.py: -------------------------------------------------------------------------------- 1 | from nose.tools import * 2 | import itasca 3 | 4 | def test_get_version(): 5 | assert type(itasca.get_version()) == str 6 | -------------------------------------------------------------------------------- /tests/p2pClient_test.py: -------------------------------------------------------------------------------- 1 | from itasca import p2pLinkClient 2 | import numpy as np 3 | 4 | with p2pLinkClient() as s: 5 | 6 | s.connect("localhost") 7 | 8 | s.send_data("James") 9 | 10 | s.send_data(np.array([1,2,3])) 11 | 12 | adata = np.random.random((1000,1000)) 13 | print(adata) 14 | s.send_data(adata) 15 | 16 | for i in range(10): 17 | print("sent", i) 18 | s.send_data(i) 19 | 20 | s.send_data(-1) 21 | -------------------------------------------------------------------------------- /tests/p2pServer_test.py: -------------------------------------------------------------------------------- 1 | # p2pServer_test.py 2 | # Test server file to be run in Itasca software 3 | from itasca.util import p2pLinkServer 4 | import numpy as np 5 | 6 | with p2pLinkServer() as s: 7 | s.start() 8 | while True: 9 | a=s.read_data() 10 | if type(a) is int and a ==-1: 11 | print("done") 12 | break 13 | print("got", a) 14 | if type(a) is np.ndarray: 15 | print(a.shape) 16 | -------------------------------------------------------------------------------- /tests/pfc_bridge_server.p3dat: -------------------------------------------------------------------------------- 1 | new 2 | 3 | ;; fish python protocall: 4 | ;; 5 | ;; 10 signals a command to run: 6 | ;; read a string and execuit it as a command 7 | ;; return zero on success 8 | ;; 11 signals a fish statement to execute and return the result of 9 | ;; read a string, execuit it as a fish expression and return the value. 10 | ;; 12 signals a fish assignment statement to execute 11 | ;; read a string, execuit it as a fish expression and return zero 12 | 13 | 14 | def inline 15 | array data_in(1) 16 | array tmp(1) 17 | array data_out(1) 18 | end 19 | inline 20 | 21 | def execute_command 22 | ;; take the string "scommand" write to a file and call the file. 23 | oo=out('writing command') 24 | status = open('pp_tempfile.dat',1,1) 25 | tmp(1) = scommand 26 | status = write(tmp,1) 27 | status = close 28 | 29 | command 30 | call pp_tempfile.dat 31 | end_command 32 | end 33 | 34 | def evalate_string 35 | ;; wrap the string 'fstring' in a fish function, write to file, call 36 | ;; return value is in ret_value 37 | status = open('pp_temp.fis',1,1) 38 | tmp(1) = 'def tmpfunc' 39 | status = write(tmp,1) 40 | 41 | tmp(1) = 'ret_value = ' + fstring 42 | status = write(tmp,1) 43 | 44 | tmp(1) = 'end' 45 | status = write(tmp,1) 46 | 47 | tmp(1) = 'tmpfunc' 48 | status = write(tmp,1) 49 | 50 | status = close 51 | 52 | command 53 | call pp_temp.fis 54 | end_command 55 | end 56 | 57 | def evalate_string_assign 58 | ;; wrap the string 'fstring' in a fish function, write to file, call 59 | ;; return value is in ret_value 60 | status = open('pp_temp.fis',1,1) 61 | tmp(1) = 'def tmpfunc' 62 | status = write(tmp,1) 63 | 64 | tmp(1) = fstring 65 | status = write(tmp,1) 66 | 67 | tmp(1) = 'end' 68 | status = write(tmp,1) 69 | 70 | tmp(1) = 'tmpfunc' 71 | status = write(tmp,1) 72 | 73 | status = close 74 | 75 | command 76 | call pp_temp.fis 77 | end_command 78 | end 79 | 80 | def ball_id_head 81 | if ball_head = null 82 | ball_id_head = -1 83 | else 84 | ball_id_head = b_id(ball_head) 85 | endif 86 | end 87 | 88 | def ball_id_next 89 | if b_next(find_ball(current_id)) = null 90 | ball_id_next = -1 91 | else 92 | ball_id_next = b_id(b_next(find_ball(current_id))) 93 | endif 94 | end 95 | 96 | 97 | def write_ball_positions 98 | status = open('bin.fish', 1, 0) ; binary output 99 | bp = ball_head 100 | loop while bp # null 101 | tmp(1) = b_x(bp) 102 | status = write(tmp,1) 103 | tmp(1) = b_y(bp) 104 | status = write(tmp,1) 105 | tmp(1) = b_z(bp) 106 | status = write(tmp,1) 107 | bp = b_next(bp) 108 | end_loop 109 | status = close 110 | end 111 | 112 | def write_ball_velocities 113 | status = open('bin.fish', 1, 0) ; binary output 114 | bp = ball_head 115 | loop while bp # null 116 | tmp(1) = b_xvel(bp) 117 | status = write(tmp,1) 118 | tmp(1) = b_yvel(bp) 119 | status = write(tmp,1) 120 | tmp(1) = b_zvel(bp) 121 | status = write(tmp,1) 122 | bp = b_next(bp) 123 | end_loop 124 | status = close 125 | end 126 | 127 | def write_ball_radii 128 | status = open('bin.fish', 1, 0) ; binary output 129 | bp = ball_head 130 | loop while bp # null 131 | tmp(1) = b_rad(bp) 132 | status = write(tmp,1) 133 | bp = b_next(bp) 134 | end_loop 135 | status = close 136 | end 137 | 138 | def map_ret_val 139 | ;; input is the fish variable ret_value 140 | ;; if this is a string, int, float return it 141 | ;; if it is a pointer encode it 142 | 143 | ;; types: 1 int, 2 float, 3 string, 4 pointer, 5 array 144 | 145 | caseof type(ret_value) 146 | 147 | case 1 148 | map_ret_value = ret_value 149 | case 2 150 | map_ret_value = ret_value 151 | case 3 152 | map_ret_value = ret_value 153 | case 4 154 | if ret_value = null then 155 | map_ret_value = ':null:' 156 | else 157 | caseof pointer_type(ret_value) 158 | ;; pointer types: 159 | ;; 21 memory, 100 ball, 101 wall, 102 contact, 103 clump, 160 | ;; 104 measuerment sphere, 0 null 161 | case 21 162 | oo=error('memory pointers are unsuported in python bridge') 163 | case 100 164 | oo=out('ball type return') 165 | map_ret_value = ':ball: ' + string(b_id(ret_value)) 166 | case 101 167 | map_ret_value = ':wall: ' + string(w_id(ret_value)) 168 | case 102 169 | current_contact = ret_value ; hack because contacts have no id 170 | map_ret_value = ':contact: 0' 171 | case 103 172 | map_ret_value = ':clump: ' + string(cl_id(ret_value)) 173 | case 104 174 | map_ret_value = ':meas: ' + string(m_id(ret_value)) 175 | endcase 176 | endif 177 | case 5 178 | oo=error('arrays not supported in python bridge') 179 | endcase 180 | end 181 | 182 | def open_socket 183 | s = sopen(0,0) 184 | 185 | loop i(0, 1000) 186 | oo = out('reading') 187 | oo = sread(data_in, 1, 0) 188 | oo = out('got ' + string (data_in(1)) + ' from python server') 189 | data_out(1) = 0 190 | 191 | if type(data_in(1)) = 1 then 192 | 193 | if data_in(1)=-1 then 194 | oo=out('breaking out of server loop') 195 | exit 196 | endif 197 | 198 | if data_in(1)=-2 then 199 | oo=out('exiting PFC') 200 | command 201 | quit 202 | end_command 203 | endif 204 | 205 | if data_in(1)=10 then 206 | oo = sread(tmp, 1,0) 207 | scommand = tmp(1) 208 | execute_command 209 | endif 210 | 211 | if data_in(1)=11 then 212 | oo = out('in 11') 213 | oo = sread(tmp, 1,0) 214 | oo = out('in 11 part 2') 215 | fstring = tmp(1) 216 | oo = out('got ' + fstring + ' from python server') 217 | evalate_string 218 | oo=out('eval done') 219 | map_ret_val 220 | data_out(1) = map_ret_value 221 | endif 222 | 223 | if data_in(1)=12 then 224 | oo = sread(tmp, 1,0) 225 | fstring = tmp(1) 226 | oo = out('got ' + fstring + ' from python server') 227 | evalate_string_assign 228 | data_out(1) = 0 229 | endif 230 | 231 | else 232 | oo=error("unknown input to PFC/python bridge server") 233 | endif 234 | 235 | oo=out('returning ' + string(data_out(1)) + ' to python') 236 | oo=swrite(data_out, 1, 0) 237 | 238 | end_loop 239 | end 240 | open_socket 241 | 242 | def close_socket 243 | oo=sclose(1) 244 | oo=out('closed socket connection') 245 | end 246 | close_socket 247 | -------------------------------------------------------------------------------- /tests/pfc_bridge_test.py: -------------------------------------------------------------------------------- 1 | # This is a demonstration of a high level interface to PFC3D 4.0. 2 | 3 | # to run this example: 4 | 5 | # (i) make sure the file pfc_bridge_server.p3dat is in the same folder 6 | # as this file. 7 | 8 | # (ii) Run this file with Python: python pfc_bridge_test.py 9 | 10 | # (iii) PFC3D will be started, click the green arrow to run the 11 | # pfc_bridge_server.p3dat file. This puts PFC into server mode where 12 | # it will respond to input from Python. 13 | 14 | from itasca import pfcBridge 15 | import math 16 | 17 | pfc = pfcBridge() 18 | 19 | res = pfc.eval("1+1") 20 | assert res == 2 21 | 22 | res = pfc.eval("cos(1+2.2)") 23 | assert res == math.cos(1+2.2) 24 | 25 | res = pfc.eval("a=123.456") 26 | assert res == 0 27 | 28 | res = pfc.eval("a") 29 | assert res == 123.456 30 | 31 | pfc.cmd("ball id 1 rad 1 x 12.30 y .2 z 0") 32 | pfc.cmd("ball id 2 rad 1 x 12.30 y .4 z 3") 33 | pfc.cmd("ball id 3 rad 1 x 12.30 y .5 z 6") 34 | pfc.cmd("prop dens 2500 kn 1.0e3 ks 1.0e3") 35 | pfc.cmd("set grav 0 0 -9.81") 36 | pfc.cmd("measure id 1 x 0.122 y 0.4 z -0.3 rad 0.0225") 37 | pfc.cmd("wall face 0 0 0 1 1 1 2 0 0 fric 0.25") 38 | 39 | for ball in pfc.ball_list(): 40 | print ball.x(), ball.y(), ball.z() 41 | 42 | print pfc.time() 43 | print pfc.ball_head() 44 | print pfc.ball_near3(0,0,0) 45 | 46 | b = pfc.ball_head() 47 | b.rad(99.9) 48 | assert b.rad() == 99.9 49 | b = b.next() 50 | print b.rad() 51 | 52 | w = pfc.wall_head() 53 | print w, w.fric() 54 | w.fric(0.112) 55 | assert w.fric() == 0.112 56 | 57 | meas = pfc.circ_head() 58 | print meas, meas.x() 59 | meas.x(12.55) 60 | assert meas.x() == 12.55 61 | 62 | # ball iterator 63 | for ball in pfc.ball_list(): 64 | print ball.x(), ball.y(), ball.z() 65 | ball.rad(0.123) 66 | 67 | # array interface 68 | print pfc.ball_positions() 69 | print pfc.ball_velocities() 70 | 71 | 72 | #pfc.quit() 73 | -------------------------------------------------------------------------------- /tests/test_fish_file_read.py: -------------------------------------------------------------------------------- 1 | import struct 2 | from itasca import FishBinaryReader 3 | 4 | # see create-test-data.f3dat to generate this test data 5 | fish_file = FishBinaryReader("testdata.fish") 6 | 7 | assert fish_file.read() == 1 8 | assert fish_file.read() == 99.987 9 | assert fish_file.read() == "James" 10 | assert fish_file.read() == [99.5, 89.3] 11 | assert fish_file.read() == [7.0, 8.0, 9.0] 12 | print("FISH read test passed") 13 | -------------------------------------------------------------------------------- /tests/test_flac3d_connection.py: -------------------------------------------------------------------------------- 1 | from itasca import FLAC3D_Connection 2 | 3 | flac3d = FLAC3D_Connection() 4 | flac3d.start("flac3d_socket_test.f3dat") 5 | flac3d.connect() 6 | 7 | flac3d.send(99.9) 8 | flac3d.send([1,2]) 9 | flac3d.send([1,2,3]) 10 | flac3d.send("James") 11 | flac3d.send({1:2, 2:3, "john":"doe"}) 12 | 13 | for i in range(10): 14 | print "sending", i, "...", 15 | flac3d.send(i) 16 | print "sent", i 17 | value = flac3d.receive() 18 | print "got", value, "from FLAC3D" 19 | 20 | flac3d.send(-1) 21 | flac3d.end() 22 | -------------------------------------------------------------------------------- /tests/test_flac_connection.py: -------------------------------------------------------------------------------- 1 | from itasca import FLAC_Connection 2 | 3 | flac = FLAC_Connection() 4 | flac.connect() 5 | 6 | for i in range(10): 7 | print "sending", i 8 | flac.send(i) 9 | value = flac.receive() 10 | print "got", value, "from FLAC" 11 | 12 | flac.send(-1) 13 | flac.end() 14 | -------------------------------------------------------------------------------- /tests/test_three_dec_connection.py: -------------------------------------------------------------------------------- 1 | 2 | from itasca import threeDEC_Connection 3 | threeDEC = threeDEC_Connection() 4 | threeDEC.start("./three_dec_socket_test.dat") 5 | threeDEC.connect() 6 | 7 | threeDEC.send(99.9) 8 | threeDEC.send([1,2]) 9 | threeDEC.send([1,2,3]) 10 | threeDEC.send("James") 11 | 12 | for i in range(10): 13 | print "sending", i, "...", 14 | threeDEC.send(i) 15 | print "sent", i 16 | value = threeDEC.receive() 17 | print "got", value, "from 3DEC" 18 | 19 | threeDEC.send(-1) 20 | threeDEC.end() 21 | -------------------------------------------------------------------------------- /tests/test_udec_connection.py: -------------------------------------------------------------------------------- 1 | from itasca import UDEC_Connection 2 | 3 | udec = UDEC_Connection() 4 | udec.connect() 5 | 6 | for i in range(10): 7 | print "sending", i 8 | udec.send(i) 9 | value = udec.receive() 10 | print "got", value, "from UDEC" 11 | 12 | udec.send(-1) 13 | udec.end() 14 | -------------------------------------------------------------------------------- /tests/testdata.fish: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itascaconsulting/itasca-python/91c766abc64e4940605b454ef96ad694926b61d7/tests/testdata.fish -------------------------------------------------------------------------------- /tests/three_dec_socket_test.dat: -------------------------------------------------------------------------------- 1 | ;; This is the 3DECFLAC or UDEC side of the Python coupling example. 2 | 3 | ;; This example demonstrates low level reading and writing of fish 4 | ;; parameters (int, float or string) across a socket. 5 | 6 | ;; To run this example: 7 | 8 | ;; The open_socket FISH function opens a connection to the 9 | ;; Python program. FLAC then waits for the Python program to write a 10 | ;; FISH parameter. 1 is added to the value and it is sent back to the 11 | ;; Python program. When 3DEC receives a value of -1 from Python it 12 | ;; exits the read/write loop. 13 | 14 | new 15 | 16 | def open_socket 17 | array data(1) 18 | s = sopen(0,0) 19 | loop i(0, 1000) 20 | oo = sread(data, 1, 0) 21 | oo = out('got ' + string(data(1)) + ' from python server') 22 | if type(data(1)) = 1 then 23 | if data(1)=-1 then 24 | oo=out('breaking out of read/write loop') 25 | exit 26 | endif 27 | data(1) = data(1) + 1 28 | endif 29 | oo=swrite(data, 1, 0) 30 | end_loop 31 | end 32 | @open_socket 33 | 34 | def close_socket 35 | oo=sclose(0) 36 | oo=out('closed socket connection') 37 | end 38 | @close_socket 39 | --------------------------------------------------------------------------------