C2VSIM single processor - giorgk/ichnos GitHub Wiki

The following guide shows how to prepare the files for a steady and transient state simulation. Since this is only a demo for the transient simulation we will use a very small domain. Note also to make things less complicated we assume that the groundwater aquifer is bounded on the top by the ground surface. This is obviously not true and one should consider a steady state representation of the water table instead for top.

In addition, the following shows the input file preparation when a single processor is used. For multi processor simulations check this page

Setup C2VSim simulation

The C2VSim model can be downloaded from the DWR repository. The download includes only the inputs and one need to run the model to create the outputs.

By default, the velocity is not printed. To print the velocity field you need to set a filename for the VELOUTFL parameter of the main Groundwater input file. (For version 1 and 1.01 the name of this file is C2VSimFG_Groundwater1974.dat). For this tutorial we assume that we set the name of the velocity output as C2VSimFG_GW_VELOUTFL.out.

There are also two parameters that modify how the velocity is printed. The FACTVROU is a multiplier to the velocity that was set by default to 0.000022957. This has to be changed to FACTVROU=1000.0. The second parameter UNITVROU it is used only for displaying the correct units and does not affect the numeric values. Since we change the FACTVROU=1000.0 for consistency reasons set UNITVROU = 10^3FT/MON.

You may notice that the default value was AC-FT/MON e.g. L^3/T but we changed to a unit that is L/T. After personal communication I confirmed that the velocity is printed in L/T not in L^3/T. If we set the FACTVROU = 1 then the velocity is printed in FT/MON. However, there are parts of the flow domain where the velocity is very low and the printing accuracy does not allow to capture these small ranges. For that reason we set the FACTVROU = 1000 which increases the printed accuracy. If this value is increased to a very large multiplier value e.g. 10^6 then a few of the velocity numbers become very large which exceed the allowable printing width.

Before getting into the input files of Ichnos there are two output data from C2VSim that are time-consuming to read. These are the Groundwater heads and the Velocity file. It is recommended to read those in advance and save them as Matlab variables which makes the loading significantly faster.

Set up the paths

c2vsim_path = fullfile('path','to','C2VsimV1','c2vsim-working');
input_files_path = fullfile('path','to','input','files');

Read the data

Heads = readIWFM_headalloutput(fullfile(c2vsim_path,'Results',"C2VSimFG_GW_HeadAll.out"), size(ND,1), 4, 505, 1);
VelOut = readIWFM_Velocity(fullfile(c2vsim_path,'Results','C2VSimFG_VETOUTFL.out'), 32537, 4, 1000);

Save the data

save(fullfile(c2vsim_path,'GW_Heads.mat'), Heads);
save(fullfile(c2vsim_path,'GW_Velocity.mat'), VelOut);

Domain configuration

Domain outline

For the domain files we have converted the nodes and elements files to a polygon shapefile. In a GIS software, the elements shapefile was dissolved to a single polygon that is the outline of the domain. It has also been re-projected to 3310.

First we read the shapefile and split the polygon because the outline consists of several rings.

c2vsim_outline = shaperead('C2VsimMesh_Outline_3310.shp'); 
[Xs, Ys] = polysplit(c2vsim_outline.X, c2vsim_outline.Y);

Next we write the outline to the file after each subpolygon has been simplified. The simplify threshold requires some experimentation.

