How to create Multiple Custom PDEs in Modulus

I added a second PDE and am wondering is there anything additional I need to add to my script.

Here I created the PDE class with the example of two PDEs. Is this the right way of doing it?

class PFR_equation2D(PDE):
name = “PFR_equation2D”

def __init__(self):
    #input varables are defined with SymPy symbols
    #x coordinate
    x = Symbol("x")

    #time
    t = Symbol("t")

    #make input variables
    input_variables = {"x": x, "t": t}


    #make output,c and Temp function
  
    c = Function("c")(*input_variables) 
    T = Function("T")(*input_variables)

    #write equation and store it in the class by adding it to the dictionary of equation
    self.equations = {}
    self.equations["PFR_equation"] =  c.diff(t,1) - (c.diff(x,2)) + c 
    self.equations["PFR_equation"] =  T.diff(t,1) - (T.diff(x,2)) - c

Hi @nga77

This looks like your headed in the right direction! Although for your last line you’ll probably want to change the key value (PFR_equation) to something unique, otherwise you’ll overwrite the equation above it. E.g.

self.equations["PFR_equation_c"] =  c.diff(t,1) - (c.diff(x,2)) + c 
self.equations["PFR_equation_T"] =  T.diff(t,1) - (T.diff(x,2)) - c

Have a look at the 1D Wave example with its custom PDE as an example.

1 Like

I then validated both PDEs with two sets of data from two different CSV files, but I get an error that the CSV file cannot be found. I know that is not the actual error as the file is within the same directory.

What am I actually doing wrong?
Below is a snippet of my code

#add validation data

c2 = np.loadtxt(“Concentration_data.csv”, delimiter=“,”)
c3=c2.reshape(2450,1) #reshaping array
c_outvar_numpy={“c”:c3}
validator = PointwiseValidator(
nodes=nodes,
invar=invar_numpy,
true_outvar=c_outvar_numpy,
batch_size=128,
plotter=ValidatorPlotter(),
)
domain.add_validator(validator, “val_data_c”)

T2 = np.loadtxt(“Temperature_data.csv”, delimiter=“,”)
T3=T2.reshape(2450,1) #reshaping array
T_outvar_numpy={“T”:T3}
validator = PointwiseValidator(
nodes=nodes,
invar=invar_numpy,
true_outvar=T_outvar_numpy,
batch_size=128,
plotter=ValidatorPlotter(),
)
domain.add_validator(validator, “val_data_Temp”)

Thank you

Hi @nga77

This is because Hydra moves the local run location. Please use the following util:

from modulus.sym.hydra import to_absolute_path

Here’s a sample of its usage from LDC example:

    file_path = "openfoam/cavity_uniformVel0.csv"
    if os.path.exists(to_absolute_path(file_path)):
        mapping = {"Points:0": "x", "Points:1": "y", "U:0": "u", "U:1": "v", "p": "p"}
        openfoam_var = csv_to_dict(to_absolute_path(file_path), mapping)
        openfoam_var["x"] += -width / 2  # center OpenFoam data
        openfoam_var["y"] += -height / 2  # center OpenFoam data
        openfoam_invar_numpy = {
            key: value for key, value in openfoam_var.items() if key in ["x", "y"]
        }
        openfoam_outvar_numpy = {
            key: value for key, value in openfoam_var.items() if key in ["u", "v"]
        }
        openfoam_validator = PointwiseValidator(
            nodes=nodes,
            invar=openfoam_invar_numpy,
            true_outvar=openfoam_outvar_numpy,
            batch_size=1024,
            plotter=ValidatorPlotter(),
        )

Thank you!

I changed my code accordingly and got the following error message:

IndexError: index 2449 is out of bounds for dimension 0 with size 2449

My csv file has two columns of data. Is there a right batch size that I am suppose to use or is there an error elsewhere in my code?

Below is the validation section of my code.

#add validation data
mapping = {“Concentration” : “c”,“Temperature” : “Temp”}
val_data = csv_to_dict(
to_absolute_path(“Val_data.csv”), mapping
)
val_outvar_numpy = {
key: value for key, value in val_data.items() if key in [“c”, “Temp”]
}
conc_validator = PointwiseValidator(
nodes=nodes,
invar=invar_numpy,
true_outvar=val_outvar_numpy,
batch_size=128,
plotter=ValidatorPlotter(),
)
domain.add_validator(conc_validator, “val_data”)

Hi @nga77

The batch size should be fine. Can you please confirm that all arrays in val_outvar_numpy are of the same size Nx1 where N=2449?

Now, my program runs when I have all my independent and dependent variables in one CSV file.

