Example #1
0
func Test_functions03(tst *testing.T) {

	//verbose()
	chk.PrintTitle("functions03")

	eps := 1e-2
	f := func(x float64) float64 { return Sabs(x, eps) }
	ff := func(x float64) float64 { return SabsD1(x, eps) }

	np := 401
	//x  := utl.LinSpace(-5e5, 5e5, np)
	//x  := utl.LinSpace(-5e2, 5e2, np)
	x := utl.LinSpace(-5e1, 5e1, np)
	Y := make([]float64, np)
	y := make([]float64, np)
	g := make([]float64, np)
	h := make([]float64, np)
	tolg, tolh := 1e-6, 1e-5
	with_err := false
	for i := 0; i < np; i++ {
		Y[i] = math.Abs(x[i])
		y[i] = Sabs(x[i], eps)
		g[i] = SabsD1(x[i], eps)
		h[i] = SabsD2(x[i], eps)
		gnum := numderiv(f, x[i])
		hnum := numderiv(ff, x[i])
		errg := math.Abs(g[i] - gnum)
		errh := math.Abs(h[i] - hnum)
		clrg, clrh := "", ""
		if errg > tolg {
			clrg, with_err = "", true
		}
		if errh > tolh {
			clrh, with_err = "", true
		}
		io.Pf("errg = %s%23.15e   errh = %s%23.15e\n", clrg, errg, clrh, errh)
	}

	if with_err {
		chk.Panic("errors found")
	}

	if false {
		//if true {
		plt.Subplot(3, 1, 1)
		plt.Plot(x, y, "'k--', label='abs'")
		plt.Plot(x, y, "'b-', label='sabs'")
		plt.Gll("x", "y", "")

		plt.Subplot(3, 1, 2)
		plt.Plot(x, g, "'b-', label='sabs'")
		plt.Gll("x", "dy/dx", "")

		plt.Subplot(3, 1, 3)
		plt.Plot(x, h, "'b-', label='sabs'")
		plt.Gll("x", "d2y/dx2", "")

		plt.Show()
	}
}
Example #2
0
// PlotYxe plots the function y(x) implemented by Cb_yxe
func PlotYxe(ffcn Cb_yxe, dirout, fname string, xsol, xa, xb float64, np int, xsolLbl, args string, save, show bool, extra func()) (err error) {
	if !save && !show {
		return
	}
	x := utl.LinSpace(xa, xb, np)
	y := make([]float64, np)
	for i := 0; i < np; i++ {
		y[i], err = ffcn(x[i])
		if err != nil {
			return
		}
	}
	var ysol float64
	ysol, err = ffcn(xsol)
	if err != nil {
		return
	}
	plt.Cross("")
	plt.Plot(x, y, args)
	plt.PlotOne(xsol, ysol, io.Sf("'ro', label='%s'", xsolLbl))
	if extra != nil {
		extra()
	}
	plt.Gll("x", "y(x)", "")
	if save {
		os.MkdirAll(dirout, 0777)
		plt.Save(dirout + "/" + fname)
	}
	if show {
		plt.Show()
	}
	return
}
Example #3
0
// PlotEnd ends plot and show figure, if show==true
func PlotEnd(show bool) {
	plt.AxisYrange(0, 1)
	plt.Cross("")
	plt.Gll("$p_c$", "$s_{\\ell}$", "")
	if show {
		plt.Show()
	}
}
Example #4
0
// Draw draws or save figure with plot
//  dirout -- directory to save figure
//  fname  -- file name; e.g. myplot.eps or myplot.png. Use "" to skip saving
//  show   -- shows figure
//  extra  -- is called just after Subplot command and before any plotting
//  Note: subplots will be split if using 'eps' files
func Draw(dirout, fname string, show bool, extra ExtraPlt) {
	var fnk string // filename key
	var ext string // extension
	var eps bool   // is eps figure
	if fname != "" {
		fnk = io.FnKey(fname)
		ext = io.FnExt(fname)
		eps = ext == ".eps"
	}
	nplots := len(Splots)
	nr, nc := utl.BestSquare(nplots)
	var k int
	for i := 0; i < nr; i++ {
		for j := 0; j < nc; j++ {
			if !eps {
				plt.Subplot(nr, nc, k+1)
			}
			if extra != nil {
				extra(i+1, j+1, nplots)
			}
			if Splots[k].Title != "" {
				plt.Title(Splots[k].Title, Splots[k].Topts)
			}
			data := Splots[k].Data
			for _, d := range data {
				if d.Style.L == "" {
					d.Style.L = d.Alias
				}
				x, y := d.X, d.Y
				if math.Abs(Splots[k].Xscale) > 0 {
					x = make([]float64, len(d.X))
					la.VecCopy(x, Splots[k].Xscale, d.X)
				}
				if math.Abs(Splots[k].Yscale) > 0 {
					y = make([]float64, len(d.Y))
					la.VecCopy(y, Splots[k].Yscale, d.Y)
				}
				plt.Plot(x, y, d.Style.GetArgs("clip_on=0"))
			}
			plt.Gll(Splots[k].Xlbl, Splots[k].Ylbl, Splots[k].GllArgs)
			if eps {
				savefig(dirout, fnk, ext, k)
				plt.Clf()
			}
			k += 1
		}
	}
	if !eps && fname != "" {
		savefig(dirout, fnk, ext, -1)
	}
	if show {
		plt.Show()
	}
}
Example #5
0
// PlotT plots F, G and H for varying t and fixed coordinates x
func PlotT(o Func, dirout, fname string, t0, tf float64, xcte []float64, np int, args string, withG, withH, save, show bool, extra func()) {
	t := utl.LinSpace(t0, tf, np)
	y := make([]float64, np)
	for i := 0; i < np; i++ {
		y[i] = o.F(t[i], xcte)
	}
	var g, h []float64
	nrow := 1
	if withG {
		g = make([]float64, np)
		for i := 0; i < np; i++ {
			g[i] = o.G(t[i], xcte)
		}
		nrow += 1
	}
	if withH {
		h = make([]float64, np)
		for i := 0; i < np; i++ {
			h[i] = o.H(t[i], xcte)
		}
		nrow += 1
	}
	os.MkdirAll(dirout, 0777)
	if withG || withH {
		plt.Subplot(nrow, 1, 1)
	}
	plt.Plot(t, y, args)
	if extra != nil {
		extra()
	}
	plt.Gll("t", "y", "")
	pidx := 2
	if withG {
		plt.Subplot(nrow, 1, pidx)
		plt.Plot(t, g, args)
		plt.Gll("t", "dy/dt", "")
		pidx += 1
	}
	if withH {
		plt.Subplot(nrow, 1, pidx)
		plt.Plot(t, h, args)
		plt.Gll("t", "d2y/dt2", "")
	}
	if save {
		plt.Save(dirout + "/" + fname)
	}
	if show {
		plt.Show()
	}
}
Example #6
0
func main() {

	// define function and derivative function
	y_fcn := func(x float64) float64 { return math.Sin(x) }
	dydx_fcn := func(x float64) float64 { return math.Cos(x) }
	d2ydx2_fcn := func(x float64) float64 { return -math.Sin(x) }

	// run test for 11 points
	X := utl.LinSpace(0, 2*math.Pi, 11)
	for _, x := range X {

		// analytical derivatives
		dydx_ana := dydx_fcn(x)
		d2ydx2_ana := d2ydx2_fcn(x)

		// numerical derivative: dydx
		dydx_num, _ := num.DerivCentral(func(t float64, args ...interface{}) float64 {
			return y_fcn(t)
		}, x, 1e-3)

		// numerical derivative d2ydx2
		d2ydx2_num, _ := num.DerivCentral(func(t float64, args ...interface{}) float64 {
			return dydx_fcn(t)
		}, x, 1e-3)

		// check
		chk.PrintAnaNum(io.Sf("dy/dx   @ %.6f", x), 1e-10, dydx_ana, dydx_num, true)
		chk.PrintAnaNum(io.Sf("d²y/dx² @ %.6f", x), 1e-10, d2ydx2_ana, d2ydx2_num, true)
	}

	// generate 101 points
	X = utl.LinSpace(0, 2*math.Pi, 101)
	Y := make([]float64, len(X))
	for i, x := range X {
		Y[i] = y_fcn(x)
	}

	// plot
	plt.Plot(X, Y, "'b.-'")
	plt.Gll("x", "y", "")
	plt.Show()
}
Example #7
0
// PlotHc3d plots 3D hypercube
func PlotHc3d(dirout, fnkey string, x [][]int, xrange [][]float64, show bool) {
	m := len(x)
	n := len(x[0])
	dx := make([]float64, m)
	for i := 0; i < m; i++ {
		dx[i] = (xrange[i][1] - xrange[i][0]) / float64(n-1)
	}
	X := utl.DblsAlloc(m, n)
	for i := 0; i < m; i++ {
		for j := 0; j < n; j++ {
			X[i][j] = xrange[i][0] + float64(x[i][j]-1)*dx[i]
		}
	}
	if !show {
		plt.SetForEps(0.8, 455)
	}
	plt.Plot3dPoints(X[0], X[1], X[2], "clip_on=0, zorder=10")
	if show {
		plt.Show()
	} else {
		plt.SaveD(dirout, fnkey+".eps")
	}
}
Example #8
0
func main() {

	// input data
	simfn := "elast.sim"
	matname := "lrm1"
	pcmax := 30.0
	npts := 101

	// parse flags
	flag.Parse()
	if len(flag.Args()) > 0 {
		simfn = flag.Arg(0)
	}
	if len(flag.Args()) > 1 {
		matname = flag.Arg(1)
	}
	if len(flag.Args()) > 2 {
		pcmax = io.Atof(flag.Arg(2))
	}
	if len(flag.Args()) > 3 {
		npts = io.Atoi(flag.Arg(3))
	}

	// check extension
	if io.FnExt(simfn) == "" {
		simfn += ".sim"
	}

	// print input data
	io.Pf("\nInput data\n")
	io.Pf("==========\n")
	io.Pf("  simfn   = %30s // simulation filename\n", simfn)
	io.Pf("  matname = %30s // material name\n", matname)
	io.Pf("  pcmax   = %30v // max pc\n", pcmax)
	io.Pf("  npts    = %30v // number of points\n", npts)
	io.Pf("\n")

	// load simulation
	sim := inp.ReadSim("", simfn, "lrm_", false)
	if sim == nil {
		io.PfRed("cannot load simulation\n")
		return
	}

	// get material data
	mat := sim.Mdb.Get(matname)
	if mat == nil {
		io.PfRed("cannot get material\n")
		return
	}
	io.Pforan("mat = %v\n", mat)

	// get and initialise model
	mdl := mreten.GetModel(simfn, matname, mat.Model, false)
	if mdl == nil {
		io.PfRed("cannot allocate model\n")
		return
	}
	mdl.Init(mat.Prms)

	// plot drying path
	d_Pc := utl.LinSpace(0, pcmax, npts)
	d_Sl := make([]float64, npts)
	d_Sl[0] = 1
	var err error
	for i := 1; i < npts; i++ {
		d_Sl[i], err = mreten.Update(mdl, d_Pc[i-1], d_Sl[i-1], d_Pc[i]-d_Pc[i-1])
		if err != nil {
			io.PfRed("drying: cannot updated model\n%v\n", err)
			return
		}
	}
	plt.Plot(d_Pc, d_Sl, io.Sf("'b-', label='%s (dry)', clip_on=0", matname))

	// plot wetting path
	w_Pc := utl.LinSpace(pcmax, 0, npts)
	w_Sl := make([]float64, npts)
	w_Sl[0] = d_Sl[npts-1]
	for i := 1; i < npts; i++ {
		w_Sl[i], err = mreten.Update(mdl, w_Pc[i-1], w_Sl[i-1], w_Pc[i]-w_Pc[i-1])
		if err != nil {
			io.PfRed("wetting: cannot updated model\n%v\n", err)
			return
		}
	}
	plt.Plot(w_Pc, w_Sl, io.Sf("'c-', label='%s (wet)', clip_on=0", matname))

	// save results
	type Results struct{ Pc, Sl []float64 }
	res := Results{append(d_Pc, w_Pc...), append(d_Sl, w_Sl...)}
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	err = enc.Encode(&res)
	if err != nil {
		io.PfRed("cannot encode results\n")
		return
	}
	fn := path.Join(sim.Data.DirOut, matname+".dat")
	io.WriteFile(fn, &buf)
	io.Pf("file <%s> written\n", fn)

	// show figure
	plt.AxisYrange(0, 1)
	plt.Cross()
	plt.Gll("$p_c$", "$s_{\\ell}$", "")
	plt.Show()
}
Example #9
0
// PlotX plots F and the gradient of F, Gx and Gy, for varying x and fixed t
//  hlZero  -- highlight F(t,x) = 0
//  axEqual -- use axis['equal']
func PlotX(o Func, dirout, fname string, tcte float64, xmin, xmax []float64, np int, args string, withGrad, hlZero, axEqual, save, show bool, extra func()) {
	if len(xmin) == 3 {
		chk.Panic("PlotX works in 2D only")
	}
	X, Y := utl.MeshGrid2D(xmin[0], xmax[0], xmin[1], xmax[1], np, np)
	F := la.MatAlloc(np, np)
	var Gx, Gy [][]float64
	nrow := 1
	if withGrad {
		Gx = la.MatAlloc(np, np)
		Gy = la.MatAlloc(np, np)
		nrow += 1
	}
	x := make([]float64, 2)
	g := make([]float64, 2)
	for i := 0; i < np; i++ {
		for j := 0; j < np; j++ {
			x[0], x[1] = X[i][j], Y[i][j]
			F[i][j] = o.F(tcte, x)
			if withGrad {
				o.Grad(g, tcte, x)
				Gx[i][j] = g[0]
				Gy[i][j] = g[1]
			}
		}
	}
	prop, wid, dpi := 1.0, 600.0, 200
	os.MkdirAll(dirout, 0777)
	if withGrad {
		prop = 2
		plt.SetForPng(prop, wid, dpi)
		plt.Subplot(nrow, 1, 1)
		plt.Title("F(t,x)", "")
	} else {
		plt.SetForPng(prop, wid, dpi)
	}
	plt.Contour(X, Y, F, args)
	if hlZero {
		plt.ContourSimple(X, Y, F, false, 8, "levels=[0], linewidths=[2], colors=['yellow']")
	}
	if axEqual {
		plt.Equal()
	}
	if extra != nil {
		extra()
	}
	plt.Gll("x", "y", "")
	if withGrad {
		plt.Subplot(2, 1, 2)
		plt.Title("gradient", "")
		plt.Quiver(X, Y, Gx, Gy, args)
		if axEqual {
			plt.Equal()
		}
		plt.Gll("x", "y", "")
	}
	if save {
		plt.Save(dirout + "/" + fname)
	}
	if show {
		plt.Show()
	}
}
Example #10
0
func Test_step01(tst *testing.T) {

	//verbose()
	chk.PrintTitle("step01")

	dat := `
#94 = B_SPLINE_CURVE_WITH_KNOTS('',3,(#95,#96,#97,#98,#99,#100,#101,#102
    ,#103,#104,#105,#106,#107,#108,#109,#110,#111,#112,#113,#114,#115,
    #116,#117,#118),.UNSPECIFIED.,.F.,.F.,(4,2,2,2,2,2,2,2,2,2,2,4),(0.,
    6.25E-02,0.125,0.1875,0.25,0.375,0.5,0.625,0.75,0.875,0.9375,1.),
  .UNSPECIFIED.);
#95 = CARTESIAN_POINT('',(-101.6,22.574321695,10.));
#96 = CARTESIAN_POINT('',(-102.823664276,22.574321695,10.));
#97 = CARTESIAN_POINT('',(-103.997218852,22.153675049,10.));
#98 = CARTESIAN_POINT('',(-106.08086702,20.987537487,10.));
#99 = CARTESIAN_POINT('',(-107.039417042,20.231466666,10.));
#100 = CARTESIAN_POINT('',(-108.789255706,18.580147193,10.));
#101 = CARTESIAN_POINT('',(-109.592274608,17.667817128,10.));
#102 = CARTESIAN_POINT('',(-111.037380535,15.764228357,10.));
#103 = CARTESIAN_POINT('',(-111.683917439,14.768224001,10.));
#104 = CARTESIAN_POINT('',(-113.420358551,11.662050083,10.));
#105 = CARTESIAN_POINT('',(-114.315877571,9.399456447,10.));
#106 = CARTESIAN_POINT('',(-115.497833567,4.783654514,10.));
#107 = CARTESIAN_POINT('',(-115.797754021,2.416719426,10.));
#108 = CARTESIAN_POINT('',(-115.799425478,-2.39678873,10.));
#109 = CARTESIAN_POINT('',(-115.502828861,-4.759182837,10.));
#110 = CARTESIAN_POINT('',(-114.318091024,-9.395746276,10.));
#111 = CARTESIAN_POINT('',(-113.429172576,-11.644133428,10.));
#112 = CARTESIAN_POINT('',(-111.107704555,-15.801102362,10.));
#113 = CARTESIAN_POINT('',(-109.654523885,-17.763443825,10.));
#114 = CARTESIAN_POINT('',(-107.045203102,-20.226082032,10.));
#115 = CARTESIAN_POINT('',(-106.086648295,-20.984357267,10.));
#116 = CARTESIAN_POINT('',(-103.990686794,-22.157275434,10.));
#117 = CARTESIAN_POINT('',(-102.821037828,-22.574321695,10.));
#118 = CARTESIAN_POINT('',(-101.6,-22.574321695,10.));
`

	var stp STEP
	err := stp.ParseDATA(dat)
	if err != nil {
		tst.Errorf("Parse filed:\n%v", err)
		return
	}

	np := len(stp.Points)
	x := make([]float64, np)
	y := make([]float64, np)
	z := make([]float64, np)
	i := 0
	for _, p := range stp.Points {
		io.Pforan("p = %#v\n", p)
		x[i] = p.Coordinates[0]
		y[i] = p.Coordinates[1]
		z[i] = p.Coordinates[2]
		i++
	}

	for _, c := range stp.BScurves {
		io.Pf("c = %#v\n", c)
	}

	if false {
		plt.Plot3dPoints(x, y, z, "")
		plt.Show()
	}
}
Example #11
0
func Test_sg1121(tst *testing.T) {

	//verbose()
	chk.PrintTitle("sg1121")

	// run simulation
	if !Start("data/sg1121.sim", true, chk.Verbose) {
		tst.Errorf("test failed\n")
		return
	}

	// make sure to flush log
	defer End()

	// run simulation
	if !Run() {
		tst.Errorf("test failed\n")
		return
	}

	// plot
	doplot := false
	if doplot {

		// read summary
		sum := ReadSum(Global.Dirout, Global.Fnkey)

		// allocate domain
		distr := false
		d := NewDomain(Global.Sim.Regions[0], distr)
		if !d.SetStage(0, Global.Sim.Stages[0], distr) {
			tst.Errorf("SetStage failed\n")
			return
		}

		// selected node and dof index
		nidx := 30
		didx := 1

		// new results
		ntout := len(sum.OutTimes)
		uy := make([]float64, ntout)
		for tidx := 0; tidx < ntout; tidx++ {
			if !d.ReadSol(sum.Dirout, sum.Fnkey, tidx) {
				tst.Errorf("test failed:\n")
				return
			}
			nod := d.Nodes[nidx]
			eq := nod.Dofs[didx].Eq
			uy[tidx] = d.Sol.Y[eq]
			// check
			if math.Abs(d.Sol.T-sum.OutTimes[tidx]) > 1e-14 {
				tst.Errorf("output times do not match time in solution array")
				return
			}
		}
		plt.Plot(sum.OutTimes, uy, "'k*-', clip_on=0, label='gofem'")

		// old results
		b, err := io.ReadFile("cmp/sg1121gofemold.json")
		if err != nil {
			tst.Errorf("cannot read comparison file\n")
			return
		}
		var gofemold struct {
			Time, Uy30 []float64
		}
		err = json.Unmarshal(b, &gofemold)
		if err != nil {
			tst.Errorf("cannot unmarshal comparison file\n")
			return
		}
		plt.Plot(gofemold.Time, gofemold.Uy30, "'ro-', label='gofemOld'")

		// mechsys results
		_, res, err := io.ReadTable("cmp/sg1121mechsysN30.cmp")
		if err != nil {
			tst.Errorf("cannot read mechsys comparison file\n")
			return
		}
		plt.Plot(res["Time"], res["uy"], "'b+-', label='mechsys'")

		// show figure
		plt.Gll("$t$", "$u_y$", "")
		plt.Show()
	}
}
Example #12
0
func Test_sg111(tst *testing.T) {

	//verbose()
	chk.PrintTitle("sg111")

	// run simulation
	if !Start("data/sg111.sim", true, chk.Verbose) {
		tst.Errorf("test failed\n")
		return
	}

	// make sure to flush log
	defer End()

	// run simulation
	if !Run() {
		tst.Errorf("test failed\n")
		return
	}

	// plot
	doplot := false
	if doplot {

		// read summary
		sum := ReadSum(Global.Dirout, Global.Fnkey)

		// allocate domain
		distr := false
		d := NewDomain(Global.Sim.Regions[0], distr)
		if !d.SetStage(0, Global.Sim.Stages[0], distr) {
			tst.Errorf("SetStage failed\n")
			return
		}

		// selected node and dof index
		nidx := 1
		didx := 1

		// gofem
		ntout := len(sum.OutTimes)
		uy := make([]float64, ntout)
		for tidx := 0; tidx < ntout; tidx++ {
			if !d.ReadSol(sum.Dirout, sum.Fnkey, tidx) {
				tst.Errorf("cannot read solution\n")
				return
			}
			nod := d.Nodes[nidx]
			eq := nod.Dofs[didx].Eq
			uy[tidx] = d.Sol.Y[eq]
			// check
			if math.Abs(d.Sol.T-sum.OutTimes[tidx]) > 1e-14 {
				tst.Errorf("output times do not match time in solution array")
				return
			}
		}
		plt.Plot(sum.OutTimes, uy, "'ro-', clip_on=0, label='gofem'")

		// analytical solution
		ta := 1.0
		calc_uy := func(t float64) float64 {
			if t < ta {
				return 0.441*math.Sin(math.Pi*t/ta) - 0.216*math.Sin(2.0*math.Pi*t/ta)
			}
			return -0.432 * math.Sin(2*math.Pi*(t-ta))
		}
		tAna := utl.LinSpace(0, 5, 101)
		uyAna := make([]float64, len(tAna))
		for i, t := range tAna {
			uyAna[i] = calc_uy(t)
		}
		plt.Plot(tAna, uyAna, "'g-', clip_on=0, label='analytical'")

		plt.Gll("$t$", "$u_y$", "")
		plt.Show()
	}
}
Example #13
0
func Test_rjoint01(tst *testing.T) {

	//verbose()
	chk.PrintTitle("rjoint01. curved line in 3D")

	// initialisation
	defer End()
	if !Start("data/rjoint01.sim", true, chk.Verbose) {
		tst.Errorf("Start failed\n")
		return
	}

	// callback to check consistent tangent operators
	eid := 2 // rjoint element
	if true {
		defer rjoint_DebugKb(&testKb{
			tst: tst, eid: eid, tol: 1e-8, verb: chk.Verbose,
			ni: -1, nj: -1, itmin: 1, itmax: -1, tmin: -1, tmax: -1,
		})()
	}

	// run simulation
	if !Run() {
		tst.Errorf("Run failed\n")
		return
	}

	// plot
	//if true {
	if false {

		// allocate domain
		sum := ReadSum(Global.Dirout, Global.Fnkey)
		dom := NewDomain(Global.Sim.Regions[0], false)
		if !dom.SetStage(0, Global.Sim.Stages[0], false) {
			tst.Errorf("SetStage failed\n")
			return
		}
		ele := dom.Elems[eid].(*Rjoint)
		ipd := ele.OutIpsData()

		// load results from file
		n := len(sum.OutTimes)
		mτ := make([]float64, n)
		ωpb := make([]float64, n)
		for i, _ := range sum.OutTimes {
			if !dom.In(sum, i, true) {
				tst.Errorf("cannot read solution\n")
				return
			}
			for _, dat := range ipd {
				res := dat.Calc(dom.Sol)
				mτ[i] = -res["tau"]
				ωpb[i] = res["ompb"]
			}
		}

		// plot
		plt.Plot(ωpb, mτ, "'b-', marker='o', clip_on=0")
		plt.Gll("$\\bar{\\omega}_p$", "$-\\tau$", "")
		plt.Show()
	}
}
Example #14
0
func main() {

	// catch errors
	defer func() {
		if err := recover(); err != nil {
			io.PfRed("ERROR: %v\n", err)
		}
	}()

	// input data
	simfn, _ := io.ArgToFilename(0, "elast", ".sim", true)
	matname := io.ArgToString(1, "lrm1")
	pcmax := io.ArgToFloat(2, 30.0)
	npts := io.ArgToInt(3, 101)

	// print input table
	io.Pf("\n%s\n", io.ArgsTable(
		"simulation filename", "simfn", simfn,
		"material name", "matname", matname,
		"max pc", "pcmax", pcmax,
		"number of points", "npts", npts,
	))

	// load simulation
	sim := inp.ReadSim(simfn, "lrm", false, 0)
	if sim == nil {
		io.PfRed("cannot load simulation\n")
		return
	}

	// get material data
	mat := sim.MatParams.Get(matname)
	if mat == nil {
		io.PfRed("cannot get material\n")
		return
	}
	io.Pforan("mat = %v\n", mat)

	// get and initialise model
	mdl := mreten.GetModel(simfn, matname, mat.Model, false)
	if mdl == nil {
		io.PfRed("cannot allocate model\n")
		return
	}
	mdl.Init(mat.Prms)

	// plot drying path
	d_Pc := utl.LinSpace(0, pcmax, npts)
	d_Sl := make([]float64, npts)
	d_Sl[0] = 1
	var err error
	for i := 1; i < npts; i++ {
		d_Sl[i], err = mreten.Update(mdl, d_Pc[i-1], d_Sl[i-1], d_Pc[i]-d_Pc[i-1])
		if err != nil {
			io.PfRed("drying: cannot updated model\n%v\n", err)
			return
		}
	}
	plt.Plot(d_Pc, d_Sl, io.Sf("'b-', label='%s (dry)', clip_on=0", matname))

	// plot wetting path
	w_Pc := utl.LinSpace(pcmax, 0, npts)
	w_Sl := make([]float64, npts)
	w_Sl[0] = d_Sl[npts-1]
	for i := 1; i < npts; i++ {
		w_Sl[i], err = mreten.Update(mdl, w_Pc[i-1], w_Sl[i-1], w_Pc[i]-w_Pc[i-1])
		if err != nil {
			io.PfRed("wetting: cannot updated model\n%v\n", err)
			return
		}
	}
	plt.Plot(w_Pc, w_Sl, io.Sf("'c-', label='%s (wet)', clip_on=0", matname))

	// save results
	type Results struct{ Pc, Sl []float64 }
	res := Results{append(d_Pc, w_Pc...), append(d_Sl, w_Sl...)}
	var buf bytes.Buffer
	enc := json.NewEncoder(&buf)
	err = enc.Encode(&res)
	if err != nil {
		io.PfRed("cannot encode results\n")
		return
	}
	fn := path.Join(sim.Data.DirOut, matname+".dat")
	io.WriteFile(fn, &buf)
	io.Pf("file <%s> written\n", fn)

	// show figure
	plt.AxisYrange(0, 1)
	plt.Cross("")
	plt.Gll("$p_c$", "$s_{\\ell}$", "")
	plt.Show()
}