simplify_threshold = 1000;
fid = fopen(fullfile(input_files_path, 'c2vsimFG_outline.ich') ,'w');
for ii = 1:length(Xs)
    [xx,yy] = reducem(Xs{ii,1}', Ys{ii,1}', simplify_threshold);
    fprintf(fid, '%d %d\n',[length(xx) ispolycw(xx,yy)]);
    fprintf(fid, '%.3f %.3f\n', [xx yy]');
end
fclose(fid);

Top - Bottom function

Since the top and bottom elevations are both defined on the same XY nodes we will write one file containing both elevations.

In the following snippets we use functions from gwtools

Read the Nodes where the elevation is set.

ND = readIWFM_Nodes(fullfile(c2vsim_path,'Preprocessor','C2VSimFG_Nodes.dat'));

The coordinates of the original file are in the EPSG:26910. Here we convert them to EPSG:3310.

[lat,lon] = projinv(projcrs(26910),ND(:,1), ND(:,2));
[X_3310, Y_3310] = projfwd(projcrs(3310),lat, lon);

Read the Elevation data. Note that the elevation data are in feet, so we convert them to meters

Strat = readIWFM_Stratigraphy(fullfile(c2vsim_path,'Preprocessor','C2VSimFG_Stratigraphy.dat'), size(ND,1), 4, 105);
TopElev = Strat(:,2) * 0.3048; 
Bottom_elev = (Strat(:,2) - sum(Strat(:,3:end),2)) * 0.3048;

Write both top and bottom elevation data in one file

fid = fopen(fullfile(input_files_path,'c2vsim_TopBot_CLOUD.ich'),'w');
fprintf(fid, 'CLOUD\n');
fprintf(fid, '%.2f %.2f\n', [4000, 3]);
fprintf(fid, '%.3f %.3f %.4f %.4f\n', [X_3310 Y_3310 TopElev Bottom_elev]');
fclose(fid);

Processors polygons

Processor polygons are not needed for single processing simulation

Velocity field configuration

To read the velocity output file, we use one of the Matlab functions of gwtools. Note that the last argument of the function is the FACTVROU multiplier. The function will read and divide the velocity to convert it to FT/MON.

VelOut = readIWFM_Velocity(fullfile(c2vsim_path,'Results','C2VSimFG_GW_VELOUTFL.out'), 32537, 4, 1000);

The output is a structure that contains the coordinates of the 2D element barycenters where the velocity is defined. However, the coordinates are in ft as well as in a different projection system.

[lat,lon] = projinv(projcrs(26910),VelOut.ND(:,1)*0.3048, VelOut.ND(:,2)*0.3048);
[BCX_3310, BCY_3310] = projfwd(projcrs(3310),lat, lon);

Steady State flow field

First we will run a steady state particle tracking. We will average the velocities of the last decade of the simulation (10/2005 - 9/2015).

for ii = 1:size(VelOut.VX,3)
    Vx_av(:,ii) = mean(VelOut.VX(:,385:504,ii),2);
    Vy_av(:,ii) = mean(VelOut.VY(:,385:504,ii),2);
    Vz_av(:,ii) = mean(VelOut.VZ(:,385:504,ii),2);
end

Calculate the elevation of the velocity nodes.

First compute the elevations of the element mesh nodes

ND_ELEV(:,1) = Strat(:,2)*0.3048;
ND_ELEV(:,2) = ND_ELEV(:,1) - sum(Strat(:,3:4),2)*0.3048;
ND_ELEV(:,3) = ND_ELEV(:,2) - sum(Strat(:,5:6),2)*0.3048;
ND_ELEV(:,4) = ND_ELEV(:,3) - sum(Strat(:,7:8),2)*0.3048;
ND_ELEV(:,5) = ND_ELEV(:,4) - sum(Strat(:,9:10),2)*0.3048;

Then calculate the elevation of the element barycenters, the element diameter and the ratio between xy/z element extend and the average element thickness

msh = readIWFM_Elements(fullfile(c2vsim_path,'Preprocessor','C2VSimFG_Elements.dat'), 32537, 142);
for ii = 1:size(msh,1)
    nv = 4;
    if msh(ii,5) == 0
        nv = 3;
    end
    for j = 1:4
        t = mean(ND_ELEV(msh(ii,2:nv+1),j));
        b = mean(ND_ELEV(msh(ii,2:nv+1),j+1));
        MSH_ELEV(ii,j) = (t + b)/2;
        av_thick(ii,j) =  mean(ND_ELEV(msh(ii,2:nv+1),j) - ND_ELEV(msh(ii,2:nv+1),j+1));
    end
    DIAM(ii,1) = max(pdist([X_3310(msh(ii,2:nv+1)) Y_3310(msh(ii,2:nv+1))]));
end

Convert the velocity units from FT/MON to m/day.

Vx_av = Vx_av .* (0.3048/30.437);
Vy_av = Vy_av .* (0.3048/30.437);
Vz_av = Vz_av .* (0.3048/30.437);

The velocity values, especially the z components are very small e.g. 10^-5. Therefore, we will multiply the values with a very large constant multiplier. During particle tracking we have to reverse this by setting the multiplier equal to 1/mult.

Prepare and reshape the data for printing:

nvel_p = size(MSH_ELEV,1)*size(MSH_ELEV,2);
mult = 1000000;
VEL_DATA = [...
    repmat(BCX_3310,4,1) ... % X
    repmat(BCY_3310,4,1) ... % X
    reshape(MSH_ELEV, nvel_p, 1) ... % Z
    zeros(nvel_p,1) ... % Proc ID
    repmat(DIAM,4,1) ... % DIAM 
    reshape(bsxfun(@rdivide, DIAM, av_thick), nvel_p, 1) ... % RATIO
    reshape(Vx_av, nvel_p, 1)*mult ... % VX
    reshape(Vy_av, nvel_p, 1)*mult ... % VY
    reshape(Vz_av, nvel_p, 1)*mult ... % VZ
];

Last we can write the data as

  • ASCII
fid = fopen(fullfile(input_files_path,'c2vsim_VelCLOUD_SS_000.ich'),'w');
fprintf(fid, '%.3f %.3f %.3f %d %.2f %.2f %.5f %.5f %.5f\n', VEL_DATA');
fclose(fid);

Using ASCII file has the advantage that we can easily examine the printed files. Yet the loading times are quite large for very large velocity fields.

  • HDF5

HDF5 format reduces the loading times and we can print the data in an efficient HDF5 format as follows:

fname = fullfile(input_files_path,'c2vsim_VelCLOUD_SS_000.h5');
h5create(fname,'/XYZDR',[size(VEL_DATA,1) 5], 'Datatype','single');
h5write(fname, '/XYZDR', VEL_DATA(:,[1 2 3 5 6]));
h5create(fname,'/PROC',[size(VEL_DATA,1) 1], 'Datatype','uint32');
h5write(fname, '/PROC', VEL_DATA(:,4));
h5create(fname,'/VXYZ',[size(VEL_DATA,1) 3], 'Datatype','single');
h5write(fname, '/VXYZ', VEL_DATA(:,[7 8 9]));

Graph file

The following is optional but recommended. It will create a file that shows the connectivity of the cloud. Then every time step the algorithm will search for the nearest point and use only the connected points to the nearest point for interpolation.

Let's create the 2D connection graph of the mesh. For each element we will make a list of the elements that that is connected to

msh_v = msh(:,2:5); % Isolate the mesh vertices
clear msh_graph
msh_graph{size(msh,1),1} = [];
for ii = 1:size(msh,1)
    nd = msh_v(ii,:);
    nd(:, nd == 0) = [];
    con_elem = [];
    for jj = 1:length(nd)
        [II, JJ] = find(msh_v == nd(jj));
        con_elem = [con_elem;II];
    end
    con_elem = unique(con_elem);
    con_elem(con_elem == ii,:) = [];
    msh_graph{ii,1} = con_elem;
end

Each velocity node in the 3D cloud depends on the nodes of the elements of the same layer, the layer above and the layer below.

clear mesh3D_graph
nel = size(msh_graph,1);
mesh3D_graph{nel,4} = [];
for ii = 1:size(msh,1)
    for jj = 1:4
        if jj == 1
            mesh3D_graph{ii,jj} = [jj*nel+ii; (jj-1)*nel+msh_graph{ii}; jj*nel+msh_graph{ii}];
        elseif jj == 4
            mesh3D_graph{ii,jj} = [(jj-2)*nel+ii; (jj-2)*nel+msh_graph{ii}; (jj-1)*nel+msh_graph{ii}];
        else
            mesh3D_graph{ii,jj} = [(jj-2)*nel+ii; jj*nel+ii; ...
                (jj-2)*nel+msh_graph{ii}; (jj-1)*nel+msh_graph{ii}; jj*nel+msh_graph{ii}];
        end
    end
end
mesh3D_graph = reshape(mesh3D_graph, nvel_p, 1);

Print the file. (see here for details)


fid = fopen(fullfile(input_files_path, 'c2vsim_SS_05_15_000.grph'), 'w');
for ii = 1:length(mesh3D_graph)
    fprintf(fid, '%.2f %.2f %.2f', VEL_DATA(ii,1:3));
    fprintf(fid, ' %d %d', [length(mesh3D_graph{ii}) 1]);
    fprintf(fid, ' %d', [mesh3D_graph{ii}' ii]-1);
    fprintf(fid, '\n');
end
fclose(fid);

Initial particle positions

Particle file

Next we will generate a few particles to trace within the steady state field. Typically the locations of the particles depend on the study. For the purpose of this tutorial we will manually select 20 locations in x and y

plot(c2vsim_outline.X, c2vsim_outline.Y)
particles_locations = ginput(20);

Calculate the top elevation at these locations

Ftop = scatteredInterpolant(X_3310, Y_3310, TopElev, 'linear');
pTop = Ftop(particles_locations(:,1),particles_locations(:,2));

In each location we will release 4 particles with 10 m apart in the vertical direction. As you can see the particles are grouped in 4 entities with ids 10, 20, 30, 40. Within each entity the streamline ids span from 1-20.

particleData = [ ...
    10*ones(length(pTop),1) (1:length(pTop))' particles_locations pTop - 10];
particleData = [particleData; ...
    20*ones(length(pTop),1) (1:length(pTop))' particles_locations pTop - 20];
particleData = [particleData; ...
    30*ones(length(pTop),1) (1:length(pTop))' particles_locations pTop - 30];
particleData = [particleData; ...
    40*ones(length(pTop),1) (1:length(pTop))' particles_locations pTop - 40];

Print the particles to a file.

fid = fopen(fullfile(input_files_path,'CV_particles.ich'),'w');
fprintf(fid, '# Test particle file\n');
fprintf(fid, '#\n');
fprintf(fid, '%d %d %.3f %.3f %.3f\n', particleData');

Well file

In addition to single particles we will generate particles from hypothetical wells.

We will generate 3 wells with Eid {100, 200, 300} and top 10 m below the top function and 50 m screen length. Note that we specify the release time to 8022 days. This will be ignored in the steady state.

plot(c2vsim_outline.X, c2vsim_outline.Y)
well_locations = ginput(3);
wtop = Ftop(well_locations(:,1), well_locations(:,2));
well_data = [ (100:100:300)' well_locations wtop - 10 wtop - 60 8022*ones(length(wtop),1)];

Write the data to file. We will release 20 particles per well organized into 4 layers at a distance of 10 m from the x,y well location

fid = fopen(fullfile(input_files_path,'CV_wells.ich'),'w');
fprintf(fid, '# Test well file\n');
fprintf(fid, '#\n');
fprintf(fid, '# Cloud point tutorial\n');
fprintf(fid, '# Npart Nlay Rad\n');
fprintf(fid, '%d %d %.2f\n', [25 5 10.0]); 
fprintf(fid, '# Eid X Y T B RT\n');
fprintf(fid, '%d %.3f %.3f %.3f %.3f %.1f\n', well_data');
fclose(fid);

Run and post process

To run the code set up the two configuration files, the main configuration file c2vsim_CLOUD_SS_config.ini and the velocity configuration file c2vsim_CLOUD_SS_vf.ini.

For the step configuration we deactivate the StepSizeTime and the nStepsTime by setting them to 0 so that we can control the step size without taking into account the field velocity speed.

⚠️
Make sure that the output folder exists before running

Then run the code as:

ichnos.exe -c c2vsim_CLOUD_SS_config.ini

Alternatively you can run the simulation tracking using more processors. Make sure that the you set the options RunAsThread = 1 and nThreads = 1

C:\"Program Files\Microsoft MPI"\Bin\mpiexec.exe -n 4 ichnos.exe -c c2vsim_CLOUD_SS_config.ini

To read the results in matlab you can use one of the functions of gwtools

S = readICHNOStraj('CLOUD_SS_iter_0000.traj');

The following snippet plots the streamlines.

clf
hold on
for ii = 1:length(S)
    plot3(S(ii,1).p(:,1), S(ii,1).p(:,2), S(ii,1).p(:,3),'-')
end
plot(c2vsim_outline.X, c2vsim_outline.Y, 'linewidth',2)
axis equal
axis off
URFs for well 4

We run the code with 2 step configurations. One with StepSize = 50 and one with StepSize = 500

If we zoom into one of the wells we can see the cyan streamlines that correspond to 50 m step and the yellow streamlines(500 m). It is obvious that the particle trajectories with large Euler steps appear rather wiggly.

Streamlines around well

In the next figure we focus on one of the locations where we release 4 streamlines at different depths. The cyan and yellow streamlines correspond to Euler with very small and large step respectively. The purple and the red correponds to RK2 and RK4 using the same large step, while the blue is based on RK45. We can see that the RK45 results in similar streamlines as the very fine Euler

Streamlines around well

Transient state

In the next sections we will demonstrate how to prepare the input files for transient velocity fields. For the purpose of this demo we will use the flow field of the last decade of simulation.

Prepare velocity

Se above how to read the velocity field. The units of the velocity of the VelOut variable are FT/month and the following snippet converts them to m/day

days_per_month = eomday(VelOut.YMD(:,1), VelOut.YMD(:,2));
Vx_m_day = VelOut.VX;
Vy_m_day = VelOut.VY;
Vz_m_day = VelOut.VZ;
for ii = 1:length(days_per_month)
    Vx_m_day(:,ii,:) = 0.3048 .* Vx_m_day(:,ii,:) ./ days_per_month(ii);
    Vy_m_day(:,ii,:) = 0.3048 .* Vy_m_day(:,ii,:) ./ days_per_month(ii);
    Vz_m_day(:,ii,:) = 0.3048 .* Vz_m_day(:,ii,:) ./ days_per_month(ii);
end

In transient state the data are printed in multiple files. We refer to the file that contains the coordinates of the velocity points as the XYZ file. The following code puts all the data we need in a 2D array

nvel_p = size(MSH_ELEV,1)*size(MSH_ELEV,2);
XYZ_DATA = [...
    repmat(BCX_3310,4,1) ... % X
    repmat(BCY_3310,4,1) ... % X
    reshape(MSH_ELEV, nvel_p, 1) ... % Z
    zeros(nvel_p,1) ... % Proc ID
    repmat(DIAM,4,1) ... % DIAM 
    reshape(bsxfun(@rdivide, DIAM, av_thick), nvel_p, 1) ... % RATIO
];

We repeat a similar step for the velocities

VX_DATA = [];
VY_DATA = [];
VZ_DATA = [];
for ii = 1:size(Vx_m_day,3)
    VX_DATA = [VX_DATA; Vx_m_day(:,:,ii)];
    VY_DATA = [VY_DATA; Vy_m_day(:,:,ii)];
    VZ_DATA = [VZ_DATA; Vz_m_day(:,:,ii)];
end

Write the data of the last decade

  • ASCII format

Write the XYZ file

prefix = fullfile(input_files_path, 'c2vsim_VelCLOUD_TR_');
fid = fopen([prefix 'XYZ_000.ich'],'w');
fprintf(fid, '%.3f %.3f %.3f %d %.2f %.2f\n', XYZ_DATA');
fclose(fid);

and the velocity files

mult = 1000000;
itimes = 385:504;
frmt = ['%.5f' repmat(' %.5f',1,length(itimes)-1) '\n'];

% VX
fid = fopen([prefix 'VX_000.ich'],'w');
fprintf(fid, frmt, mult*VX_DATA(:,itimes)');
fclose(fid);
% VY
fid = fopen([prefix 'VY_000.ich'],'w');
fprintf(fid, frmt, mult*VY_DATA(:,itimes)');
fclose(fid);
% VZ
fid = fopen([prefix 'VZ_000.ich'],'w');
fprintf(fid, frmt, mult*VZ_DATA(:,itimes)');
fclose(fid);
  • HDF5 format For HDF5 format we write the data to a single file under different datasets as:
h5create([prefix '000.h5'],'/XYZDR',[size(XYZ_DATA,1) 5], 'Datatype','single');
h5write([prefix '000.h5'], '/XYZDR', XYZ_DATA(:,[1 2 3 5 6]));
h5create([prefix '000.h5'],'/PROC',[size(XYZ_DATA,1) 1], 'Datatype','uint32');
h5write([prefix '000.h5'], '/PROC', XYZ_DATA(:,4));
h5create([prefix '000.h5'],'/VX',[size(VX_DATA,1) size(VX_DATA,2)], 'Datatype','single');
h5write([prefix '000.h5'], '/VX', mult*VX_DATA);
h5create([prefix '000.h5'],'/VY',[size(VY_DATA,1) size(VY_DATA,2)], 'Datatype','single');
h5write([prefix '000.h5'], '/VY', mult*VY_DATA);
h5create([prefix '000.h5'],'/VZ',[size(VZ_DATA,1) size(VZ_DATA,2)], 'Datatype','single');
h5write([prefix '000.h5'], '/VZ', mult*VZ_DATA);

The graph file is the same in both steady and transient state.

Time step file

The time step file for the last decade of C2VSim is:

Ndays = eomday(VelOut.YMD(itimes,1), VelOut.YMD(itimes,2));
TS = [0; cumsum(Ndays)];
fid = fopen(fullfile(input_files_path,'TimeStep10yrs.ich'),'w');
fprintf(fid,'%.1f\n', TS);
fclose(fid);

Prepare particles

For this demo we have prepared two files. One for individual particles and for with wells.

In transient state we have to specify the release time. The above snippet sets the release time to 2161. In the TimeSteps variable we see that 2161 is between the 72 step which corresponds to SEP 2011. Therefore, these particles will be released the 30/SEP/2011.

Run the particle tracking

In transient state the step size has to be at least as large as the time steps. The time steps are months therefore the steps will be generally very small. In fact, they are going to be so small that Euler method will give very accurate and smooth results.

The main configuration file for the transient state is the c2vsim_CLOUD_TR_config.ini and the velocity configuration is the c2vsim_CLOUD_TR_vf.ini.
Among the step configuration parameters the nStepsTime = 2 is important in transient state simulation. This parameter controls how many steps to take within a time step. Therefore since the difference between two steps is a month, setting this to 2 will force the code to take a step every 15 days.

In the following figure we compare the streamlines between steady (blue) and transient (red) state.

Streamlines around well

We can see that the steady state streamlines is a smoothed version of the transient state since the transient velocity field was repeated over and over.

The following plot shows the histograms of the streamline lengths and travel time. We can see that the length for the transient streamlines is are longer compared to steady, which is expected as their trajectories are more wiggly.
However the travel time is of the same order. This is because the steady state velocity is the average velocity. Although the transient streamlines are longer, the cumulative time of the low and high velocity segments is approximately equal to the cumulative average velocity.

Streamlines around well
⚠️ **GitHub.com Fallback** ⚠️