However, now I get a complex error message.
For the independent values, I had to change the scale to get the same number of rows for all columns in my CSV file. For example, my x-axis range is from of 0 to 1 with 2449 values in between. If these very small values are causing the problem, how else can I go about this?

I have an inferencer output with the right plot, but I don’t get a plot for the validator output.

error: <modulus.utils.io.plotter.ValidatorPlotter object at 0x7f67b5dd3340>.call raised an exception: QH6154 Qhull precision error: Initial simplex is flat (facet 1 is coplanar with the interior point)

While executing: | qhull d Qz Q12 Qt Qbb Qc
Options selected for Qhull 2019.1.r 2019/06/21:
run-id 916568361 delaunay Qz-infinity-point Q12-allow-wide Qtriangulate
Qbbound-last Qcoplanar-keep _pre-merge _zero-centrum Qinterior-keep
Pgood _max-width 1 Error-roundoff 1.4e-15 _one-merge 9.7e-15
Visible-distance 2.8e-15 U-max-coplanar 2.8e-15 Width-outside 5.5e-15
_wide-facet 1.7e-14 _maxoutside 1.1e-14

The input to qhull appears to be less than 3 dimensional, or a
computation has overflowed.

Qhull could not construct a clearly convex simplex from points:

  • p722(v4): 0.3 0.3 0.079
  • p2449(v3): 0.5 0.5 1
  • p2448(v2): 1 1 0.91
  • p0(v1): 0.00041 0.00041 0

The center point is coplanar with a facet, or a vertex is coplanar
with a neighboring facet. The maximum round off error for
computing distances is 1.4e-15. The center point, facets and distances
to the center point are as follows:

center point 0.449 0.449 0.4971

facet p2449 p2448 p0 distance= 0
facet p722 p2448 p0 distance= 0
facet p722 p2449 p0 distance= 0
facet p722 p2449 p2448 distance= 0

These points either have a maximum or minimum x-coordinate, or
they maximize the determinant for k coordinates. Trial points
are first selected from points that maximize a coordinate.

The min and max coordinates for each dimension are:
0: 0.0004083 1 difference= 0.9996
1: 0.0004083 1 difference= 0.9996
2: 0 1 difference= 1

If the input should be full dimensional, you have several options that
may determine an initial simplex:

  • use ‘QJ’ to joggle the input and make it full dimensional
  • use ‘QbB’ to scale the points to the unit cube
  • use ‘QR0’ to randomly rotate the input for different maximum points
  • use ‘Qs’ to search all points for the initial simplex
  • use ‘En’ to specify a maximum roundoff error less than 1.4e-15.
  • trace execution with ‘T3’ to see the determinant for each point.

If the input is lower dimensional:

  • use ‘QJ’ to joggle the input and make it full dimensional
  • use ‘Qbk:0Bk:0’ to delete coordinate k from the input. You should
    pick the coordinate with the least range. The hull will have the
    correct topology.
  • determine the flat containing the points, rotate the points
    into a coordinate plane, and delete the other coordinates.
  • add one or more points to make the input full dimensional.

This is a snapshot of my CSV file

Hi @nga77

My suggestion would be to create a custom plotter for this since your last two columns are nearly identical. This should be possible, you can extend / copy the ValidatorPlotter and write your own __call__ function to override it.

Then just feed this into your PointwiseValidator. From there you can manipulate matplotlib however you like as long as you return a list[(figure, variable name)].

In the built in plotters we do some assuming about the form of the data, so for unique cases like this a custom one will be best to give you the needed control.

Hi @ngeneva
I fixed the data in my CSV file and using the CustomValidatorPlotter fixed my previous error. However, now I am getting a new error message:


`> error: <__main__.CustomValidatorPlotter object at 0x7f249738d4c0>.__call__ raised an exception: __call__() takes 3 positional arguments but 4 were given`

This is my code after importing all the necessary libraries:


class PFR_equation2D(PDE): 
    name = "PFR_equation2D"
    
    def __init__(self):
        # z coordinate
        x = Symbol("x")

        #time
        t = Symbol("t")

        temp = Symbol("temp")
        temp = 273

        input_variables = {"x": x, "t": t}

        c = Function("c")(*input_variables) 
        Temp = Function("Temp")(*input_variables) 

        #write equation and store it in the class by adding it to the dictionary of equation
        self.equations = {}
        self.equations["PFR_equation_c"] =  c.diff(t,1) - 0.01*(c.diff(x,2)) + 0.5*(c.diff(x)) + 1.0*(c**2) 
        arrh_exp = 4.68*math.exp(-806/((600-273)*temp+273)) #rate constant calculated using arrhenius expression at T=273degC. At Tmax, plot didn't change
        self.equations["PFR_equation_T"] =  Temp.diff(t,1) - 0.01*(Temp.diff(x,2)) + 0.5*(Temp.diff(x)) - 2.45*arrh_exp*(c**2) 

