-
Notifications
You must be signed in to change notification settings - Fork 1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Adding BMI to Fortran77 code #104
Comments
Hi @lzhu5,
Also, be sure to look at the documentation and use the latest Fortran BMI specification! |
Hi @mdpiper, Thanks for your reply. For instance, eventually, I want to couple "heat.f" with a python code. I guess I need to wrap "bmi_heat.f90" using babelizer with Python bindings, right? Another concern is that my Fortran 77 code uses common blocks for global variables, and subroutines use common variables. Please see below a brief structure of the Fortran 77 code:
After coupling with the python code, I want to receive values of c & d from the python code, and call subroutines A() and B() to do computation at each time step. My understanding is that the initialize/compute/finalize in Fortran code will be subroutines and be wrapped into a python library that I can call directly in python. If not using module, how to share global variables among initialize/compute/finalize subroutines? Would you please suggest? Below is the flow that I want to achieve. (for time loop, repeat Step 2-4) Step 5 finalize Thanks for your help. |
Hi Ling, I've been thinking about this problem this weekend. It's still a Fortran problem, not a BMI problem. We need to find a way to call F77 code from F03. A module would be nice, but shouldn't be necessary. Also, the common block won't be a problem for the BMI. (The only issue will be that only one instance of the model can be running at a time; otherwise, the common blocks will conflict.) Mark |
Another option, if you have permission to modify the model source code, would be to upgrade it from F77 to F90. The advantage of this is there's a clear path from F90 through BMI to pymt and Python. |
Hi Mark, Thanks for your reply. Yes. We are able to adapt F77 code to F90 code. Could you please point me to a full F90 example similar to example-heat-c? (I have already tested wrapping GIPL model from the repository with babelizer, but the final 'make install' step for building the Python bindings cannot go through.) Thanks for your help again. Best, |
Hi Ling, Just to be careful, what I recommend is to update your F77 model code to at least F90, modularizing it and removing common blocks. This will make creating a BMI for it much easier. You should base your BMI on the current Fortran specification in bmi-fortran, using bmi-example-fortran as a guide. Writing the BMI will take some time, and it can be tricky. The babelizer uses the current Fortran BMI specification--that's why it didn't work for GIPL, which uses an earlier BMI version, v1.2. Mark |
Hi Mark, I have a question about the bmi-example-fortran. I tried to set the value of a variable in type :: heat_model via BMI's set_value() and found that the value would change after calling the "initialize" function. For instance, Also, valgrind detected data loss for bmi-example-fortran. I am wondering whether the data leak is related to the value change. Would you please shed light on this issue? Thanks for your help. The modified code can be found here. Best, |
Hi Ling, This is by design--model variables that are exposed through the BMI (we sometimes call them "exchange items") can't be get/set before calling the initialize method. Thank you for the tip about running Mark |
Hi Mark, Thank you for explaining the order of calling BMI functions. I guess this order is enforced by "intent(out) :: this” in heat_initialize function in bmi_heat.f90? In our F77 model’s own initialization subroutine, the input file read and disk writes are blended together. We intend to enable an optional disk write turn-on and keep F77 model’s own initialization subroutine unchanged. That is why we need to set the value of a logical parameter, which controls the disk writes, before calling F77 model’s own initialization subroutine. We figured out a workaround to this issue. Please advise whether our method is compatible with the babelizer and PyMT users’ coding conventions. Our workaround (use bmi-fortran-example as an example) (1) In “type :: heat_model” (in heat.f90), add variables: “enable_write”, “model_initialized”, and “config_file_path”. In short, we still call BMI’s initialize() function as the first step, but we do actual initialization in BMI’s update() or update_until() function. This modified bmi_heat model can be used as below: bmi_model%initialize() Would this modified bmi_heat model be compatible with the babelizer? BTW, we updated our compiler to gcc 10.2.0, and valgrind stopped complaining about the data loss. Thanks again for your help. Best, |
Hi Ling, Yes, your solution should work with the CSDMS Workbench. I've seen other models with multi-step initialization, and we even have an open issue to determine a better way to address it. I wonder if it might be easier to pass in these parameter values through the configuration file? This is just a thought, since you already have a working solution. It would still require some refactoring, but perhaps less than what you have done. Thank you for the update about the possible data loss. I need to include a check for this in the continuous integration. Mark |
Hi Mark, Thank you for your suggestion. I have two more questions:
Thanks. Best, |
Hi Ling, The BMI functions return zero for success and nonzero for failure, so you can use different status codes for different errors. This isn't really clear in the documentation, so I'll add language to improve it. The status code isn't available in Python through pymt--any nonzero (not success) value gets swallowed by the babelizer. The way we handle errors from wrapped libraries is something that could be improved. If you have suggestions from your work, please feel free to create issues in the BMI repo or the BMI Fortran repo. By definition, all BMI functions have to be implemented. However, not all of them are always used. For example, in bmi-example-fortran, the Heat model is solved on a uniform rectilinear grid, so grid functions like get_grid_face_count aren't used. In this case, you can just set them to fail. For example: ! Get the number of faces in an unstructured grid.
function heat_grid_face_count(this, grid, count) result(bmi_status)
class(bmi_heat), intent(in) :: this
integer, intent(in) :: grid
integer, intent(out) :: count
integer :: bmi_status
count = -1
bmi_status = BMI_FAILURE
end function heat_grid_face_count Thank you for your questions, Ling. You're helping me think about BMI, and how to improve the documentation and testing. Mark |
Hi Ling, I made a mistake. The nonzero status code is returned through a RuntimeError in pymt. Here's the relevant code in the babelizer: def ok_or_raise(status):
if status != 0:
raise RuntimeError('error code {status}'.format(status=status)) Mark |
Hi Mark, Thanks for your reply. It is very helpful. I want to elaborate on my second question. I actually want to delete un-used functions from bmi.f90 so that I don't need to implement those functions in bmi_heat.f90. Does babelizer support fortran libraries using the modified bmi.f90? Thanks. Ling |
Hi Ling, No—by the definition of an interface, you can't leave out or remove functions from it. Mark |
Hi Mark, Thanks again for your help. I successfully converted my model to bmi, tested the bmi-model through bmi_main.f90, and built Python bindings for the model through babelizer. However, I encountered an error when I tried to import my model in Python. The error message says: Many posts online say the error is caused by insufficient memory. However, my machine has 40G memory. Would you please provide some suggestions? Thanks. Best, |
Hi Ling, Congratulations on adding a BMI to your model and babelizing it! These aren't easy tasks, so it's great that you've accomplished them. It's interesting that the exception is raised on import, and not on instantiation or when initialize is called. When are you allocating arrays for your model, and how large are they? Arrays occupy contiguous memory on the system, and depending on what other processes are running, Python won't have access to the full 40 GB of memory. Mark |
Hi Mark, Thanks for your reply. Your help and time are greatly appreciated. We strictly follow the structure of "bmi-fortran-example" to implement the BMI in our Fortran program. Still use "bmi-fortran-example" as an example. We put all global variables (i.e., variables in common blocks) in "heat_model" module in heat.f90. The "heat_model" is instantiated within bmi_heat. All global arrays are statically declared. There are around 50 two-dimensional arrays with the size of 5000*100. There are other arrays with relatively smaller sizes. If we run "bmi_heat" through a Fortran main program (bmi_main.f90), the program uses 388MB of memory and Valgrinds reports no errors. Would you please suggest how to make Python access more memory? Thanks. Best, |
@lzhu5 I would like to try to reproduce this error. Could you please point me to the code so I can try to install it on my machine and see if I get the same error? Could you also verify that if you simply open python and try to allocate that same amount of memory, you can do so without a problem? So maybe something like, >>> import numpy as np
>>> x = np.array((50, 5000, 100)) |
@mcflugen Please find the code here. The code was downloaded from bmi_example_fortran. I made the following changes to "heat.f90":
With NL=100 (Line 6 in heat.f90), I encountered ImportError (ImportError: ~/BMI/build_bmi_fortran_heat/pymt_heat/pymt_heat/lib/heatmodel.cpython-39-x86_64-linux-gnu.so: cannot map zero-fill pages: Cannot allocate memory) while loading the babelized "Heatmodel" in Python. After changing NL to 30, I could successfully import "Heatmodel". Yes. I could do x = np.array((50, 5000, 100)) in Python without any problem. Best, |
@lzhu5 Thank you! I was able to download your code, build it, and import it into Python without a problem. I'm on a Mac. It looks like you're also on a Mac, is that right? Are these more or less the steps you followed? From what you say, it sounds like your error is occurring after you run First, I built and installed the bmi-fortran-heat example: $ cd build_bmi_fortran_heat
$ conda create -n heat python fortran-compiler cmake bmi-fortran babelizer -c conda-forge
$ conda activate heat
$ cd bmi-example-fortran
$ mkdir _build && cd _build
$ export BMIF_VERSION=2.0
$ cmake ../ -DCMAKE_INSTALL_PREFIX=$CONDA_PREFIX
$ make all install Second, I babelized the library, $ cd build_bmi_fortran_heat
$ babelize init babel_heat.toml
$ cd pymt_heat
$ pip install -e . I was then able to import your wrapped model in Python, >>> from pymt_heat import Heatmodel
>>> heat = Heatmodel()
>>> heat.get_component_name()
'The 2D Heat Equation' |
@mcflugen Yes. I followed these steps to build and import the code. I ran the model on HPC (centOS). The ImportError seems like an HPC-related issue now. I will reach out to the HPC help desk for their advice on the memory issue. Thanks again. Best, |
@mcflugen I still cannot resolve the ImportError. I tried on a local Linux OS (ubuntu 18.04) and two different HPC systems (using centOS and RedHat Enterprise Linux, respectively). I got the same ImportError. I started from scratch (i.e., installing anaconda3) in all tries. The code I tested is still the version that works on your Mac, and I followed exactly the steps you listed in your previous reply. I am wondering would you please provide suggestions? Thanks a lot. Best, |
@lzhu5 Following @mcflugen's instructions above, I was able to build your example in a Docker container based on the condaforge/miniforge3 image (which is built on Debian Linux), then run it in Python. I'll set up a Dockerfile and post an image on Docker Hub so that you can try it locally. I was surprised that this worked because I think I've spotted a problem in the Fortran template code I wrote for the Babelizer. I'll keep digging to try to reproduce the error you're seeing. |
@mdpiper I am a bit confused. Do you mean that you don't have the "ImportError" in the example built in the Docker container? |
@lzhu5 Yes, that's correct. I still need to build and post an image, but I should be able to finish that tomorrow. |
I think I've got it. As I mentioned above, I was able to build, install, and run the modified Fortran BMI example through Docker using a Debian Linux base image on my iMac. A Dockerfile and everything needed to reproduce this is in this repo. The README has build and run instructions. I tried the same on a test Linux machine I have. I was able to build the Docker image, but running it threw a familiar error:
(I don't know lots about Docker, but it's interesting that a Docker Linux image would run on macOS, but fail on Linux.) This is where I'd thought I'd try the hunch I mentioned earlier. I modified the Babelizer, removing the ability to run multiple instances of the same Fortran model simultaneously (this feature works fine for dynamically allocated variables in a model, but not for static variables). In a branch of my Docker repo, I built this version of the Babelizer before using it on the modified Fortran BMI example. Building and running the Docker image now works on both my iMac and my Linux machine:
If you'd like, what you can do is follow my steps to build the Babelizer locally from the What I'll do is add a new option to the Babelizer |
I'm adding BMI to a model written in Fortran 77 and encountered the following issues so far:
Do you have an example for Fortran 77?
How does BMI's Fortran 77 compatibility help make the code change minimal?
Thanks.
The text was updated successfully, but these errors were encountered: