Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F70916609
adimensional_system.cpp
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Mon, Jul 8, 06:16
Size
36 KB
Mime Type
text/x-c
Expires
Wed, Jul 10, 06:16 (1 d, 23 h)
Engine
blob
Format
Raw Data
Handle
18886052
Attached To
rSPECMICP SpecMiCP / ReactMiCP
adimensional_system.cpp
View Options
/*-------------------------------------------------------
- Module : specmicp
- File : adim_system.cpp
- Author : Fabien Georget
Copyright (c) 2014, Fabien Georget <fabieng@princeton.edu>, Princeton University
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the Princeton University nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
---------------------------------------------------------*/
#include <cmath>
#include "adimensional_system.hpp"
#include "utils/log.hpp"
#include "../equilibrium_data.hpp"
#include "physics/constants.hpp"
#include "physics/laws.hpp"
#include "adimensional_system_solution.hpp"
#include <random>
// uncomment to activate the finite difference jacobian
// #define SPECMICP_DEBUG_EQUATION_FD_JACOBIAN
namespace
specmicp
{
constexpr
scalar_t
log10
=
std
::
log
(
10.0
);
// Constructor
// ===========
AdimensionalSystem
::
AdimensionalSystem
(
RawDatabasePtr
ptrdata
,
const
AdimensionalSystemConstraints
&
constraints
,
const
AdimensionalSystemOptions
&
options
)
:
AdimemsionalSystemNumbering
(
ptrdata
),
OptionsHandler
<
AdimensionalSystemOptions
>
(
options
),
m_fixed_values
(
constraints
.
total_concentrations
),
m_secondary_conc
(
ptrdata
->
nb_aqueous
),
m_loggamma
(
ptrdata
->
nb_component
+
ptrdata
->
nb_aqueous
),
m_saturation_gas
(
0
),
m_gas_fugacity
(
ptrdata
->
nb_gas
),
m_sorbed_concentrations
(
ptrdata
->
nb_sorbed
),
m_inert_volume_fraction
(
constraints
.
inert_volume_fraction
)
{
number_eq
(
constraints
);
m_secondary_conc
.
setZero
();
m_gas_fugacity
.
setZero
();
m_loggamma
.
setZero
();
m_sorbed_concentrations
.
setZero
();
}
AdimensionalSystem
::
AdimensionalSystem
(
RawDatabasePtr
ptrdata
,
const
AdimensionalSystemConstraints
&
constraints
,
const
AdimensionalSystemSolution
&
previous_solution
,
const
AdimensionalSystemOptions
&
options
)
:
AdimemsionalSystemNumbering
(
ptrdata
),
OptionsHandler
<
AdimensionalSystemOptions
>
(
options
),
m_fixed_values
(
constraints
.
total_concentrations
),
m_secondary_conc
(
previous_solution
.
secondary_molalities
),
m_loggamma
(
previous_solution
.
log_gamma
),
m_ionic_strength
(
previous_solution
.
ionic_strength
),
m_saturation_gas
(
0
),
m_gas_fugacity
(
previous_solution
.
gas_fugacities
),
m_sorbed_concentrations
(
previous_solution
.
sorbed_molalities
),
m_inert_volume_fraction
(
constraints
.
inert_volume_fraction
)
{
number_eq
(
constraints
);
}
// Equation numbering
// ==================
void
AdimensionalSystem
::
number_eq
(
const
AdimensionalSystemConstraints
&
constraints
)
{
index_t
neq
=
0
;
m_ideq
=
std
::
vector
<
index_t
>
(
total_dofs
(),
no_equation
);
m_component_equation_type
=
std
::
vector
<
int
>
(
m_data
->
nb_component
,
no_equation
);
m_fixed_activity_species
=
std
::
vector
<
index_t
>
(
m_data
->
nb_component
,
no_species
);
// Water
// =====
if
(
constraints
.
water_equation
!=
WaterEquationType
::
NoEquation
)
{
m_ideq
[
dof_water
()]
=
neq
;
m_component_equation_type
[
dof_water
()]
=
static_cast
<
int
>
(
constraints
.
water_equation
);
++
neq
;
}
// Aqueous components
// ==================
number_eq_aqueous_component
(
constraints
,
neq
);
// Surface model
// =============
if
(
constraints
.
surface_model
.
model_type
==
SurfaceEquationType
::
Equilibrium
)
{
// add the equation
m_ideq
[
dof_surface
()]
=
neq
;
++
neq
;
// setup the total concentration
m_surface_concentration
=
constraints
.
surface_model
.
concentration
;
}
// above equations are 'free' (i.e. non constrained)
m_nb_free_vars
=
neq
;
// following equations are complementarity conditions
// Minerals
// ========
for
(
index_t
m:
m_data
->
range_mineral
())
{
bool
can_precipitate
=
true
;
// Remove minerals that cannot precipitate
for
(
index_t
&
k:
m_nonactive_component
)
{
if
(
m_data
->
nu_mineral
(
m
,
k
)
!=
0.0
)
{
can_precipitate
=
false
;
break
;
// this is not a mineral that can precipitate
}
}
if
(
can_precipitate
)
{
m_ideq
[
dof_mineral
(
m
)]
=
neq
;
++
neq
;
}
}
m_nb_tot_vars
=
neq
;
m_nb_compl_vars
=
m_nb_tot_vars
-
m_nb_free_vars
;
// Secondary species
// =================
// Secondary aqueous species
// -------------------------
m_active_aqueous
.
reserve
(
m_data
->
nb_aqueous
);
for
(
index_t
j:
m_data
->
range_aqueous
())
{
bool
can_exist
=
true
;
for
(
const
index_t
&
k:
m_nonactive_component
)
{
if
(
m_data
->
nu_aqueous
(
j
,
k
)
!=
0
)
{
can_exist
=
false
;
}
}
m_active_aqueous
.
push_back
(
can_exist
);
}
m_active_gas
.
reserve
(
m_data
->
nb_gas
);
// Gas
// ---
for
(
index_t
k:
m_data
->
range_gas
())
{
bool
can_exist
=
true
;
for
(
const
index_t
&
n:
m_nonactive_component
)
{
if
(
m_data
->
nu_gas
(
k
,
n
)
!=
0.0
)
{
can_exist
=
false
;
break
;
}
}
m_active_gas
.
push_back
(
can_exist
);
}
// Sorbed species
// --------------
for
(
index_t
s:
m_data
->
range_sorbed
())
{
// Check if the surface model is computed
if
(
constraints
.
surface_model
.
model_type
!=
SurfaceEquationType
::
Equilibrium
)
{
m_active_sorbed
.
push_back
(
false
);
continue
;
}
// If so, check that all components of the sorbed species exist
bool
can_exist
=
true
;
for
(
const
index_t
&
k:
m_nonactive_component
)
{
if
(
m_data
->
nu_sorbed
(
s
,
k
)
!=
0.0
)
{
can_exist
=
false
;
break
;
}
}
m_active_sorbed
.
push_back
(
can_exist
);
}
}
void
AdimensionalSystem
::
number_eq_aqueous_component
(
const
AdimensionalSystemConstraints
&
constraints
,
index_t
&
neq
)
{
using
EqT
=
AqueousComponentEquationType
;
// First set the charge keeper
if
(
constraints
.
charge_keeper
!=
no_species
)
{
if
(
constraints
.
charge_keeper
==
0
or
constraints
.
charge_keeper
>
m_data
->
nb_component
)
{
throw
std
::
invalid_argument
(
"The charge keeper must be an aqueous component. Invalid argument : "
+
std
::
to_string
(
constraints
.
charge_keeper
));
}
m_component_equation_type
[
constraints
.
charge_keeper
]
=
static_cast
<
int
>
(
EqT
::
ChargeBalance
);
}
// Then go over fix fugacity gas
for
(
auto
it
=
constraints
.
fixed_fugacity_cs
.
begin
();
it
!=
constraints
.
fixed_fugacity_cs
.
end
();
++
it
)
{
if
(
m_component_equation_type
[
it
->
id_component
]
!=
static_cast
<
int
>
(
EqT
::
NoEquation
))
{
throw
std
::
invalid_argument
(
"Component '"
+
m_data
->
labels_basis
[
it
->
id_component
]
+
"' is already constrained, a fixed fugacity condition can not be applied"
);
}
m_component_equation_type
[
it
->
id_component
]
=
static_cast
<
int
>
(
EqT
::
FixedFugacity
);
m_fixed_values
(
it
->
id_component
)
=
it
->
log_value
;
m_fixed_activity_species
[
it
->
id_component
]
=
it
->
id_gas
;
}
// Then over the fix activity species
for
(
auto
it
=
constraints
.
fixed_activity_cs
.
begin
();
it
!=
constraints
.
fixed_activity_cs
.
end
();
++
it
)
{
if
(
m_component_equation_type
[
it
->
id_component
]
!=
static_cast
<
int
>
(
EqT
::
NoEquation
))
{
throw
std
::
invalid_argument
(
"Component '"
+
m_data
->
labels_basis
[
it
->
id_component
]
+
"' is already constrained, a fixed activity condition can not be applied."
);
}
m_component_equation_type
[
it
->
id_component
]
=
static_cast
<
int
>
(
EqT
::
FixedActivity
);
m_fixed_values
(
it
->
id_component
)
=
it
->
log_value
;
}
// Finally number the equations
for
(
index_t
component:
m_data
->
range_aqueous_component
())
{
// If no equation is assigned yet
if
(
m_component_equation_type
[
component
]
==
static_cast
<
int
>
(
EqT
::
NoEquation
))
{
// Mass is conserved for this component
//###FIXME: H[+], HO[-]
if
(
std
::
abs
(
m_fixed_values
(
component
))
>
get_options
().
cutoff_total_concentration
)
{
m_component_equation_type
[
component
]
=
static_cast
<
int
>
(
EqT
::
MassConservation
);
m_ideq
[
component
]
=
neq
;
++
neq
;
}
else
// add component to the nonactive component list
{
m_nonactive_component
.
push_back
(
component
);
}
}
// If equation is already assigned
else
{
m_ideq
[
component
]
=
neq
;
++
neq
;
}
}
if
(
stdlog
::
ReportLevel
()
>=
logger
::
Debug
and
m_nonactive_component
.
size
()
>
0
)
{
// if in debug mode list the non active components
DEBUG
<<
"Non active components :"
;
for
(
auto
it:
m_nonactive_component
)
{
DEBUG
<<
" - "
<<
it
;
}
}
}
// ================ //
// //
// Residuals //
// //
// ================ //
scalar_t
AdimensionalSystem
::
residual_water
(
const
Vector
&
x
)
const
{
scalar_t
res
=
0
;
if
(
m_component_equation_type
[
dof_water
()]
==
static_cast
<
int
>
(
WaterEquationType
::
MassConservation
))
{
const
scalar_t
conc_w
=
density_water
()
*
saturation_water
(
x
);
res
=
total_concentration_bc
(
0
)
-
conc_w
/
m_data
->
molar_mass_basis_si
(
0
);
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
not
is_aqueous_active
(
j
))
continue
;
res
-=
conc_w
*
m_data
->
nu_aqueous
(
j
,
0
)
*
m_secondary_conc
(
j
);
}
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
continue
;
res
-=
conc_w
*
m_data
->
nu_sorbed
(
s
,
0
)
*
sorbed_species_concentration
(
s
);
}
for
(
index_t
m:
m_data
->
range_mineral
())
{
if
(
ideq_min
(
m
)
==
no_equation
or
m_data
->
nu_mineral
(
m
,
0
)
==
0.0
)
continue
;
res
-=
m_data
->
nu_mineral
(
m
,
0
)
*
saturation_mineral
(
x
,
m
)
/
molar_volume_mineral
(
m
);
}
for
(
index_t
k:
m_data
->
range_gas
())
{
if
(
m_data
->
nu_gas
(
k
,
0
)
==
0.0
)
continue
;
res
-=
m_data
->
nu_gas
(
k
,
0
)
*
saturation_gas_phase
()
*
gas_fugacity
(
k
)
*
gas_total_pressure
()
/
(
constants
::
gas_constant
*
temperature
());
}
res
/=
total_concentration_bc
(
0
);
}
else
if
(
m_component_equation_type
[
dof_water
()]
==
static_cast
<
int
>
(
WaterEquationType
::
SaturatedSystem
))
{
res
=
1
-
saturation_water
(
x
)
-
m_inert_volume_fraction
;
for
(
index_t
mineral:
m_data
->
range_mineral
())
{
res
-=
saturation_mineral
(
x
,
mineral
);
}
}
return
res
;
}
scalar_t
AdimensionalSystem
::
residual_component
(
const
Vector
&
x
,
index_t
component
)
const
{
specmicp_assert
(
aqueous_component_equation_type
(
component
)
==
AqueousComponentEquationType
::
MassConservation
);
const
scalar_t
conc_w
=
density_water
()
*
saturation_water
(
x
);
scalar_t
res
=
total_concentration_bc
(
component
)
-
conc_w
*
component_molality
(
x
,
component
);
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
not
is_aqueous_active
(
j
))
continue
;
res
-=
conc_w
*
m_data
->
nu_aqueous
(
j
,
component
)
*
secondary_molality
(
j
);
}
if
(
ideq_surf
()
!=
no_equation
)
{
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
continue
;
res
-=
conc_w
*
m_data
->
nu_sorbed
(
s
,
component
)
*
sorbed_species_concentration
(
s
);
}
}
for
(
index_t
m:
m_data
->
range_mineral
())
{
if
(
ideq_min
(
m
)
==
no_equation
)
continue
;
res
-=
m_data
->
nu_mineral
(
m
,
component
)
*
saturation_mineral
(
x
,
m
)
/
molar_volume_mineral
(
m
);
}
for
(
index_t
k:
m_data
->
range_gas
())
{
if
(
m_data
->
nu_gas
(
k
,
component
)
==
0.0
)
continue
;
res
-=
m_data
->
nu_gas
(
k
,
component
)
*
saturation_gas_phase
()
*
gas_fugacity
(
k
)
*
gas_total_pressure
()
/
(
constants
::
gas_constant
*
temperature
());
}
res
/=
total_concentration_bc
(
component
);
return
res
;
}
scalar_t
AdimensionalSystem
::
residual_fixed_activity
(
const
Vector
&
x
,
index_t
component
)
const
{
specmicp_assert
(
aqueous_component_equation_type
(
component
)
==
AqueousComponentEquationType
::
FixedActivity
);
scalar_t
res
=
(
fixed_activity_bc
(
component
)
-
log_gamma_component
(
component
)
-
log_component_molality
(
x
,
component
)
);
res
/=
fixed_activity_bc
(
component
);
return
res
;
}
scalar_t
AdimensionalSystem
::
residual_fixed_fugacity
(
const
Vector
&
x
,
index_t
component
)
const
{
specmicp_assert
(
aqueous_component_equation_type
(
component
)
==
AqueousComponentEquationType
::
FixedFugacity
);
index_t
id_g
=
m_fixed_activity_species
[
component
];
scalar_t
res
=
fixed_fugacity_bc
(
component
)
+
m_data
->
logk_gas
(
id_g
);
for
(
index_t
component:
m_data
->
range_aqueous_component
())
{
if
(
m_data
->
nu_gas
(
id_g
,
component
)
==
0
)
continue
;
res
-=
m_data
->
nu_gas
(
id_g
,
component
)
*
(
log_gamma_component
(
component
)
+
log_component_molality
(
x
,
component
));
}
res
/=
fixed_fugacity_bc
(
component
);
return
res
;
}
scalar_t
AdimensionalSystem
::
residual_mineral
(
const
Vector
&
x
,
index_t
m
)
const
{
specmicp_assert
(
ideq_min
(
m
)
!=
no_equation
);
scalar_t
res
=
m_data
->
logk_mineral
(
m
);
for
(
index_t
i:
m_data
->
range_aqueous_component
())
{
if
(
m_data
->
nu_mineral
(
m
,
i
)
!=
0
)
res
-=
m_data
->
nu_mineral
(
m
,
i
)
*
(
log_component_molality
(
x
,
i
)
+
log_gamma_component
(
i
));
}
return
res
;
}
scalar_t
AdimensionalSystem
::
residual_charge_conservation
(
const
Vector
&
x
)
const
{
scalar_t
res
=
0.0
;
for
(
index_t
i:
m_data
->
range_aqueous_component
())
{
if
(
m_data
->
charge_component
(
i
)
!=
0
and
ideq_paq
(
i
)
!=
no_equation
)
res
+=
m_data
->
charge_component
(
i
)
*
component_molality
(
x
,
i
);
}
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
m_data
->
charge_aqueous
(
j
)
==
0
and
not
is_aqueous_active
(
j
))
continue
;
res
+=
m_data
->
charge_aqueous
(
j
)
*
secondary_molality
(
j
);
}
return
res
;
}
scalar_t
AdimensionalSystem
::
residual_surface
(
const
Vector
&
x
)
const
{
specmicp_assert
(
ideq_surf
()
!=
no_equation
);
const
scalar_t
conc_w
=
density_water
()
*
saturation_water
(
x
);
scalar_t
res
=
surface_total_concentration
()
-
conc_w
*
free_sorption_site_concentration
(
x
);
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
continue
;
res
-=
conc_w
*
m_data
->
nb_surface_sites
(
s
)
*
sorbed_species_concentration
(
s
);
}
return
res
/
surface_total_concentration
();
}
void
AdimensionalSystem
::
get_residuals
(
const
Vector
&
x
,
Vector
&
residual
)
{
residual
.
resize
(
total_variables
());
// initialisation of 'main' secondary variables
// They are especially nessary if the finite difference jacobian is used
set_secondary_concentration
(
x
);
set_sorbed_concentrations
(
x
);
//
// water
if
(
ideq_w
()
!=
no_equation
)
residual
(
ideq_w
())
=
residual_water
(
x
);
// aqueous component
for
(
index_t
i:
m_data
->
range_aqueous_component
())
{
switch
(
aqueous_component_equation_type
(
i
))
{
case
AqueousComponentEquationType
::
NoEquation:
continue
;
case
AqueousComponentEquationType
::
MassConservation:
residual
(
ideq_paq
(
i
))
=
residual_component
(
x
,
i
);
break
;
case
AqueousComponentEquationType
::
ChargeBalance:
residual
(
ideq_paq
(
i
))
=
residual_charge_conservation
(
x
);
break
;
case
AqueousComponentEquationType
::
FixedActivity:
residual
(
ideq_paq
(
i
))
=
residual_fixed_activity
(
x
,
i
);
break
;
case
AqueousComponentEquationType
::
FixedFugacity:
residual
(
ideq_paq
(
i
))
=
residual_fixed_fugacity
(
x
,
i
);
break
;
}
}
// surface
if
(
ideq_surf
()
!=
no_equation
)
residual
(
ideq_surf
())
=
residual_surface
(
x
);
// mineral
for
(
index_t
m:
m_data
->
range_mineral
())
{
if
(
ideq_min
(
m
)
!=
no_equation
)
residual
(
ideq_min
(
m
))
=
residual_mineral
(
x
,
m
);
}
// std::cout << residual << std::endl;
}
// ================ //
// //
// Jacobian //
// //
// ================ //
void
AdimensionalSystem
::
get_jacobian
(
Vector
&
x
,
Matrix
&
jacobian
)
// //non-optimized Finite difference, for test only
#ifdef SPECMICP_DEBUG_EQUATION_FD_JACOBIAN
{
finite_difference_jacobian
(
x
,
jacobian
);
return
;
}
#else
// analytical jacobian
{
analytical_jacobian
(
x
,
jacobian
);
return
;
}
#endif
void
AdimensionalSystem
::
finite_difference_jacobian
(
Vector
&
x
,
Matrix
&
jacobian
)
{
const
int
neq
=
total_variables
();
Eigen
::
VectorXd
res
(
neq
);
Eigen
::
VectorXd
perturbed_res
(
neq
);
jacobian
.
setZero
(
neq
,
neq
);
get_residuals
(
x
,
res
);
for
(
int
j
=
0
;
j
<
neq
;
++
j
)
{
double
h
=
1e-8
*
std
::
abs
(
x
(
j
));
if
(
h
==
0
)
h
=
1e-8
;
double
tmp
=
x
(
j
);
x
(
j
)
+=
h
;
h
=
x
(
j
)
-
tmp
;
get_residuals
(
x
,
perturbed_res
);
for
(
int
i
=
0
;
i
<
neq
;
++
i
)
{
jacobian
(
i
,
j
)
=
(
perturbed_res
(
i
)
-
res
(
i
))
/
h
;
}
x
(
j
)
=
tmp
;
}
}
void
AdimensionalSystem
::
analytical_jacobian
(
Vector
&
x
,
Matrix
&
jacobian
)
{
const
int
neq
=
total_variables
();
jacobian
.
setZero
(
neq
,
neq
);
// water
jacobian_water
(
x
,
jacobian
);
// aqueous component
jacobian_aqueous_components
(
x
,
jacobian
);
// surface
if
(
ideq_surf
()
!=
no_equation
)
jacobian_surface
(
x
,
jacobian
);
// mineral equilibrium
jacobian_minerals
(
x
,
jacobian
);
}
void
AdimensionalSystem
::
jacobian_water
(
Vector
&
x
,
Matrix
&
jacobian
)
{
if
(
water_equation_type
()
==
WaterEquationType
::
MassConservation
)
{
const
scalar_t
rho_w
=
density_water
();
scalar_t
tmp
=
-
1.0
/
m_data
->
molar_mass_basis_si
(
0
);
const
scalar_t
factor
=
total_concentration_bc
(
0
);
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
m_data
->
nu_aqueous
(
j
,
0
)
==
0
and
not
is_aqueous_active
(
j
))
continue
;
tmp
-=
m_data
->
nu_aqueous
(
j
,
0
)
*
secondary_molality
(
j
);
}
tmp
*=
rho_w
;
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_aqueous_active
(
s
))
continue
;
tmp
-=
m_data
->
nu_sorbed
(
s
,
0
)
*
secondary_molality
(
s
);
}
jacobian
(
0
,
0
)
=
tmp
/
factor
;
const
scalar_t
conc_w
=
density_water
()
*
saturation_water
(
x
);
for
(
index_t
k:
m_data
->
range_aqueous_component
())
{
if
(
ideq_paq
(
k
)
==
no_equation
)
continue
;
scalar_t
tmp
=
0
;
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
not
is_aqueous_active
(
j
))
continue
;
tmp
-=
m_data
->
nu_aqueous
(
j
,
0
)
*
m_data
->
nu_aqueous
(
j
,
k
)
*
secondary_molality
(
j
);
}
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
continue
;
tmp
-=
m_data
->
nu_sorbed
(
s
,
0
)
*
m_data
->
nu_sorbed
(
s
,
k
)
*
sorbed_species_concentration
(
s
);
}
tmp
*=
conc_w
;
jacobian
(
0
,
ideq_paq
(
k
))
=
log10
*
tmp
/
factor
;
}
for
(
index_t
m:
m_data
->
range_mineral
())
{
if
(
ideq_min
(
m
)
==
no_equation
)
continue
;
jacobian
(
0
,
ideq_min
(
m
))
=
-
m_data
->
nu_mineral
(
m
,
0
)
/
molar_volume_mineral
(
m
)
/
factor
;
}
}
else
if
(
water_equation_type
()
==
WaterEquationType
::
SaturatedSystem
)
{
jacobian
(
0
,
0
)
=
-
1
;
for
(
index_t
m:
m_data
->
range_mineral
())
{
if
(
ideq_min
(
m
)
==
no_equation
)
continue
;
jacobian
(
0
,
ideq_min
(
m
))
=
-
1
;
}
}
}
void
AdimensionalSystem
::
jacobian_aqueous_components
(
Vector
&
x
,
Matrix
&
jacobian
)
{
for
(
index_t
i:
m_data
->
range_aqueous_component
())
{
const
index_t
idp
=
ideq_paq
(
i
);
if
(
idp
==
no_equation
)
continue
;
switch
(
aqueous_component_equation_type
(
i
))
{
case
AqueousComponentEquationType
::
NoEquation:
continue
;
// Mass balance equation
// =====================
case
AqueousComponentEquationType
::
MassConservation:
{
const
scalar_t
conc_w
=
density_water
()
*
saturation_water
(
x
);
const
scalar_t
factor
=
total_concentration_bc
(
i
);
// Aqueous components
for
(
index_t
k:
m_data
->
range_aqueous_component
())
{
if
(
ideq_paq
(
k
)
==
no_equation
)
continue
;
scalar_t
tmp_iip
=
0
;
if
(
k
==
i
)
tmp_iip
-=
component_molality
(
x
,
i
)
*
log10
;
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
not
is_aqueous_active
(
j
))
continue
;
tmp_iip
-=
log10
*
m_data
->
nu_aqueous
(
j
,
i
)
*
m_data
->
nu_aqueous
(
j
,
k
)
*
secondary_molality
(
j
);
}
if
(
ideq_surf
()
!=
no_equation
)
{
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
continue
;
tmp_iip
-=
log10
*
m_data
->
nu_sorbed
(
s
,
i
)
*
m_data
->
nu_sorbed
(
s
,
k
)
*
sorbed_species_concentration
(
s
);
}
}
tmp_iip
*=
conc_w
;
jacobian
(
idp
,
ideq_paq
(
k
))
=
tmp_iip
/
factor
;
}
// Minerals
for
(
index_t
m:
m_data
->
range_mineral
())
{
if
(
ideq_min
(
m
)
==
no_equation
)
continue
;
jacobian
(
idp
,
ideq_min
(
m
))
=
-
m_data
->
nu_mineral
(
m
,
i
)
/
molar_volume_mineral
(
m
)
/
factor
;
}
// Water
if
(
ideq_w
()
!=
no_equation
)
{
scalar_t
tmp_iw
=
-
component_molality
(
x
,
i
);
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
not
is_aqueous_active
(
j
)
or
m_data
->
nu_aqueous
(
j
,
i
)
==
0
)
continue
;
tmp_iw
-=
m_data
->
nu_aqueous
(
j
,
i
)
*
secondary_molality
(
j
);
}
if
(
ideq_surf
()
!=
no_equation
)
{
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
continue
;
tmp_iw
-=
m_data
->
nu_sorbed
(
s
,
i
)
*
sorbed_species_concentration
(
s
);
}
}
tmp_iw
*=
density_water
();
jacobian
(
idp
,
ideq_w
())
=
tmp_iw
/
factor
;
}
// Surface
if
(
ideq_surf
()
!=
no_equation
)
{
scalar_t
tmp_s
=
0.0
;
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
continue
;
tmp_s
-=
conc_w
*
m_data
->
nu_sorbed
(
s
,
i
)
*
m_data
->
nb_surface_sites
(
s
)
*
sorbed_species_concentration
(
s
);
}
jacobian
(
idp
,
ideq_surf
())
=
log10
*
tmp_s
/
factor
;
}
break
;
}
// Charge balance equation
// =======================
case
AqueousComponentEquationType
::
ChargeBalance:
{
// Aqueous components
for
(
index_t
k:
m_data
->
range_aqueous_component
())
{
const
index_t
idc
=
ideq_paq
(
k
);
if
(
idc
==
no_equation
)
continue
;
scalar_t
tmp_drdb
=
0.0
;
if
(
m_data
->
charge_component
(
k
)
!=
0.0
)
tmp_drdb
=
m_data
->
charge_component
(
k
);
// Secondary species
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
not
is_aqueous_active
(
j
)
or
m_data
->
nu_aqueous
(
j
,
k
)
==
0.0
or
m_data
->
charge_aqueous
(
j
)
==
0.0
)
continue
;
scalar_t
tmp_value
=
m_data
->
nu_aqueous
(
j
,
k
)
*
m_data
->
charge_aqueous
(
j
);
tmp_value
*=
secondary_molality
(
j
)
/
component_molality
(
x
,
k
);
tmp_drdb
+=
tmp_value
;
}
jacobian
(
idp
,
idc
)
+=
component_molality
(
x
,
k
)
*
log10
*
tmp_drdb
;
}
break
;
}
// Fixed activity equation
// =======================
case
AqueousComponentEquationType
::
FixedActivity:
jacobian
(
idp
,
idp
)
=
-
1.0
/
fixed_activity_bc
(
i
);
break
;
// Fixed fugacity equation
// =======================
case
AqueousComponentEquationType
::
FixedFugacity:
{
index_t
id_g
=
m_fixed_activity_species
[
i
];
for
(
index_t
k:
m_data
->
range_aqueous_component
())
{
if
(
ideq_paq
(
k
)
==
no_equation
or
m_data
->
nu_gas
(
id_g
,
k
)
==
0.0
)
continue
;
jacobian
(
idp
,
ideq_paq
(
k
))
=
-
m_data
->
nu_gas
(
id_g
,
k
)
/
fixed_fugacity_bc
(
i
);
}
break
;
}
// end case
}
// end switch
}
}
void
AdimensionalSystem
::
jacobian_minerals
(
Vector
&
x
,
Matrix
&
jacobian
)
{
for
(
index_t
m:
m_data
->
range_mineral
())
{
const
index_t
idm
=
ideq_min
(
m
);
if
(
idm
==
no_equation
)
continue
;
for
(
index_t
i:
m_data
->
range_aqueous_component
())
{
if
(
ideq_paq
(
i
)
==
no_equation
)
continue
;
jacobian
(
idm
,
ideq_paq
(
i
))
=
-
m_data
->
nu_mineral
(
m
,
i
);
}
}
}
void
AdimensionalSystem
::
jacobian_surface
(
Vector
&
x
,
Matrix
&
jacobian
)
{
const
index_t
ids
=
ideq_surf
();
const
scalar_t
factor
=
surface_total_concentration
();
const
scalar_t
conc_w
=
density_water
()
*
saturation_water
(
x
);
specmicp_assert
(
ids
!=
no_equation
);
scalar_t
tmp_s
=
-
conc_w
*
free_sorption_site_concentration
(
x
);
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
continue
;
tmp_s
-=
conc_w
*
m_data
->
nb_surface_sites
(
s
)
*
m_data
->
nb_surface_sites
(
s
)
*
sorbed_species_concentration
(
s
);
}
jacobian
(
ids
,
ids
)
=
log10
*
tmp_s
/
factor
;
// water
const
index_t
idw
=
ideq_w
();
if
(
idw
!=
no_equation
)
{
const
scalar_t
rho_w
=
density_water
();
scalar_t
tmp_w
=
-
rho_w
*
free_sorption_site_concentration
(
x
);
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
continue
;
tmp_w
-=
rho_w
*
m_data
->
nb_surface_sites
(
s
)
*
sorbed_species_concentration
(
s
);
}
jacobian
(
ids
,
idw
)
=
tmp_w
/
factor
;
}
// component
for
(
index_t
k:
m_data
->
range_aqueous_component
())
{
const
index_t
idk
=
ideq_paq
(
k
);
if
(
idk
==
no_equation
)
continue
;
scalar_t
tmp_k
=
0.0
;
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
continue
;
tmp_k
-=
conc_w
*
m_data
->
nb_surface_sites
(
s
)
*
m_data
->
nu_sorbed
(
s
,
k
)
*
sorbed_species_concentration
(
s
);
}
jacobian
(
ids
,
idk
)
=
log10
*
tmp_k
/
factor
;
}
// water
}
// ========================== //
// //
// Secondary variables //
// //
// ========================== //
void
AdimensionalSystem
::
set_secondary_variables
(
const
Vector
&
x
)
{
set_saturation_gas_phase
(
x
);
set_pressure_fugacity
(
x
);
if
(
ideq_surf
()
!=
no_equation
)
set_sorbed_concentrations
(
x
);
set_secondary_concentration
(
x
);
compute_log_gamma
(
x
);
}
void
AdimensionalSystem
::
set_saturation_gas_phase
(
const
Vector
&
x
)
{
m_saturation_gas
=
1
-
saturation_water
(
x
)
-
sum_saturation_minerals
(
x
)
-
m_inert_volume_fraction
;
}
void
AdimensionalSystem
::
set_pressure_fugacity
(
const
Vector
&
x
)
{
for
(
index_t
k:
m_data
->
range_gas
())
{
if
(
not
is_active_gas
(
k
))
continue
;
scalar_t
logp
=
-
m_data
->
logk_gas
(
k
);
for
(
index_t
i:
m_data
->
range_aqueous_component
())
{
if
(
m_data
->
nu_gas
(
k
,
i
)
==
0.0
)
continue
;
logp
+=
m_data
->
nu_gas
(
k
,
i
)
*
(
log_component_molality
(
x
,
i
)
+
log_gamma_component
(
i
));
}
m_gas_fugacity
(
k
)
=
pow10
(
logp
);
}
}
void
AdimensionalSystem
::
set_secondary_concentration
(
const
Vector
&
x
)
{
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
not
is_aqueous_active
(
j
))
{
m_secondary_conc
(
j
)
=
0.0
;
continue
;
}
scalar_t
logconc
=
-
m_data
->
logk_aqueous
(
j
)
-
m_loggamma
(
j
+
m_data
->
nb_component
);
for
(
index_t
k:
m_data
->
range_aqueous_component
())
{
if
(
m_data
->
nu_aqueous
(
j
,
k
)
==
0
)
continue
;
logconc
+=
m_data
->
nu_aqueous
(
j
,
k
)
*
(
log_component_molality
(
x
,
k
)
+
m_loggamma
(
k
));
}
m_secondary_conc
(
j
)
=
pow10
(
logconc
);
}
}
void
AdimensionalSystem
::
set_sorbed_concentrations
(
const
Vector
&
x
)
{
for
(
index_t
s:
m_data
->
range_sorbed
())
{
if
(
not
is_active_sorbed
(
s
))
{
m_sorbed_concentrations
(
s
)
=
0.0
;
continue
;
}
scalar_t
logconc
=
-
m_data
->
logk_sorbed
(
s
)
+
m_data
->
nb_surface_sites
(
s
)
*
(
log_free_sorption_site_concentration
(
x
));
for
(
index_t
k:
m_data
->
range_aqueous_component
())
{
if
(
m_data
->
nu_sorbed
(
s
,
k
)
==
0.0
)
continue
;
logconc
+=
m_data
->
nu_sorbed
(
s
,
k
)
*
(
log_component_molality
(
x
,
k
)
+
m_loggamma
(
k
));
}
m_sorbed_concentrations
(
s
)
=
pow10
(
logconc
);
}
}
void
AdimensionalSystem
::
set_ionic_strength
(
const
Vector
&
x
)
{
scalar_t
ionic
=
0
;
for
(
index_t
i:
m_data
->
range_aqueous_component
())
{
if
(
ideq_paq
(
i
)
==
no_equation
or
m_data
->
charge_component
(
i
)
==
0
)
continue
;
ionic
+=
component_molality
(
x
,
i
)
*
std
::
pow
(
m_data
->
charge_component
(
i
),
2
);
}
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
not
is_aqueous_active
(
j
)
or
m_data
->
charge_aqueous
(
j
)
==
0
)
continue
;
ionic
+=
secondary_molality
(
j
)
*
std
::
pow
(
m_data
->
charge_aqueous
(
j
),
2
);
}
ionic_strength
()
=
ionic
/
2
;
}
void
AdimensionalSystem
::
compute_log_gamma
(
const
Vector
&
x
)
{
set_ionic_strength
(
x
);
const
scalar_t
sqrti
=
std
::
sqrt
(
ionic_strength
());
for
(
index_t
i:
m_data
->
range_aqueous_component
())
{
if
(
ideq_paq
(
i
)
==
no_equation
)
{
log_gamma_component
(
i
)
=
0.0
;
continue
;
}
log_gamma_component
(
i
)
=
laws
::
extended_debye_huckel
(
ionic_strength
(),
sqrti
,
m_data
->
charge_component
(
i
),
m_data
->
a_debye_component
(
i
),
m_data
->
b_debye_component
(
i
)
);
}
for
(
index_t
j:
m_data
->
range_aqueous
())
{
if
(
not
is_aqueous_active
(
j
))
{
log_gamma_secondary
(
j
)
=
0.0
;
continue
;
}
log_gamma_secondary
(
j
)
=
laws
::
extended_debye_huckel
(
ionic_strength
(),
sqrti
,
m_data
->
charge_aqueous
(
j
),
m_data
->
a_debye_aqueous
(
j
),
m_data
->
b_debye_aqueous
(
j
)
);
}
}
bool
AdimensionalSystem
::
hook_start_iteration
(
const
Vector
&
x
,
scalar_t
norm_residual
)
{
if
(
not
get_options
().
non_ideality
)
{
set_secondary_variables
(
x
);
// we still need to compute secondary species !
// if (norm_residual < nb_free_variables()*get_options().start_non_ideality_computation)
// {
// set_secondary_variables(x);
// }
return
true
;
}
not_in_linesearch
=
true
;
scalar_t
previous_norm
=
m_loggamma
.
norm
();
bool
may_have_converged
=
false
;
if
(
norm_residual
<
nb_free_variables
()
*
get_options
().
start_non_ideality_computation
)
{
// Use fixed point iterations for non-ideality
for
(
int
i
=
0
;
i
<
get_options
().
non_ideality_max_iter
;
++
i
)
{
set_secondary_variables
(
x
);
compute_log_gamma
(
x
);
// convergence check
if
(
std
::
abs
(
previous_norm
-
m_loggamma
.
norm
())
/
previous_norm
<
get_options
().
non_ideality_tolerance
)
{
may_have_converged
=
true
;
break
;
}
previous_norm
=
m_loggamma
.
norm
();
}
}
return
may_have_converged
;
}
double
AdimensionalSystem
::
max_lambda
(
const
Vector
&
x
,
const
Vector
&
update
)
{
if
(
ideq_w
()
!=
no_equation
)
{
return
1.0
/
std
::
max
(
1.0
,
-
update
(
0
)
/
(
get_options
().
under_relaxation_factor
*
x
(
0
)));
}
else
{
return
1.0
;
}
}
AdimensionalSystemSolution
AdimensionalSystem
::
get_solution
(
Vector
&
xtot
,
const
Vector
&
x
)
{
double
previous_norm
=
m_loggamma
.
norm
();
set_saturation_gas_phase
(
x
);
set_pressure_fugacity
(
x
);
set_secondary_concentration
(
x
);
if
(
ideq_surf
()
!=
no_equation
)
set_sorbed_concentrations
(
x
);
if
(
get_options
().
non_ideality
)
{
compute_log_gamma
(
x
);
if
(
std
::
abs
(
previous_norm
-
m_loggamma
.
norm
())
>
1e-6
)
{
WARNING
<<
"Activity coefficient have not converged !"
<<
std
::
endl
<<
"output can not be trusted
\n
Difference : "
+
std
::
to_string
(
std
::
abs
(
previous_norm
-
m_loggamma
.
norm
()));
}
}
// Set the correct value for the water total saturation
if
(
ideq_w
()
==
no_equation
)
{
xtot
(
dof_water
())
=
saturation_water
(
x
);
}
return
AdimensionalSystemSolution
(
xtot
,
m_secondary_conc
,
m_loggamma
,
m_ionic_strength
,
m_gas_fugacity
,
m_sorbed_concentrations
,
m_inert_volume_fraction
);
}
// Water, saturation and density
// ==============================
scalar_t
AdimensionalSystem
::
saturation_water
(
const
Vector
&
x
)
const
{
if
(
ideq_w
()
!=
no_equation
)
return
x
(
ideq_w
());
else
return
1.0
-
sum_saturation_minerals
(
x
)
-
m_inert_volume_fraction
;
}
scalar_t
AdimensionalSystem
::
saturation_mineral
(
const
Vector
&
x
,
index_t
mineral
)
const
{
specmicp_assert
(
mineral
>=
0
and
mineral
<
m_data
->
nb_mineral
);
if
(
ideq_min
(
mineral
)
==
no_equation
)
return
0.0
;
else
return
x
(
ideq_min
(
mineral
));
}
scalar_t
AdimensionalSystem
::
sum_saturation_minerals
(
const
Vector
&
x
)
const
{
scalar_t
sum_saturations
=
0.0
;
for
(
index_t
mineral:
m_data
->
range_mineral
())
{
sum_saturations
+=
saturation_mineral
(
x
,
mineral
);
}
return
sum_saturations
;
}
scalar_t
AdimensionalSystem
::
density_water
()
const
{
return
laws
::
density_water
(
units
::
celsius
(
25.0
),
length_unit
(),
mass_unit
());
}
scalar_t
AdimensionalSystem
::
molar_volume_mineral
(
index_t
mineral
)
const
{
return
m_data
->
molar_volume_mineral
(
mineral
,
length_unit
());
}
// Starting guess
// ==============
void
AdimensionalSystem
::
reasonable_starting_guess
(
Vector
&
xtot
)
{
xtot
.
resize
(
total_dofs
());
xtot
(
dof_water
())
=
0.8
;
for
(
index_t
i:
m_data
->
range_aqueous_component
())
{
xtot
(
dof_component
(
i
))
=
-
4.0
;
}
if
(
ideq_surf
()
!=
no_equation
)
xtot
(
dof_surface
())
=
std
::
log10
(
0.8
*
surface_total_concentration
());
else
xtot
(
dof_surface
())
=
0.0
;
xtot
.
block
(
offset_minerals
(),
0
,
m_data
->
nb_mineral
,
1
).
setZero
();
m_loggamma
.
setZero
();
m_secondary_conc
.
setZero
();
m_sorbed_concentrations
.
setZero
();
}
void
AdimensionalSystem
::
reasonable_restarting_guess
(
Vector
&
xtot
)
{
std
::
random_device
rd
;
std
::
mt19937
gen
(
rd
());
std
::
uniform_real_distribution
<>
dis
(
-
2
,
2
);
xtot
(
dof_water
())
=
0.5
;
for
(
index_t
i:
m_data
->
range_aqueous_component
())
{
//if (xtot(dof_component(i)) > 0 or xtot(dof_component(i)) < -9)
xtot
(
i
)
=
get_options
().
restart_concentration
+
dis
(
gen
);
}
if
(
ideq_surf
()
!=
no_equation
)
xtot
(
dof_surface
())
=
std
::
log10
(
0.8
*
surface_total_concentration
());
else
xtot
(
dof_surface
())
=
0.0
;
xtot
.
segment
(
offset_minerals
(),
m_data
->
nb_mineral
).
setZero
();
m_loggamma
.
setZero
();
m_secondary_conc
.
setZero
();
m_sorbed_concentrations
.
setZero
();
}
}
// end namespace specmicp
Event Timeline
Log In to Comment