class CustomValidatorPlotter(ValidatorPlotter):
    def __call__(self, invar, outvar):
        ndim = len(invar)
        extent, outvar = self._interpolate_2D(100, invar, outvar) 
        a = np.linspace(0, 1, 100)
        b = np.linspace(0, 4, 100)
        dims = list(invar.keys())
        fs = []
        for k in outvar:
            f = plt.figure(figsize=(5, 4), dpi=100)
            plt.pcolor(a, b, outvar[k].T, vmax=1.0, vmin=0.0) # origin="lower", extent=extent)
            plt.xlabel(dims[0])
            plt.ylabel(dims[1])
            plt.colorbar()
            plt.title(k)
            plt.tight_layout()
            fs.append((f, k))

@modulus.main(config_path="conf", config_name="config")
def run(cfg: ModulusConfig) -> None:

    pfr = PFR_equation2D()
    pfr_net = instantiate_arch(
        input_keys=[Key("x"), Key("t")],
        output_keys=[Key("c"), Key("Temp")],
        cfg=cfg.arch.fully_connected,
    )
    nodes = pfr.make_nodes() + [pfr_net.make_node(name="pfr_network")]

    x, t = Symbol("x"), Symbol("t") 
    time_range = {t : (0,4)}
    geo = Line1D(0, 1)

    #make domain
    domain = Domain()
    
    #initial condition
    IC = PointwiseInteriorConstraint( 
        nodes=nodes,
        geometry=geo,
        outvar={"c": 0, "Temp":0}, 
        batch_size=cfg.batch_size.IC,
        parameterization={t: 0.0}
    )
    domain.add_constraint(IC, "IC")
    
    #boundary condition for both PDEs
    BC = PointwiseBoundaryConstraint(
        nodes=nodes,
        geometry=geo,
        outvar={"c": 1},
        batch_size=cfg.batch_size.BC,
        criteria=Eq(x, 0),
        parameterization=time_range
    )
    domain.add_constraint(BC, "BC")

    #boundary condition 2 for both PDEs
    BC2 = PointwiseBoundaryConstraint(
        nodes=nodes,
        geometry=geo,
        outvar={"c__x": 0,"Temp__x": 0,},
        batch_size=cfg.batch_size.BC2,
        criteria=Eq(x, 1),
        parameterization=time_range
    )
    domain.add_constraint(BC2, "BC2")

    #interior for PDE 1
    interior = PointwiseInteriorConstraint(
        nodes=nodes,
        geometry=geo,
        outvar={"PFR_equation_c": 0},
        batch_size=cfg.batch_size.interior,
        parameterization=time_range
    )
    domain.add_constraint(interior, "interior")
    
    #interior for PDE 2
    interior2 = PointwiseInteriorConstraint(
        nodes=nodes,
        geometry=geo,
        outvar={"PFR_equation_T": 0},
        batch_size=cfg.batch_size.interior,
        parameterization=time_range
    )
    domain.add_constraint(interior2, "interior2")
    

    x = np.linspace(0, 1, 100) 
    t = np.linspace(0, 4, 100)
    X, T = np.meshgrid(x, t)
    X = np.expand_dims(X.flatten(), axis=-1)
    T = np.expand_dims(T.flatten(), axis=-1)
    invar_numpy = {"x": X, "t": T}

    grid_inference = PointwiseInferencer(
        nodes=nodes,
        invar=invar_numpy,
        output_names=["c","Temp"],
        batch_size=128,
        plotter=InferencerPlotter(),
    )
    domain.add_inferencer(grid_inference, "inf_data")

    #add validation data
    #need to create CSV files for inputs too, even though they don't need validation, 
    mapping = {"Concentration":"c", "Temperature": "Temp", "z-axis": "x", "time":"t"} 
    val_data = csv_to_dict(to_absolute_path("Val_data.csv"),mapping)
    val_invar_numpy = {
        key: value for key, value in val_data.items() if key in ["x", "t"]
    }
    val_outvar_numpy = {
        key: value for key, value in val_data.items() if key in ["c", "Temp"]
    }
    validator = PointwiseValidator(
        nodes=nodes,
        invar=val_invar_numpy,
        true_outvar=val_outvar_numpy,
        batch_size=128,
        plotter=CustomValidatorPlotter(),
    )
    domain.add_validator(validator, "val_data")
    
   #create solver
    slv = Solver(cfg, domain)
    slv.solve()
