You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 11 Next »

Main purpose of FC2K is to embed native code inside Kepler workflow. This way, you are able to connect various, physics based, calculations into chain of execution. There are few rules you have to follow while embedding native code inside Kepler:

  • your routine signature must be exactly the same as one suggested by FC2K
  • in order to access IDSes, you have to use IMAS based modules inside your Fortran code
  • you can pass data into and outside of the code in the form of:
    • primitive types
    • IDSes
    • code parameters
  • you should avoid accessing external files unless it's really impossible to run the simulation without external source of data
  • all physics related information should be exchanged only via IDSes

In this tutorial, I will show show you how to build simple native code and plug it inside Kepler using FC2K.

Sample, shown below, will access IDS data and print it on console.

source code

Source code for this sample can be found at following location

/afs/gw/swimas/resources/tutorials/2019-10-PSNC/FC2K/simple_IMAS_code 

Note that workflow is running such way it reads data from pulse file of user g2michal - it means you don't have to copy input data into your public/imasdb  folder.

Make sure to initialise your environment. Follow the steps here: 00. Initial setup or here: 05.3. IMAS - basic topics - environment set-up(POZ'19Oct)


Make sure to initialise west database - it will be used for temporary files

> imasdb west

1. Preparing the code

First of all, we need simple code that will read data. In this sample, we will use very simple code that reads distribution IDS, shows some info, and output scalar value

IDS -> actor code [we treat the actor as a black box] -> integer

When you talk about actor, most important is the interface. It's API defines how actor interacts with other actors in the workflow.

1.1. What interface should I provide

You can always start with FC2K in order to make sure how native interface should be implemented like.

Let's start with running FC2K. It's little bit counterintuitive, but will help us to properly prepare the code.

First of all, let's add two ports into actor. Input port - IDS, and output port - integer.

Now, let's take a look how the native code should look like in order to provide correct implementation of the actor's API.

Now, once we know how our interface looks like, we can start developing the code

1.2. Source code of the actor

In our case, we can start with providing very basic implementation

subroutine distributiondisp(distributionin, output)

  use ids_schemas

  implicit none

  type (ids_distribution_sources) :: distributionin
  integer, intent(out)            :: output

  output = 1

  return

end subroutine

As you can see, we don't even access input data, yet. All we have, so far, is the API of the native code.

1.3. Creating library with native code

As you recall from previous session (dedicated to FC2K) we have to have library that contains native code we are supposed to run.

Let's prepare it. We will do it, by creating Makefile  project - library will be called libdistribution_disp.a 

# In this sample I will use ifort
# However, depending on target system (where IMAS is installed)
# it might be you have other options as well - e.g.: gfortran, pgi, NAG, etc.

F90      = ifort
COPTS    = -g -O0 -assume no2underscore -fPIC -shared-intel

# Note that you should _always_ use pkg-config to obtain
# flags for compiler and linker
# do not pass hardcoded locations unless it's really unavoidable!

LIBS     = `pkg-config --libs imas-ifort imas-lowlevel`
INCLUDES = `pkg-config --cflags imas-ifort imas-lowlevel`

# It is little bit messy here, but we need two different targets
# for the purpose of the tutorial
# first  - builds very basic code that doesn't touch IDSes data
# second - builds the code that will read IDSed data
#

first: distribution_disp.o libdistribution_disp.a

second: distribution_disp_2.o libdistribution_disp_2.a

libdistribution_disp.a: distribution_disp.o
  ar -rvs libdistribution_disp.a $^

libdistribution_disp_2.a: distribution_disp_2.o
  ar -rvs libdistribution_disp.a $^

distribution_disp.o: distribution_disp.f90
  $(F90) $(COPTS) -c -o $@ $^ ${INCLUDES} $(LIBS)

distribution_disp_2.o: distribution_disp_2.f90
  $(F90) $(COPTS) -c -o $@ $^ ${INCLUDES} $(LIBS)

# try to provide _clean_ target, so you can easily
# cleanup source tree

clean:
  rm -f *.o *.a

Once we run make we can use library inside the project

> make first
ifort -g -O0 -assume no2underscore -fPIC -shared-intel -c -o distribution_disp.o distribution_disp.f90 `pkg-config --cflags imas-ifort imas-lowlevel` `pkg-config --libs imas-ifort imas-lowlevel`
ar -rvs libdistribution_disp.a distribution_disp.o
ar: creating libdistribution_disp.a
a - distribution_disp.o

1.4. Describing the actor

When we fill information regarding actor, we make it possible to create a bridge between Kepler (actor) and native code (in this case Fortran based library). I will fill the table following way

FieldValueDescription
ProjectIMASThis is the name of the project you will see later on inside Kepler - in the actor browser
NamedistributiondispThis is the name of the actor in Kepler. Once you place it on canvas this is what you will see.
Version1.0This is the version of actor - you are in charge of it. It is supposed to help people know what version of your actor they are using.
SubroutinedistributiondispThis is the name of routine in your native code (take a look at sources above)

Now, go ahed and take a look at Interface again. You can observe that now, interface is complete and perfectly matches your code (can you spot the difference?).

1.5. Choosing compiler

The only thing that is left is to set proper values for the compiler. Make sure we are using Fortran and the vendor is set to Intel

We have all we need to build our first, IMAS based, actor. Press Generate.

1.6. Running the actor

Now it's time to put all the things together. Start your Kepler and create following workflow.

Now we can run it.

As you can see, the result is exactly the same as we have expected. Now, let's try to get some data from the IDS.

1.7. Getting value from the IDS and passing it back to Kepler

Now, I will change my code a little bit to retrieve some data from the IDS and pass it to the workflow.

First of all, let's take a look at the data we are working with

> idsdump g2michal west 3 92436 1 distribution_sources
class distribution_sources
Attribute ids_properties
 	class ids_properties
	Attribute comment:
	Attribute homogeneous_time: 1
	Attribute source:
	Attribute provider:
	Attribute creation_date:
	Attribute version_put
 		class version_put
		Attribute data_dictionary: 3.24.0
		Attribute access_layer: 4.2.0
		Attribute access_layer_language: fortran
Attribute source
 	class source
Attribute vacuum_toroidal_field
 	class vacuum_toroidal_field
	Attribute r0: -9e+40
	Attribute r0_error_upper: -9e+40
	Attribute r0_error_lower: -9e+40
	Attribute b0
	[]
	Attribute b0_error_upper
	[]
	Attribute b0_error_lower
	[]
Attribute magnetic_axis
 	class magnetic_axis
	Attribute r
	[]
	Attribute r_error_upper
	[]
	Attribute r_error_lower
	[]
	Attribute z
	[]
	Attribute z_error_upper
	[]
	Attribute z_error_lower
	[]
Attribute code
 	class code
	Attribute name: coreprofiles2distsourcesF
	Attribute commit:
	Attribute version: 1.1
	Attribute repository:
	Attribute parameters: my_code_specific_parameters
	Attribute output_flag
	[]
Attribute time
[49.00500107 49.02500153 49.04499817 49.06499863 49.08499908 49.10499954
 49.125      49.14500046 49.16500092 49.18500137 49.20500183 49.22499847
 49.24499893 49.26499939 49.28499985 49.30500031 49.32500076 49.34500122
 49.36499786 49.38500214 49.40499878 49.42499924 49.44499969 49.46500015
 49.48500061 49.50500107 49.52500153 49.54499817 49.56499863 49.58499908
 49.60499954 49.625      49.64500046 49.66500092 49.68500137 49.70500183
 49.72499847 49.74499893 49.76499939 49.78499985 49.80500031 49.82500076
 49.84500122 49.86499786 49.88500214 49.90499878 49.92499924 49.94499969
 49.96500015 49.98500061 50.00500107 50.02500153 50.04499817 50.06499863
 50.08499908 50.10499954 50.125      50.14500046 50.16500092 50.18500137
 50.20500183 50.22499847 50.24499893 50.26499939 50.28499985 50.30500031
 50.32500076 50.34500122 50.36499786 50.38500214 50.40499878 50.42499924
 50.44499969 50.46500015 50.48500061 50.50500107 50.52500153 50.54499817
 50.56499863 50.58499908 50.60499954 50.625      50.64500046 50.66500092
 50.68500137 50.70500183 50.72499847 50.74499893 50.76499939 50.78499985
 50.80500031 50.82500076 50.84500122 50.86499786 50.88500214 50.90499878
 50.92499924 50.94499969 50.96500015 50.98500061 51.

Let's say we will retrieve the first time index (as integer) from the array of all times. Remember, this is just an exercise where we want to make all things work together. Don't pay attention to whether it's something meaningful or not. Let's say our actor is supposed to retrieve first time index in the IDS.

We need the code that will perform this action. Let's change our source code a little bit.

subroutine distributiondisp(distributionin, output)
  use ids_schemas

  implicit none

  type (ids_distribution_sources) :: distributionin
  integer, intent(out)            :: output

  output = int( distributionin%time(1) )  ! we are reading data from the IDS and return it back to Kepler

  return
end subroutine

Once we compile the code and regenerate actor again

> make clean
> make second
> fc2k distribution_disp_fc2k.xml
> kepler

we can run workflow again. This time, the value is coming from the IDS.




  • No labels