def root_example_rtp(self): """ an example used in the tests below, a main root with laterals """ self.plant = rb.Organism( ) # Root has no dependency on RootSystem anymore p0 = rb.RootRandomParameter(self.plant) p0.name, p0.type, p0.la, p0.lb, p0.nob, p0.ln, p0.r, p0.dx = "taproot", 1, 1, 10, 20, ( 89. / 19.), 1, 0.5 p0.successor = a2i([2]) # to rb.std_int_double_() p0.successorP = a2v([1.]) # rb.std_vector_double_() p1 = rb.RootRandomParameter(self.plant) p1.name, p1.type, p1.la, p1.ln, p1.r, p1.dx = "lateral", 2, 25, 0, 2, 0.1 self.p0, self.p1 = p0, p1 # Python will garbage collect them away, if not stored self.plant.setOrganRandomParameter( self.p0) # the organism manages the type parameters self.plant.setOrganRandomParameter(self.p1) self.param0 = self.p0.realize( ) # set up root by hand (without a root system) self.param0.la = 0 # its important parent has zero length, otherwise creation times are messed up self.param0.lb = 0 # param0 is stored, because otherwise garbage collection deletes it, an program will crash <--- parentroot = rb.Root(1, self.param0, True, True, 0., 0., rb.Vector3d(0, 0, -1), 0, 0, False, 0) parentroot.setOrganism(self.plant) parentroot.addNode(rb.Vector3d(0, 0, -3), 0) # there is no nullptr in Python self.root = rb.Root(self.plant, self.p0.subType, rb.Vector3d(0, 0, -1), 0, parentroot, 0, 0) self.root.setOrganism(self.plant)
def test_sequential(self): """ tests if the the organ tree can be represented in a seqential list""" self.hand_example() self.add_nodes() # only organs with number of nodes > 1 are considered ring = rb.Organ(self.human1, self.thumb, 0, 0, 4) # add a ring to the thumb self.thumb.addChild(ring) ring.addNode(rb.Vector3d(0, -1, 1.6), self.thumb.getNodeId(1), 4) ring.addNode(rb.Vector3d(0, -1, 1.6), 4) organs = self.hand.getOrgans() self.assertEqual(len(organs), 4, "wrong number of organs")
def test_dynamics(self): """ tests if nodes created in last time step are correct """ # self.hand_example() self.hand.simulate(1) self.add_nodes() n0 = self.hand.getNumberOfNodes() - self.hand.getOldNumberOfNodes() n1 = self.little_finger.getNumberOfNodes() - self.little_finger.getOldNumberOfNodes() n2 = self.thumb.getNumberOfNodes() - self.thumb.getOldNumberOfNodes() self.assertEqual(n0, 4, "wrong number of new nodes") self.assertEqual(n1, 2, "wrong number of new nodes") self.assertEqual(n2, 2, "wrong number of new nodes") self.hand.simulate(1) n0 = self.hand.getNumberOfNodes() - self.hand.getOldNumberOfNodes() n1 = self.little_finger.getNumberOfNodes() - self.little_finger.getOldNumberOfNodes() n2 = self.thumb.getNumberOfNodes() - self.thumb.getOldNumberOfNodes() self.assertEqual(n0, 0, "wrong number of new nodes") self.assertEqual(n1, 0, "wrong number of new nodes") self.assertEqual(n2, 0, "wrong number of new nodes") self.hand.simulate(1) self.little_finger.addNode(rb.Vector3d(0, 1, 1.6), 6) n0 = self.hand.getNumberOfNodes() - self.hand.getOldNumberOfNodes() n1 = self.little_finger.getNumberOfNodes() - self.little_finger.getOldNumberOfNodes() n2 = self.thumb.getNumberOfNodes() - self.thumb.getOldNumberOfNodes() self.assertEqual(n0, 0, "wrong number of new nodes") self.assertEqual(n1, 1, "wrong number of new nodes") self.assertEqual(n2, 0, "wrong number of new nodes")
def add_nodes(self): """ used in the tests below, adds nodes to the hand example """ self.hand.addNode(rb.Vector3d(0, 0, 0), 0) self.hand.addNode(rb.Vector3d(0, 0, 1.5), 0) self.hand.addNode(rb.Vector3d(0, -1, 1.6), 0) # thumb self.hand.addNode(rb.Vector3d(0, 1, 1.6), 0) # little finger thumb = self.hand.getNodeId(2) lf = self.hand.getNodeId(3) self.thumb.addNode(rb.Vector3d(0, -1, 1.6), thumb, 4) self.thumb.addNode(rb.Vector3d(0, -2, 2.5), 4) self.little_finger.addNode(rb.Vector3d(0, 1, 1.6), lf, 3) self.little_finger.addNode(rb.Vector3d(0, 1.7, 2.5), 3)
def test_constructors(self): """ tests three different kinds of constructors """ self.root_example_rtp() # 1. constructor from scratch param = self.p0.realize() root = rb.Root(1, param, True, True, 0., 0., rb.Vector3d(0, 0, -1), 0, 0, False, 0) root.setOrganism(self.plant) root.addNode(rb.Vector3d(0, 0, -3), 0) # parent must have at least one nodes # 2. used in simulation (must have parent, since there is no nullptr in Pyhton) root2 = rb.Root(self.plant, self.p1.subType, rb.Vector3d(0, 0, -1), 0, root, 0, 0) root.addChild(root2) # 3. deep copy (with a factory function) plant2 = rb.Organism() root3 = root.copy(plant2) self.assertEqual(str(root), str(root3), "deep copy: the organs shold be equal") self.assertIsNot(root.getParam(), root3.getParam(), "deep copy: organs have same parameter set")
def rs_example_rtp(self): """ an example used in some of the tests below, 100 basals with laterals """ self.rs = rb.RootSystem() srp = rb.SeedRandomParameter(self.rs) srp.subType = 0 srp.seedPos = rb.Vector3d(0., 0., -3.) srp.maxB = 100 srp.firstB = 10 srp.delayB = 3 self.rs.setRootSystemParameter(srp) p0 = rb.RootRandomParameter(self.rs) p0.name, p0.type, p0.la, p0.nob, p0.ln, p0.r, p0.dx = "taproot", 1, 10, 20, 89. / 19., 1, 0.5 p0.lb = 2 p0.successor = a2i([2]) # to rb.std_int_double_() p0.successorP = a2v([1.]) # rb.std_vector_double_() p1 = rb.RootRandomParameter(self.rs) p1.name, p1.type, p1.la, p1.ln, p1.r, p1.dx = "lateral", 2, 25, 0, 2, 0.1 self.p0, self.p1, self.srp = p0, p1, srp # Python will garbage collect them away, if not stored self.rs.setOrganRandomParameter( self.p0) # the organism manages the type parameters self.rs.setOrganRandomParameter(self.p1)
import matplotlib.pyplot as plt rs = rb.RootSystem() # name = "Triticum_aestivum_a_Bingham_2011" # is this the same as your wheat, Shehan? name = "Zea_mays_1_Leitner_2010" rs.openFile(name) # Pore Geometry x_ = (-10, -5, 1, 15) # not 0, otherwise we start in crack y_ = (0, 0, 0, 0) crack = rb.SDF_PlantBox(0.25, 100, 160) # cm cracks_ = rb.std_vector_SDF_() py_cracks = [] for i in range(0, len(y_)): v = rb.Vector3d(x_[i], y_[i], 0) py_cracks.append(rb.SDF_RotateTranslate(crack, v)) cracks_.append(py_cracks[-1]) cracks = rb.SDF_Union(cracks_) rs.setPoreGeometry(cracks) # Increased elongation within the pores maxS = 2 # twice the elongation rate within the pore minS = 1 # normal elongation rate slope = 0 soil_prop = rb.SoilLookUpSDF(cracks, maxS, minS, slope) # Adjust Tropism sigma = [0.4] * 10 for i in range(0, 10):
import numpy as np import matplotlib.pyplot as plt rs = rb.RootSystem() name = "Brassica_oleracea_Vansteenkiste_2014" rs.openFile(name) rs.initialize() rs.simulate(120, True) rs.write(name + ".vtp") # Soil core analysis r, depth, layers = 10, 100., 100 soilcolumn = rb.SDF_PlantContainer(r, r, depth, False) # in the center of the root soilcolumn2 = rb.SDF_RotateTranslate(soilcolumn, 0, 0, rb.Vector3d(10, 0, 0)) # shift 10 cm geom = soilcolumn2 z_ = np.linspace(0, -1 * depth, layers) fig, axes = plt.subplots(nrows=1, ncols=4, figsize=(16, 8)) for a in axes: a.set_xlabel('RLD (cm/cm)') a.set_ylabel('Depth (cm)') # Make a root length distribution ana = rb.SegmentAnalyser(rs) rl_ = ana.distribution(rb.ScalarType.length, 0., depth, layers, True) axes[0].set_title('All roots (120 days)') axes[0].plot(rl_, z_) # Make a root length distribution along the soil core
# Manually set tropism to hydrotropism for the first ten root types sigma = [0.4, 1., 1., 1., 1. ] * 2 for i in range(0,10): p = rs.getRootTypeParameter(i+1) p.dx = 0.25 # adjust resolution p.tropismT = rb.TropismType.hydro p.tropismN = 2 # strength of tropism p.tropismS = sigma[i] # Static soil property maxS = 0.7 # maximal minS = 0.1 # minimal slope = 5 # linear gradient between min and max (cm) box = rb.SDF_PlantBox(30,30,2) # cm layer = rb.SDF_RotateTranslate(box, rb.Vector3d(0,0,-16)) soil_prop = rb.SoilPropertySDF(layer, maxS, minS, slope) # Set the soil properties before calling initialize rs.setSoil(soil_prop) # Initialize rs.initialize() # Simulate simtime = 100 # e.g. 30 or 60 days dt = 1 N = round(simtime/dt) for _ in range(0,N): # in a dynamic soil setting you would need to update soil_prop rs.simulate(dt)
from rb_tools import * rs = rb.RootSystem() name = "Anagallis_femina_Leitner_2010" rs.openFile(name) scale_elongation = rb.EquidistantGrid1D( 0, -50, 100) # for root elongation from 0 cm to -50 cm, 100 layers soil_strength = np.ones((99, )) * 0.5 # some data scales = np.exp(-0.4 * soil_strength) # scales from some equation (TODO) scale_elongation.data = a2v(scales) # set proportionality factors print("value at -3 cm", scale_elongation.getValue(rb.Vector3d(0, 0, -3))) # Manually set scale elongation function for i in range(0, 10): p = rs.getRootTypeParameter(i + 1) p.se = scale_elongation # Simulation rs.initialize() simtime = 120. dt = 1. N = 120 / dt for i in range(0, round(N)): # update soil model (e.g. soil_strength)
import py_rootbox as rb import math rs = rb.RootSystem() # Open plant and root parameter from a file name = "Zea_mays_4_Leitner_2014" rs.openFile(name) # 1. Creates a square rhizotron r*r, with height h, rotated around the x-axis r, h, alpha = 20, 4, 45 rhizotron2 = rb.SDF_PlantContainer(r,r,h,True) posA = rb.Vector3d(0,r,-h/2) # origin before rotation A = rb.Matrix3d.rotX(alpha/180.*math.pi) posA = A.times(posA) # origin after rotation rotatedRhizotron = rb.SDF_RotateTranslate(rhizotron2,alpha,0,posA.times(-1)) # 2. A split pot experiment topBox = rb.SDF_PlantBox(22,20,5) sideBox = rb.SDF_PlantBox(10,20,35) left = rb.SDF_RotateTranslate(sideBox, rb.Vector3d(-6,0,-5)) right = rb.SDF_RotateTranslate(sideBox, rb.Vector3d(6,0,-5)) box_ = rb.std_vector_SDF_() box_.append(topBox) box_.append(left) box_.append(right) splitBox = rb.SDF_Union(box_) # 3. Rhizotubes as obstacles box = rb.SDF_PlantBox(96,126,130) # box rhizotube = rb.SDF_PlantContainer(6.4,6.4,96,False) # a single rhizotube
"""analysis of results using signed distance functions""" import py_rootbox as rb import numpy as np import matplotlib.pyplot as plt rs = rb.RootSystem() name = "Brassica_oleracea_Vansteenkiste_2014" rs.readParameters("modelparameter/" + name + ".xml") rs.initialize() rs.simulate(120) # Soil core analysis r, depth, layers = 10, 100., 100 soilcolumn = rb.SDF_PlantContainer(r, r, depth, False) # in the center of the root soilcolumn2 = rb.SDF_RotateTranslate(soilcolumn, 0, 0, rb.Vector3d(10, 0, 0)) # shift 10 cm # pick one geometry for further analysis geom = soilcolumn z_ = np.linspace(0, -1 * depth, layers) fig, axes = plt.subplots(nrows = 1, ncols = 4, figsize = (16, 8)) for a in axes: a.set_xlabel('RLD (cm/cm)') a.set_ylabel('Depth (cm)') # Make a root length distribution ana = rb.SegmentAnalyser(rs) rl_ = ana.distribution("length", 0., depth, layers, True) axes[0].set_title('All roots (120 days)') axes[0].plot(rl_, z_)
import py_rootbox as rb from multiprocessing import Pool name = "Zea_mays_4_Leitner_2014" N = 3 # number of columns and rows dist = 40 # distance between the root systems [cm] # Creates and initializes N*N root systems allRS = [] for i in range(0,N): for j in range(0,N): rs = rb.RootSystem() rs.openFile(name) rs.getRootSystemParameter().seedPos = rb.Vector3d(dist*i,dist*j,-3) # set position of seed [cm] allRS.append(rs) rs.initialize() # Simulate parallel simtime = 120 def simulate(i): allRS[i].simulate(simtime) pool = Pool() param_space = range(0,len(allRS)) d = [1 for res in pool.imap(simulate,param_space)] # Export results as single vtp files (as polylines) c = 0 ana = rb.SegmentAnalyser() # see example 3b for rs in allRS: c += 1 # root system number
"""multiple root systems""" import py_rootbox as rb name = "Zea_mays_4_Leitner_2014" simtime = 120 N = 3 # number of columns and rows dist = 40 # distance between the root systems [cm] # Initializes N*N root systems allRS = [] for i in range(0, N): for j in range(0, N): rs = rb.RootSystem() rs.readParameters("modelparameter/" + name + ".xml") rs.getRootSystemParameter().seedPos = rb.Vector3d( dist * i, dist * j, -3.) # cm rs.initialize() allRS.append(rs) # Simulate for rs in allRS: rs.simulate(simtime, True) # Export results as single vtp files (as polylines) ana = rb.SegmentAnalyser() # see example 3b for i, rs in enumerate(allRS): vtpname = "../results/example_2b_" + str(i) + ".vtp" rs.write(vtpname) ana.addSegments(rs) # collect all # Write all into single file (segments)
def call(cc): rs = rb.RootSystem() #name = "Triticum_aestivum_a_Bingham_2011" # is this the same as your wheat, Shehan? name = "wheat" rs.openFile(name) # Pore Geometry x_ = (-10, -5, 5, 15) # not 0, otherwise we start in crack y_ = (0, 0, 0, 0) #x_=(-10, -5) #y_=(0,0) crack = rb.SDF_PlantBox(1.0, 100, 160) # cm cracks_ = rb.std_vector_SDF_() py_cracks = [] for i in range(0, len(y_)): v = rb.Vector3d(x_[i], y_[i], 0) py_cracks.append(rb.SDF_RotateTranslate(crack, v)) cracks_.append(py_cracks[-1]) cracks = rb.SDF_Union(cracks_) rs.setPoreGeometry(cracks) # Increased elongation within the pores maxS = 2 # twice the elongation rate within the pore minS = 1 # normal elongation rate slope = 0 soil_prop = rb.SoilLookUpSDF(cracks, maxS, minS, slope) # Adjust Tropism sigma = [0.4] * 10 for i in range(0, 10): p = rs.getRootTypeParameter(i + 1) p.dx = 0.25 # adjust resolution p.tropismT = rb.TropismType.gravi p.tropismN = 1 # strength of tropism p.tropismS = sigma[i] p.se = soil_prop # Pore Local Axes v1 = rb.Vector3d(0.67, 0, 0) v2 = rb.Vector3d(0, 0.67, 0) v3 = rb.Vector3d(0, 0, 0.67) rs.setPoreLocalAxes(rb.Matrix3d(v1, v2, v3)) # Pore Conductivity Tensor t1 = rb.Vector3d(1.33, 0, 0) t2 = rb.Vector3d(0, 50.33, 0) t3 = rb.Vector3d(0, 0, 50.33) rs.setPoreConductivity(rb.Matrix3d(t1, t2, t3)) # Set up depth dependent elongation scaling function scale_elongation = rb.EquidistantGrid1D( 0, -160, 17) # todo: replace this by reading in data from CSV file scales = np.zeros( len(scale_elongation.grid) ) + 0.1 # scales from some equation (scale = function(soil_strength) ), where scale in (0,1) scale_elongation.data = a2v(scales) # set proportionality factors # Proportionally scale this function se2 = rb.ProportionalElongation() se2.setBaseLookUp(scale_elongation) # multiply the scale elongation functions se3 = rb.MultiplySoilLookUps(se2, soil_prop) # Manually set scaling function for i in range(0, 10): p = rs.getRootTypeParameter(i + 1) p.se = se3 # Initialize rs.initialize() # Simulate simtime = 30 * 8 # e.g. 30 or 60 days dt = 1 #0.5 * 1./24. N = round(simtime / dt) for i in range(0, N): # time-dependent and depth-dependent scaling function scales = np.loadtxt('W2.csv', delimiter=';', usecols=i) # reading in ith column from CSV file scale_elongation.data = a2v(scales * 1.00) # set the data of scale elongation rs.simulate(dt) # Export results (as vtp) #rs.write("../results/crack.vtp") # Export cracks #rs.setGeometry(cracks) # just for vizualisation #rs.write("../results/crack.py") z_ = np.linspace(0, -1 * 160, 160) # Make a root length distribution ana = rb.SegmentAnalyser(rs) rl_ = ana.distribution(rb.ScalarType.length, 0, 160, 160, True) np.set_printoptions(precision=4) np.savetxt("cw_" + str(cc) + ".txt", rl_, fmt="%.2f")
# Distance Grid nx = 1 ny = 1000 nz = 1000 X = np.linspace(-0.25 / 2, 0.25 / 2, nx) Y = np.linspace(-27 / 2, 27 / 2, ny) Z = np.linspace(0, -27, nz) X_, Y_, Z_ = np.meshgrid(X, Y, Z, indexing="ij") # stupid matlab default D = np.zeros(X_.shape) print(D.shape) for i in range(0, X_.shape[0]): for j in range(0, X_.shape[1]): for k in range(0, X_.shape[2]): D[i, j, k] = rs_sdf.getDist( rb.Vector3d(X_[i, j, k], Y_[i, j, k], Z_[i, j, k])) D[D < -100] = -10 fig1 = plt.figure() ax = plt.axes() D_ = D[int(nx / 2), :, :] levels = np.linspace(-5, 0.05, 100) cs = ax.contourf(Y_[int(nx / 2), :, :], Z_[int(nx / 2), :, :], D_, levels=levels, cmap='jet') # levels = levels, locator = ticker.LogLocator(), ax.set_xlabel('x') ax.set_ylabel('z')
p0.tropismN = 1. p0.tropismS = 0.2 p1.name = "lateral" p1.subType = 2 p1.la = 25 p1.las = 10 # add standard deviation p1.ln = 0 p1.r = 2 p1.dx = 0.1 p1.tropismS = 0.3 rs.setOrganRandomParameter(p0) rs.setOrganRandomParameter(p1) # Root system parameter (neglecting shoot borne) rsp = rb.SeedRandomParameter(rs) rsp.seedPos = rb.Vector3d(0., 0., -3.) rsp.maxB = 100 rsp.firstB = 10. rsp.delayB = 3. rs.setRootSystemParameter(rsp) rs.initialize() rs.simulate(40, False) rs.write("../results/example_3c.vtp") print("done.")
import py_rootbox as rb import numpy as np from rb_tools import * import matplotlib.pyplot as plt N = 4 # layers between a and b a = -3 # cm b = -7 # cm scale_elongation = rb.EquidistantGrid1D(a, b, N + 1) scales = range(0, N) scale_elongation.data = a2v(scales) # set proportionality factors z_ = np.linspace(0, -10, 1000) y_ = np.zeros((1000)) for i, z in enumerate(z_): y_[i] = scale_elongation.getValue(rb.Vector3d(0, 0, z)) plt.plot(z_, y_) plt.plot(a, 0, "r*") # interval borders plt.plot(b, N - 1, "r*") plt.xlabel("z (cm)") plt.ylabel("y value (?)") plt.show() print("done.")
import py_rootbox as rb import math rs = rb.RootSystem() name = "Anagallis_femina_Leitner_2010" rs.openFile(name) # box with a left and a right compartment for analysis sideBox = rb.SDF_PlantBox(10, 20, 50) left = rb.SDF_RotateTranslate(sideBox, rb.Vector3d(-4.99, 0, 0)) right = rb.SDF_RotateTranslate(sideBox, rb.Vector3d(4.99, 0, 0)) leftright = rb.SDF_Union(left, right) rs.setGeometry(leftright) # left compartment has a minimum of 0.01, 1 elsewhere maxS = 1. # maximal minS = 0.01 # minimal slope = 1. # [cm] linear gradient between min and max leftC = rb.SDF_Complement(left) soilprop = rb.SoilLookUpSDF(leftC, maxS, minS, slope) # for root elongation soilprop2 = rb.SoilLookUpSDF(left, 1., 0.002, slope) # for branching # Manually set scaling function and tropism parameters sigma = [0.4, 1., 1., 1., 1.] * 2 for i in range(0, 10): p = rs.getRootTypeParameter(i + 1) p.dx = 0.25 # adjust resolution p.tropismS = sigma[i] # 1. Scale elongation p.se = soilprop