if __name__ == "__main__":
    run()

I tried doing two separate mappings for c and Temp, but that didn’t solve the problem. As you can see from the PDE equations I defined at the top of my code, the second equation relies on three variables and is linked to the other PDE. I believe I am almost close to the solution, but I am having problems fixing the error message

Hi @nga77

The error says you only have three inputs to your validation plotter when it should have four. Check out the source code for what the function call needs. Looks like you’re missing one at:

class CustomValidatorPlotter(ValidatorPlotter):
    def __call__(self, invar, outvar):

Hi @ngeneva
I get the following message in the terminal, and now I don’t get both inference and validator plots.

error: <modulus.utils.io.plotter.InferencerPlotter object at 0x7fe4141bfaf0>.call raised an exception: QH6154 Qhull precision error: Initial simplex is flat (facet 1 is coplanar with the interior point)
.
.
The input to qhull appears to be less than 3 dimensional, or a
computation has overflowed.

Qhull could not construct a clearly convex simplex from points:

  • p25(v4): 0.53 2.1 1
  • p2449(v3): 0.5 2 4
  • p48(v2): 1 4 3.6
  • p49(v1): 0 0 0

The center point is coplanar with a facet, or a vertex is coplanar
with a neighboring facet. The maximum round off error for
computing distances is 5.5e-15. The center point, facets and distances
to the center point are as follows:

center point 0.5077 2.031 2.165

facet p2449 p48 p49 distance= 0
facet p25 p48 p49 distance= 0
facet p25 p2449 p49 distance= 0
facet p25 p2449 p48 distance= 0

These points either have a maximum or minimum x-coordinate, or
they maximize the determinant for k coordinates. Trial points
are first selected from points that maximize a coordinate.

The min and max coordinates for each dimension are:
0: 0 1 difference= 1
1: 0 4 difference= 4
2: 0 4 difference= 4

These are parts of the code that I changed:


class CustomValidatorPlotter(ValidatorPlotter):
    class CustomValidatorPlotter(ValidatorPlotter):
    def __call__(self, invar,true_outvar,pred_outvar):
        ndim = len(invar)        
        extent, true_outvar, pred_outvar = self._interpolate_2D(
                100, invar, true_outvar, pred_outvar)
        a = np.linspace(0, 1, 50)
        b = np.linspace(0, 4, 50)
        dims = list(invar.keys())
        fs = []       
        for k in pred_outvar:
            f = plt.figure(figsize=(3 * 5, 4), dpi=100)
            for i, (o, tag) in enumerate(
                zip(
                    [true_outvar[k], pred_outvar[k], true_outvar[k] - pred_outvar[k]],
                    ["true", "pred", "diff"],
                )
            ):
                plt.subplot(1, 3, 1 + i)
                if ndim == 1:
                    plt.plot(invar[dims[0]][:, 0], o[:, 0])
                    plt.xlabel(dims[0])
                elif ndim == 2:
                    plt.imshow(o.T, origin="lower", extent=extent)
                    plt.xlabel(dims[0])
                    plt.ylabel(dims[1])
                    plt.colorbar()
                plt.title(f"{k}_{tag}")
            plt.tight_layout()
            fs.append((f, k))

        return fs

#Initial and Boundary Conditions stay the same and are added to domain

#Add validation data
    mapping = {"Concentration":"c","Temperature": "Temp", "z-axis":"x", "time":"t"} 
    val_data = csv_to_dict(to_absolute_path("Val_data1.csv"),mapping)
    val_invar_numpy = {
        key: value for key, value in val_data.items() if key in ["x", "t"]
    }
    val_outvar_numpy = {
        key: value for key, value in val_data.items() if key in ["c", "Temp"]
    }
    validator = PointwiseValidator(
        nodes=nodes,
        invar=val_invar_numpy,
        true_outvar=val_outvar_numpy,
        batch_size=128,
        plotter=CustomValidatorPlotter(),
    )
    domain.add_validator(validator, "val_data")

    grid_inference = PointwiseInferencer(
        nodes=nodes,
        invar=val_invar_numpy,
        output_names=["c","Temp"],
        batch_size=128,
        plotter=InferencerPlotter(),
    )
    domain.add_inferencer(grid_inference, "inf_data")
 
   #create solver

The data for concentration and temperature correspond to a range of spatial and temporal data which runs for a specific range 50 times.

This is how the data in my CSV file appears:

I would recommend to focus on one plotter at a time. Get your validation one running first. Its not surprise that the inference plotter would have a similar problem if its plotting a similar thing. May need to have a custom plotter for that as well.