def __init__(self, heading=Vector3(0., 1., 0.), up=Vector3(0., 0., 1.), left=Vector3(1., 0., 0.)): self.heading = heading self.left = up self.up = left
def test_rupture(): torque = Vector3(0, 0, 50e5) radius = 1 assert rupture(torque, radius) == False torque = Vector3(0, 0, 50e6) radius = 1 assert rupture(torque, radius) == True
def test_reorient_frame(): v1 = Vector3(2, 1, 1) v2 = Vector3(1, 2, 1) v3 = Vector3(1, 1, 2) rotation_velocity = Vector3(1, 1, 1) length = 1 frame = Frame(v1, v2, v3) reorient_frame(frame, rotation_velocity, length)
def test_norm(): """norm is equivalent to length in v3 mapplet""" from math import sqrt v=Vector3(0,4,4); v.normalize(); assert v == Vector3(0, sqrt(2)/2, sqrt(2)/2) #v = v.__norm__() v = norm(v) assert v>1-tol and v<1+tol
def reorient_frame(initial_hlu, rotation_velocity, rv_norm, length): h = Vector3(initial_hlu.heading) h.normalize() l = Vector3(initial_hlu.left) l.normalize() #vl = rotation_velocity.normalize() #_ look at v3d length definition #vl is replaced by rv_norm if fabs(rv_norm*length) >= 0.01: h = rotate(rotation_velocity.x, rotation_velocity.y, rotation_velocity.z, rv_norm*length, h.x, h.y, h.z) l = rotate(rotation_velocity.x, rotation_velocity.y, rotation_velocity.z, rv_norm*length, l.x, l.y, l.z) h.normalize() l.normalize() return Frame(h, l, cross(h, l))
def test_axis_angle(): from openalea.stocatree.physics import _AxisAngle angle = 0.5 v3 = Vector3(0, 0.5, 0.5) aa = _AxisAngle(v3, angle, check=True) aa.axis_angle_to_quaternion() w3 = Vector3(0.2, 0.2, 0.2) aa.rotate(w3) r1 = aa._rotate1(w3) r2 = aa._rotate2(w3) r3 = aa._rotate3(w3) r4 = aa._rotate4(w3) aa.angle = 0. r5 = aa.rotate(w3)
def reaction_wood_target(up, heading, previous_heading): cos_gh = Vector3(0.0, 0.0, 1.0) * heading cos_pu = previous_heading * up cos_ph = previous_heading * heading inclination = 0 if cos_pu*cos_ph >= 0.0: try: inclination = acos(cos_ph) except: #pass print str(cos_ph) else: try: inclination = -acos(cos_ph) except: print str(cos_ph) percentage = 0.1635 * (1.0 - cos_gh) - 0.1778 * inclination; r = 3.14159*2. * percentage; if (r < 0.0): r = 0.0 elif (r > 3.14159): r = 3.141459 return r
def _rotate1(self, v): """method of rotation that uses Quaternion method""" q = self.axis_angle_to_quaternion() """print '========' # orginal method w = q[0] * v.x + q[1] * v.y + q[2] * v.z x = q[3] * v.x + q[1] * v.z - q[2] * v.y y = q[3] * v.y - q[0] * v.z + q[2] * v.x z = q[3] * v.z + q[0] * v.y - q[1] * v.x res = Vector3( w * q[0] + x * q[3] - y * q[2] + z * q[1], w * q[1] + x * q[2] + y * q[3] - z * q[0], w * q[2] - x * q[1] - y * q[0] + z * q[3] ) print res.x, res.y, res.z """ a = q[3] b = q[0] c = q[1] d = q[2] t2 = a * b t3 = a * c t4 = a * d t5 = -b * b t6 = b * c t7 = b * d t8 = -c * c t9 = c * d t10 = -d * d v1new = 2 * ((t8 + t10) * v.x + (t6 - t4) * v.y + (t3 + t7) * v.z) + v.x v2new = 2 * ((t4 + t6) * v.x + (t5 + t10) * v.y + (t9 - t2) * v.z) + v.y v3new = 2 * ((t7 - t3) * v.x + (t2 + t9) * v.y + (t5 + t8) * v.z) + v.z return Vector3(v1new, v2new, v3new)
def reorient_frame(initial_hlu, rotation_velocity, length): """Reorient frame The initial frame is an HLU (Heading Up Left, turtle axes) frame, made of tree orthogonal vectors that indicate the heading, upwards and left directions of the beam. Then, the HLU frame is rotated around the rotation velocity vector. length is used to rotate the frame only when the product of rotation_velocity and length is large enough. :param initial_hlu: a `Frame` defining the initial HLU frame at the beginning of the season :param rotation_velocity: :param length: length of metamer :returns: a new rotated frame .. todo:: describe the algorithm .. note:: this function has been optimised to used the rotate function from optimisation.pyx that computes the rotation of the AxisAngle around a given vector. It replaces the AxisAngle./quaternion of the origninal code of MappleT. :: import openalea.stocatree.optimisation as optimisation optimisation.rotate(hlu, Vector3, length) """ h = Vector3(initial_hlu.heading) h.normalize() l = Vector3(initial_hlu.left) l.normalize() vl = rotation_velocity.normalize() #_ look at v3d length definition if abs(vl * length) >= 0.01: h = optimisation.rotate(rotation_velocity.x, rotation_velocity.y, rotation_velocity.z, vl * length, h.x, h.y, h.z) l = optimisation.rotate(rotation_velocity.x, rotation_velocity.y, rotation_velocity.z, vl * length, l.x, l.y, l.z) h.normalize() l.normalize() return Frame(h, l, cross(h, l))
def calculate_rotation_velocity(self, simulation, stake=True): r"""Calculate the rotation velocity :param bool stake: (default is True) Calculate the rotation velocity of a torque .. math:: \Omega_\tau = \frac{\tau}{R} where R is the rigidity and :math:`\tau` the cumulated total torque. The memory rotation depends on the lost masses .. math:: \Omega_m = \frac{\Delta m }{m} ? .. math:: \Omega' = \Omega_\tau + \Omega_m .. math:: \Omega = \Omega' * \alpha + (1-\alpha)*\Omega """ if stake and self.trunk: self.rotation_velocity = Vector3() return None # Calculate the rotation velocity (Taylor-Hell, 2005) self.acting_rotation = self.cumulated_torque / self.rigidity # Hypothesis: the shape memory is proportional to the mass # removed (inspired by Almeras. 2002) # The if-else here was added by Han on 22-04-2011 to avoid the case that # self.pre_harvest_mass is sometimes euqal to 0 if simulation.events.harvest.active: if self.pre_harvest_mass != 0: delta_mass = (self.pre_harvest_mass - self.cumulated_mass) / self.pre_harvest_mass else: delta_mass = 0 #delta_mass = 0.5 self.rotation_memory = self.pre_harvest_rotation * delta_mass new_rotation_velocity = self.acting_rotation + self.rotation_memory # Hypothesis: The total rotation velocity is the sum of the acting # rotation and the shape memory self.rotation_velocity = new_rotation_velocity * \ simulation.rotation_convergence.step + self.rotation_velocity * \ (1.0 - simulation.rotation_convergence.step) self.rv_norm = self.rotation_velocity.normalize()
def pruning_reaction_angle(self, phylo_angle): """ :param hlu: Frame :param phyllo_angle: float :param farthest_apex: int :param number: int :returns branching_angle: float :returns phyllotactic_angle: float """ #Determining angle between heading and vertical angle_to_vert = round( acos(dot(self.hlu.heading.normed(), Vector3(0, 0, 1))), 2) print "####### Angle from heading to vert : ", angle_to_vert #Fixing the ratio of that angle to be used as branching angle depending on the pruning intensity vert_ratio = 1 - ((1.0 * self.number) / (self.number + self.farthest_apex)) print "####### Ratio from length : ", vert_ratio new_branching_angle = vert_ratio * angle_to_vert print "####### Angle chosen : ", new_branching_angle #Determining a possible phyllotactic angle that will divert the less from vertical angles = [] #for i in range(360): # hlu = rotate_frame_at_branch(self.hlu, new_branching_angle, i) # angles.append((acos(dot(hlu.heading.normed(), Vector3(0,0,1))))) #return new_branching_angle, angles.index(min(angles)) for i in range(5): hlu = rotate_frame_at_branch( self.hlu, new_branching_angle, i * phylo_angle + self.phyllotactic_angle) angles.append((acos(dot(hlu.heading.normed(), Vector3(0, 0, 1))))) return new_branching_angle, angles.index( min(angles)) * phylo_angle + self.phyllotactic_angle
def test_reaction_wood_target(): up = Vector3(1., 1., 1.) heading = Vector3(1., 0., -0.) previous_heading = Vector3(-1., -1., -1.) reaction_wood_target(up, heading, previous_heading) #r statement up = Vector3(0., 0., 1.) heading = Vector3(0., 1., 0.) previous_heading = Vector3(3.1, 1., 1.) reaction_wood_target(up, heading, previous_heading)
def rotate(v3x, v3y, v3z, angle, vx, vy, vz): c = cos(angle) t2 = 1 - c t6 = t2*v3x t7 = t6*v3y s = sin(angle) t9 = s*v3z t11 = t6*v3z t12 = s*v3y t19 = t2*v3y*v3z t20 = s*v3x t24 = v3z*v3z R00 = c + t2*v3x*v3x R01 = t7 - t9 R02 = t11 + t12 R10 = t7 + t9 R11 = c + t2*v3y*v3y R12 = t19 - t20 R20 = t11 - t12 R21 = t19 + t20 R22 = c + t2*t24 return Vector3(R00*vx+R01*vy+R02*vz, R10*vx+R11*vy+R12*vz, R20*vx+R21*vy+R22*vz)
def test_vector3_cross(): """equivalent of % operator in v3 mapplet""" v1 = Vector3(1.0, 0, 0) v4 = Vector3(4.0, 5, -1) assert cross(v4,v1) == Vector3(0, -1, -5)
def test_stress(): torque = Vector3(100, 100, 500) radius = 0.1 stress(torque, radius)
def test_normSquared(): """normSquared is equivalent to length_sq in v3 mapplet""" from math import sqrt v=Vector3(1,2,3); assert 14==normSquared(v)
def test_rotate_frame_at_branch(): v1 = Vector3(2, 1, 1) v2 = Vector3(1, 2, 1) v3 = Vector3(1, 1, 2) frame = Frame(v1, v2, v3) rotate_frame_at_branch(frame, 45, 45)
def test_Frame(): v1 = Vector3(2, 1, 1) v2 = Vector3(1, 2, 1) v3 = Vector3(1, 1, 2) f = Frame(v1, v2, v3) print f
def test_vector3(): tropism = Vector3(0,0.1,0) tropism.y += .1; assert tropism.y == 0.2 assert tropism.x == 0. assert tropism.z == 0
def __init__(self, phyllotactic_angle=-144.0, branching_angle=-45., floral_angle=-10., tropism=0.1, preformed_leaves=8, spur_death_probability=0.3, inflorescence_death_probability=0.2): """**Constructor** ================================== ============== ============== parameters default values units ================================== ============== ============== phyllotactic_angle 144 degrees branching angle 45 degrees ================================== ============== ============== :param phyllotactic_angle: default is -144 degrees :param branching_angle: default is -45 degrees :param floral_angle: default -10 degrees :param spur_death_probability: default is0.3 :param tropism: the z value of the tropism vector (efault is z=0.1) :param preformed_leaves: 8 :param inflorescence_death_probability: default is 0.2 In addition, several attributes are defined: * an initial HLU frame is also defined to be along the z axis. * trunk_radius in meters * fruits : number of fruits * cross_sectional_area in m^2 * growth units * fruit_load .. todo:: finalise this doc. end_terminal_expansion not used ! Neither in original code. """ self.angle_unit = 'radians' self.phyllotactic_angle = phyllotactic_angle / 180.0 * pi self.branching_angle = branching_angle / 180.0 * pi self.floral_branching_angle = floral_angle / 180.0 * pi # the original mappelt code the initial hlu frame as follows #self.initial_hlu = Frame(Vector3( 0.0, 1.0, 0.0), Vector3( 0.0, 0.0, 1.0), Vector3(-1.0, 0.0, 0.0)); # # this one seems to work, which seems logival since HLU when the tree starts corresponds to zxy or zyx. self.initial_hlu = Frame(Vector3(0.0, 0.0, 1.0), Vector3(0.0, 1.0, 0.0), Vector3(1., 0.0, 0.)) # the original one from mapplet leads to trunk being horizontal #self.initial_hlu = Frame(Vector3( 0.0, 1.0, 0.0), Vector3( 0.0, 0.0, 1.0), Vector3(-1., 0.0, 0.)); self.spur_death_probability = spur_death_probability self.inflorescence_death_probability = inflorescence_death_probability self.preformed_leaves = preformed_leaves # variables self.trunk_radius = 0.0 #in m self.trunk_cross_sectional_area = 0 #cm**2' self.fruit_load = 0. #1/m**2 #self.end_terminal_expansion = convert_to_day(11, 1); # Count the number of growth units (note that the parent_unit_id starts from 0): # Commented by Han on 02-05-2011 self.growth_units = 0 # Count the number of first-order branches (note that the parent_branch_id starts from 0) # Added by Han on 02-05-2011 self.first_branches = 0 self.tropism = Vector3(0.0, 0.0, tropism) #// same as in N original mapplet self.fruits = 0
def test_clamp(): """TO FINALISE""" v = Vector3(0.000001, 1, 1) clamp_v3d_components_if_near_zero(v)
def test_update_position(self): from vplants.plantgl.all import Vector3 self.data.update_position() self.data.update_position(left_metamer_position=Vector3(1, 1, 1))
def __init__(self, floral=False, number=0, hlu=None, zone=None, observation=None, parent_observation=None, parent_unit_id=None, parent_fbr_id=None, parent_tree_id=0, p_angle=0., b_angle=0., wood=None, internode=None, fruit=None, leaf=None): """**Constructor** Leaf, internode, fruit and wood must be provided :param bool floral: :param int number: rank of the metamer in the GU :param Frame hlu: :param int zone: :param int observation: :param float p_angle: phyllotactic angle in degrees :param float b_angle: branching angle in degrees :param wood: a :class:`~openalea.stocatree.wood.Wood` instance :param internode: a :class:`~openalea.stocatree.internode.Internode` instance :param fruit: a :class:`~openalea.stocatree.fruit.Fruit` instance :param leaf: a :class:`~openalea.stocatree.leaf.Leaf` instance """ """ Parameters added by Han, commented on 02-05-2011: parent_unit_id: the id of the parent unit (the unit where the metamer is located) parent_fbr_id: the id of the parent branch (the branch where the metamer is located) # If the parent_fbr_id is 0, it represents "trunk". """ if leaf == None: raise ValueError("leaf must be provided as a Leaf class") else: self.leaf = leaf if fruit == None: raise ValueError("fruit must be provided as a Fruit class") else: self.fruit = fruit if wood == None: raise ValueError("wood must be provided as a Wood class") else: self.wood = wood if internode == None: raise ValueError("internode must be provided as a Internode class") else: self.internode = internode self.number = number # Yield the rank of the metamer self.closest_apex = 0 # Distance to the closest apex self.farthest_apex = 0 # Distance to the farthest apex self.sons_nb = 0 # Cumulated sum of metamer sons self.pruning_react = True # wether the metamer could react to pruning self.pruned_data = None # If metamer was below the pruning point, this will contain the data required to determine pruning reaction that will be passed on previous metamers (rankwise), i.e. reacting position from cutting point [0,2], closest_apex, farthest_apex, sons_nb self.observation = observation self.parent_observation = parent_observation # Yield the shoot type of that metamer self.parent_unit_id = parent_unit_id self.parent_fbr_id = parent_fbr_id self.parent_tree_id = parent_tree_id try: self.zone = zone_converter[zone] except: print zone self.hlu = hlu self.cumulated_mass = 0. #in 'kg' self.radius = self.leaf.petiole_radius # petiole_radius is in m self.offset = 0 # used to shift first branching node to keep it from disapearing into cambial growth self.cumulated_torque = Vector3(0., 0., 0.) self.developped = False #// to avoid more than 1 lateral shoot self.phyllotactic_angle = p_angle #// azimuth / parent metamer (around h) #TODO must be in [0,2pi] self.branching_angle = b_angle #TODO must be in [0,2pi] self.rigidity = 0. # in Pa * m**4 self.age = 0 # in days self.year = 0 self.length = self.internode._min_length # internode units is in m if floral == True: self.fruit.state = 'flower' else: self.fruit.state = 'no_flower' self.trunk = False #Vector3D are initilised to 0,0,0 but could have been anything. self.rotation_memory = Vector3( 0., 0., 0.) # // remaining rotation after harvest self.rotation_velocity = Vector3( 0., 0., 0.) # acting_rotation + rotation_memory self.acting_rotation = Vector3( 0., 0., 0.) # // due to mass and tropism actions self.position = Vector3(0., 0., 0.) #// absolute #Rotation velocity is normalized and its norm is saved in rv_norm self.rv_norm = self.rotation_velocity.normalize() self.season_initial_heading = self.hlu.heading #// used to compute reaction wood self.external_layer = 0 #// index in vector cambial::pool #TEST self.layers = [] self.nlayers = 0 self.total_second_moment_of_area = 0. self.pre_harvest_mass = 0.0 #in g #// used to compute rotation_memory self.pre_harvest_radius = 0 #in meters self.pre_harvest_rotation = Vector3(0.0, 0.0, 0.0) """ // The conditional on 'number' is to get around a bug in LPFG. // LPFG creates lots of temporary instances of objects that are // never used. We only want the constructor to have an effect // when the object is one that is actually used in the structure // and not a temporary object. """ if (self.number): self.season_initial_heading = self.hlu.heading l = cambial_layer(thickness=self.radius, radius=0) self.layers.append(l) self.nlayers += 1 #The following attributes were added by Han on 29-04-2011 #They are mainly used in the "attribute_list" for statistics output self.leaf_state = '' self.leaf_area = 0 #Leaf area returned from plangGL after light interception self.ta_pgl = 0 #Silhouette area returned from plantGL after light interception self.sa_pgl = 0 self.star_pgl = 0 #Added by Han on 11-07-2012, as a flag to control first-year sylleptic growth self.sylleptic = False #Flag to set if a metamer should be pruned self.to_prune = False #Flag to signal a cut self.cut = False
def reaction_wood_target(up, heading, previous_heading): r"""Reaction wood target The reaction wood is proportional to the change in inclination over a season (Almeras, 2001). Hypothesis: The reaction wood is also proportional to the inclination from gravity. .. math:: P_r = 0.1635 - 0.1778 \theta_s where :math:`P_r` is the radial portion of the outermost cambial layer that become reaction wood and :math:`\theta_s` is the change in inclination of the internode over the season (i.e., the cahnge in :math:`\vec{H}`, the heading vector of the HLU frame since the start of the spring. In order to also take into account the gravity, The firs term in the equation above must be multiply by a coefficient that varies with the angle between the internode and :math:`\vec{u}` a unit vector opposite to :math:`-\vec{g}` :param Vector3 up: :param heading: vector3 :param previous_heading: vector3 :returns: reaction_wood (double) """ #multiply by gravity normalised cos_gh = Vector3(0.0, 0.0, 1.0) * heading cos_pu = previous_heading * up cos_ph = previous_heading * heading if cos_pu * cos_ph >= 0.0: try: inclination = acos(cos_ph / 1.0001) except: print 'Problem with acos(cos_ph) where cos_ph=%f' % cos_ph inclination = 0. else: try: inclination = -acos(cos_ph) except: tol = 1e-6 try: inclination = -acos(cos_ph - tol) ValueError('try again with tol set %s %s' % (cos_ph, cos_ph - 1.)) except: print ' cos+_pu=', cos_pu, ' cos_ph=', cos_ph, 'cosgh=', cos_gh print cos_ph - 1. raise ValueError('Problem with acos(cos_ph) where cos_ph=%f' % cos_ph) percentage = 0.1635 * (1.0 - cos_gh) - 0.1778 * inclination r = constants.two_pi * percentage #if r!=0: # print ' cos+_pu=', cos_pu, ' cos_ph=', cos_ph, ' incl=',inclination, ' percentage', percentage, 'r=', r if (r < 0.0): r = 0.0 elif (r > constants.pi): r = constants.pi return r
def test_operator(): v1 = Vector3(1.0, 0, 0) v2 = Vector3(0, 1, 0) assert v1+v2 == Vector3(1,1,0) assert v1-v2 == Vector3(1,-1,0) assert v1*2 == Vector3(2,0,0)
from openalea.stocatree.tools.simulation import SimulationStocatree from openalea.stocatree.sequences import Markov, generate_sequence, terminal_fate from openalea.stocatree.metamer import metamer_data from openalea.stocatree.growth_unit import growth_unit_data from openalea.sequence_analysis import HiddenSemiMarkov from vplants.plantgl.all import Vector3, cross, Viewer from openalea.stocatree.srandom import boolean_event from openalea.stocatree.physics import rotate_frame_at_branch, rupture from openalea.stocatree.tools.surface import * from openalea.stocatree import get_shared_data import time import os import datetime gravity = Vector3(0.0, 0.0, -9.81) #// in m s^-2 original mappleT # First, read the configuration file options = ConfigParams(get_shared_data('stocatree.ini')) # Then, define a data structure to store outputs such as MTG, counts, sequences and so on data = Data(options=options, revision=__revision__) # Initialise the simulation simulation = SimulationStocatree(dt=options.general.time_step, starting_date=options.general.starting_year, ending_date=options.general.end_year) # Read PGLshape surfaces stride = int(options.stocatree.stride_number)