Changeset 8152


Ignore:
Timestamp:
Mar 15, 2011, 6:02:19 PM (13 years ago)
Author:
steve
Message:

Working towards passing unit tests

Location:
trunk/anuga_core/source/anuga/operators
Files:
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/anuga_core/source/anuga/operators/kinematic_viscosity.py

    r8135 r8152  
    1 
    21from anuga import Domain
    32from anuga import Quantity
     
    3635    """
    3736
    38     def __init__(self, domain, diffusivity = None, triangle_areas=True, verbose=False):
     37    def __init__(self, domain, diffusivity = None, use_triangle_areas=True, verbose=False):
    3938        if verbose: log.critical('Kinematic Viscosity: Beginning Initialisation')
    4039        #Expose the domain attributes
     
    4342        self.mesh = domain.mesh
    4443        self.boundary = domain.boundary
    45 
     44        self.boundary_enumeration = domain.boundary_enumeration
     45       
    4646        # Setup diffusivity quantity
    4747        if diffusivity is None:
    4848            diffusivity = Quantity(domain)
    4949            diffusivity.set_values(1.0)
     50            diffusivity.set_boundary_values(1.0)
    5051        #check that diffusivity is a quantity associated with domain
    5152        assert diffusivity.domain == domain
    5253
    5354        self.diffusivity = diffusivity
    54         self.diffusivity_bdry_data = self.diffusivity.boundary_values
    55         self.diffusivity_data = self.diffusivity.centroid_values
     55        #self.diffusivity_bdry_data = self.diffusivity.boundary_values
     56        #self.diffusivity_cell_data = self.diffusivity.centroid_values
    5657
    5758
     
    6162        self.tot_len = self.n + self.boundary_len
    6263
    63         # FIXME SR: maybe this should already exist in mesh!
    64         self.boundary_enum = self.enumerate_boundary()
    65        
    6664        self.verbose = verbose
    6765
     
    7371
    7472        # Only needs to built once, doesn't change
    75         kinematic_viscosity_ext.build_geo_structure(self, self.n, self.tot_len)
     73        kinematic_viscosity_ext.build_geo_structure(self)
    7674
    7775        # Setup type of scaling
    78         self.apply_triangle_areas = triangle_areas
     76        self.set_triangle_areas(use_triangle_areas)       
    7977
    8078        # FIXME SR: should this really be a matrix?
     
    8482           
    8583        self.triangle_areas = Sparse_CSR(temp)
     84        #self.triangle_areas
    8685
    8786        # FIXME SR: More to do with solving equation
    8887        self.qty_considered = 1 #1 or 2 (uh or vh respectively)
    89 
    90         #FIXME SR: Do we need Sparse or should we just go with Sparse_CSR
    91         self.operator_matrix = Sparse(self.n, self.tot_len)
    9288
    9389        #Sparse_CSR.data
     
    9894        self.operator_rowptr = 4 * num.arange(self.n + 1)
    9995
    100         # Build matrix self.elliptic_operator_matrix [A B]
    101         self.build_elliptic_operator()
     96        # Build matrix self.elliptic_matrix [A B]
     97        self.build_elliptic_matrix()
    10298
    10399        # Build self.boundary_term
    104         self.build_operator_boundary_term()
     100        #self.build_elliptic_boundary_term()
    105101
    106102        self.parabolic_solve = False #Are we doing a parabolic solve at the moment?
     
    108104        if verbose: log.critical('Kinematic Viscosity: Initialisation Done')
    109105
     106    def set_triangle_areas(self,flag=True):
     107
     108        self.apply_triangle_areas = flag
    110109       
    111     def enumerate_boundary(self):
    112         #Enumerate by boundary index
    113         # FIXME SR: Probably should be part of mesh
    114         enumeration = {}
    115         for i in range(self.n):
    116             for edge in range(3):
    117                 j = self.mesh.neighbours[i, edge]
    118                 if j < 0:
    119                     enumeration[(i, edge)] = -j-1
    120         return enumeration
    121110
    122111    def set_qty_considered(self, qty):
     
    130119            assert 0 == 1, msg
    131120
    132     def build_elliptic_operator(self):
     121    def build_elliptic_matrix(self):
    133122        """
    134123        Builds matrix representing
     
    141130        #Arrays self.operator_data, self.operator_colind, self.operator_rowptr
    142131        # are setup via this call
    143         kinematic_viscosity_ext.build_operator_matrix(self,self.diffusivity_data, self.diffusivity_bdry_data)
    144 
    145         self.elliptic_operator_matrix = Sparse_CSR(None, \
     132        kinematic_viscosity_ext.build_elliptic_matrix(self, \
     133                self.diffusivity.centroid_values, \
     134                self.diffusivity.boundary_values)
     135
     136        self.elliptic_matrix = Sparse_CSR(None, \
    146137                self.operator_data, self.operator_colind, self.operator_rowptr, \
    147138                self.n, self.tot_len)
     139
     140        #print 'elliptic_matrix'
     141        #print self.elliptic_matrix
    148142
    149143#        #Set up the scaling matrix
     
    153147#            Sparse_CSR(None, self.diffusivity_data, num.arange(self.n), num.arange(self.n +1), self.n, self.n)
    154148
    155     def update_elliptic_operator(self):
     149    def update_elliptic_matrix(self):
    156150        """
    157151        Updates the data values of matrix representing
     
    165159        # through to the Sparse_CSR matrix.
    166160
    167         kinematic_viscosity_ext.update_operator_matrix(self, self.n, self.tot_len, \
    168           self.diffusivity_data, self.diffusivity_bdry_data)
    169 
    170 
    171 
    172 
    173     def build_operator_boundary_term(self):
     161        kinematic_viscosity_ext.update_elliptic_matrix(self, \
     162                self.diffusivity.centroid_values, \
     163                self.diffusivity.boundary_values)
     164       
     165
     166    def build_elliptic_boundary_term(self,quantity):
    174167        """
    175168        Operator has form [A B] and U = [ u ; b]
     
    187180        X = num.zeros((tot_len,), num.float)
    188181
    189         X[n:] = self.diffusivity_bdry_data
    190         self.boundary_term[:] = self.operator_matrix * X
     182        X[n:] = quantity.boundary_values
     183        self.boundary_term[:] = self.elliptic_matrix * X
    191184
    192185        #Tidy up
     
    195188
    196189
    197     def elliptic_multiply(self, V, qty_considered=None, include_boundary=True):
    198         msg = "(KV_Operator.apply_vector) V vector has incorrect dimensions"
    199         assert V.shape == (self.n, ) or V.shape == (self.n, 1), msg
    200 
    201         if qty_considered != None:
    202             print "Quantity Considered changed!"
    203             self.set_qty_considered(qty_considered)
    204 
    205         D = num.zeros((self.n, ), num.float)
    206         #Change (uh) to (u), (vh) to (v)
    207         X = self.stage_heights_scaling * V
     190    def elliptic_multiply(self, quantity_in, quantity_out=None, include_boundary=True):
     191
     192        n = self.n
     193        tot_len = self.tot_len
     194
     195        V = num.zeros((tot_len,), num.float)
     196        X = num.zeros((n,), num.float)
     197
     198        if quantity_out is None:
     199            quantity_out = Quantity(self.domain)
     200
     201        V[0:n] = quantity_in.centroid_values
     202        V[n:] = 0.0
     203
     204
     205        # FIXME SR: These sparse matrix vector multiplications
     206        # should be done in such a way to reuse memory, ie
     207        # should have a procedure
     208        # matrix.multiply(vector_in, vector_out)
     209
    208210
    209211        if self.apply_triangle_areas:
    210             X = self.triangle_areas * X
    211 
    212         X = num.append(X, num.zeros((self.boundary_len, )), axis=0)
    213         D = self.operator_matrix * X
     212            V[0:n] = self.triangle_areas * V[0:n]
     213
     214
     215        X[:] = self.elliptic_matrix * V
    214216
    215217        if include_boundary:
    216             D += self.boundary_vector[:, self.qty_considered-1]
    217         #make sure we return (uh) not (u), for the solver's benefit
    218         D = self.stage_heights * D
    219 
    220         return num.array(D).reshape(self.n, )
     218            self.build_elliptic_boundary_term(quantity_in)
     219            X[:] += self.boundary_term
     220
     221
     222        quantity_out.set_values(X, location = 'centroids')
     223        return quantity_out
    221224
    222225    #parabolic_multiply(V) = identity - dt*elliptic_multiply(V)
     
    237240        #Multiply out
    238241        X = num.append(X, num.zeros((self.boundary_len, )), axis=0)
    239         D = D - self.dt * (self.operator_matrix * X)
     242        D = D - self.dt * (self.elliptic_matrix * X)
    240243
    241244        return num.array(D).reshape(self.n, )
     
    270273            raise TypeError, msg
    271274        else:
    272             new = self.operator_matrix * new
     275            new = self.elliptic_matrix * new
    273276        return new
    274277
  • trunk/anuga_core/source/anuga/operators/kinematic_viscosity_ext.c

    r8135 r8152  
    6363                        // Edge
    6464                        if (j < 0 ) {
    65                                 m = -j - 1;
     65                m = -j - 1;
    6666                                geo_indices[3*i+edge] = n + m;
    6767                                edge_counted++;
     
    8686
    8787
    88 int build_operator_matrix(int n,
     88int build_elliptic_matrix(int n,
    8989                          int tot_len,
    9090                          long *geo_indices,
     
    132132}
    133133
    134 int update_operator_matrix(int n,
     134int update_elliptic_matrix(int n,
    135135                          int tot_len,
    136136                          long *geo_indices,
     
    194194   
    195195    //Convert Python arguments to C
    196     if (!PyArg_ParseTuple(args, "Oii", &kv_operator)) {
     196    if (!PyArg_ParseTuple(args, "O", &kv_operator)) {
    197197        PyErr_SetString(PyExc_RuntimeError, "build_geo_structure could not parse input");
    198198        return NULL;
     
    241241}
    242242
    243 static PyObject *py_build_operator_matrix(PyObject *self, PyObject *args) {
     243static PyObject *py_build_elliptic_matrix(PyObject *self, PyObject *args) {
    244244        PyObject *kv_operator;
    245245        int n, tot_len, err;
     
    254254        //Convert Python arguments to C
    255255    if (!PyArg_ParseTuple(args, "OOO", &kv_operator, &cell_data, &bdry_data)) {
    256         PyErr_SetString(PyExc_RuntimeError, "build_operator_matrix could not parse input");
     256        PyErr_SetString(PyExc_RuntimeError, "build_elliptic_matrix could not parse input");
    257257        return NULL;
    258258    }
     
    268268        colind = get_consecutive_array(kv_operator,"operator_colind");
    269269       
    270         err = build_operator_matrix(n,tot_len,
     270        err = build_elliptic_matrix(n,tot_len,
    271271                                                                (long *)geo_indices -> data,
    272272                                                                (double *)geo_values -> data,
     
    289289
    290290
    291 static PyObject *py_update_operator_matrix(PyObject *self, PyObject *args) {
     291static PyObject *py_update_elliptic_matrix(PyObject *self, PyObject *args) {
    292292        PyObject *kv_operator;
    293293        int n, tot_len, err;
     
    302302        //Convert Python arguments to C
    303303    if (!PyArg_ParseTuple(args, "OOO", &kv_operator, &cell_data, &bdry_data)) {
    304         PyErr_SetString(PyExc_RuntimeError, "update_operator_matrix could not parse input");
     304        PyErr_SetString(PyExc_RuntimeError, "update_elliptic_matrix could not parse input");
    305305        return NULL;
    306306    }
     
    316316        colind = get_consecutive_array(kv_operator,"operator_colind");
    317317
    318         err = update_operator_matrix(n,tot_len,
     318        err = update_elliptic_matrix(n,tot_len,
    319319                                                                (long *)geo_indices -> data,
    320320                                                                (double *)geo_values -> data,
     
    339339static struct PyMethodDef MethodTable[] = {
    340340        {"build_geo_structure",py_build_geo_structure,METH_VARARGS,"Print out"},
    341                 {"build_operator_matrix",py_build_operator_matrix,METH_VARARGS,"Print out"},
    342         {"update_operator_matrix",py_update_operator_matrix,METH_VARARGS,"Print out"},
     341                {"build_elliptic_matrix",py_build_elliptic_matrix,METH_VARARGS,"Print out"},
     342        {"update_elliptic_matrix",py_update_elliptic_matrix,METH_VARARGS,"Print out"},
    343343        {NULL,NULL,0,NULL} // sentinel
    344344};
  • trunk/anuga_core/source/anuga/operators/test_kinematic_viscosity.py

    r8135 r8152  
    11from anuga import Domain
     2from anuga import Quantity
    23from anuga import Dirichlet_boundary
    34from kinematic_viscosity import Kinematic_Viscosity
     
    3334
    3435
     36
    3537        #print domain.quantities['stage'].vertex_values
    3638
     
    6365        domain.update_boundary()
    6466
     67
     68
    6569        return Kinematic_Viscosity(domain)
    6670
    6771    def test_enumerate_boundary(self):
    6872        operator1 = self.operator1()
    69         boundary_enum = operator1.boundary_enum
    70 
    71         assert boundary_enum[(0,0)] == 0
    72         assert boundary_enum[(0,1)] == 1
    73         assert boundary_enum[(0,2)] == 2
     73        boundary_enumeration = operator1.domain.boundary_enumeration
     74
     75        assert boundary_enumeration[(0,0)] == 0
     76        assert boundary_enumeration[(0,1)] == 1
     77        assert boundary_enumeration[(0,2)] == 2
    7478
    7579        operator2 = self.operator2()
    76         boundary_enum = operator2.boundary_enum
    77 
    78 
    79         assert boundary_enum[(0,1)] == 0
    80         assert boundary_enum[(0,2)] == 1
    81         assert boundary_enum[(1,0)] == 2
    82         assert boundary_enum[(1,2)] == 3
     80        boundary_enumeration = operator2.domain.boundary_enumeration
     81
     82
     83        assert boundary_enumeration[(0,1)] == 0
     84        assert boundary_enumeration[(0,2)] == 1
     85        assert boundary_enumeration[(1,0)] == 2
     86        assert boundary_enumeration[(1,2)] == 3
    8387
    8488    def test_geo_structure(self):
     
    96100        assert num.allclose(values, num.array([[-3.0,-6.0/sqrt(5),-6.0/sqrt(5)],[-6.0/sqrt(5),-3.0,-6.0/sqrt(5)]]))
    97101
    98     def test_elliptic_matrix(self):
    99         operator1 = self.operator1()
    100         operator2 = self.operator2()
    101 
    102         domain1 = operator1.domain
    103         domain2 = operator2.domain
    104 
    105 
    106         A = operator1.elliptic_operator_matrix
    107 
    108         print A
    109         print num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)])
    110        
     102    def test_elliptic_matrix_one_triangle(self):
     103
     104        operator = self.operator1()
     105        domain = operator.domain
     106       
     107        operator.update_elliptic_matrix()
     108
     109        A = operator.elliptic_matrix
     110
    111111        assert num.allclose(A.todense(), num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)]))
    112112
    113 
    114         A = operator1.elliptic_operator_matrix
    115         assert num.allclose(A.todense(), 1.5*num.array([-6.0-12.0/sqrt(5), 6.0, 6.0/sqrt(5), 6.0/sqrt(5)]))
    116 
    117         h = num.array([1.0, 1.0])
    118         A = operator2.apply_stage_heights(h)
    119         #From test_kv_system_geometry
     113        diffusivity = operator.diffusivity
     114        diffusivity.set_values(10.0)
     115        diffusivity.set_boundary_values(10.0)
     116       
     117        operator.update_elliptic_matrix()
     118
     119        assert num.allclose(A.todense(), 10*num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)]))
     120   
     121
     122    def test_elliptic_matrix_two_triangles(self):
     123
     124
     125        operator = self.operator2()
     126
     127        domain = operator.domain
     128        diffusivity = operator.diffusivity
     129
     130        A = operator.elliptic_matrix
     131
     132        diffusivity.set_values(1.0)
     133        diffusivity.set_boundary_values(1.0)
     134        operator.update_elliptic_matrix()
     135
    120136        A0 = num.array([[-3.0,3.0,0.0,0.0,0.0,0.0],
    121137                        [0.0,-6.0/sqrt(5.0),0.0,0.0,6.0/sqrt(5.0),0.0]])
     
    124140        A2 = num.array([[-6.0/sqrt(5.0),0.0,0.0,6.0/sqrt(5.0),0.0,0.0],\
    125141                        [0.0, -6.0/sqrt(5.0), 0.0, 0.0, 0.0, 6.0/sqrt(5.0)]])
     142
     143
    126144        assert num.allclose(A.todense(), A0+A1+A2)
    127145
    128         h = num.array([2.0, 1.0])
    129         A = operator2.apply_stage_heights(h)
     146        diffusivity.set_values([2.0, 1.0], location = 'centroids')
     147        diffusivity.set_boundary_values(1.0)
     148        operator.update_elliptic_matrix()
     149
     150        A = operator.elliptic_matrix
     151       
     152
    130153        assert num.allclose(A.todense()[0,:], 1.5*A0[0,:]+1.5*A1[0,:]+1.5*A2[0,:])
    131154        assert num.allclose(A.todense()[1,:], A0[1,:]+1.5*A1[1,:]+A2[1,:])
    132155
    133         h = num.array([-2.0, -2.0])
    134         A = operator2.apply_stage_heights(h)
     156        diffusivity.set_values([-2.0, -2.0], location = 'centroids')
     157        diffusivity.set_boundary_values(1.0)
     158        operator.update_elliptic_matrix()
     159
    135160        assert num.allclose(A.todense()[0,:], -2*A0[0,:]-0.5*A1[0,:]-0.5*A2[0,:])
    136161        assert num.allclose(A.todense()[1,:], -0.5*A0[1,:]-2*A1[1,:]-0.5*A2[1,:])
    137162
    138     def test_elliptic_multiply(self):
    139         operator1 = self.operator1()
    140         operator1.apply_stage_heights(num.array([[1.0]])) #h=1
    141         operator1.build_boundary_vector()
    142         V1 = num.array([2.0]) #(uh)=2 <- Centriod value
    143         V2 = num.array([2.0]) #(vh)=2 <- Centroid value
    144         A = num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)])
    145         U1 = num.array([[2.0],[2.0],[1.0],[1.0]]) #(uh) for centroid, 3 edges
    146         U2 = num.array([[2.0],[1.0],[2.0],[0.0]]) #(vh) for centroid, 3 edge
    147 
    148         operator1.set_qty_considered('u')
    149         D1 = operator1.elliptic_multiply(V1)
    150         assert num.allclose(D1, 2*num.array(num.mat(A)*num.mat(U1)).reshape(1,)) #2* for triangle_areas
    151 
    152         operator1.set_qty_considered(2)
    153         D2 = operator1.elliptic_multiply(V2)
    154         assert num.allclose(D2, 2*num.array(num.mat(A)*num.mat(U2)).reshape(1,)) #2* for triangle_areas
     163    def test_elliptic_multiply_include_boundary_one_triangle(self):
     164        operator = self.operator1()
     165        operator.set_triangle_areas(False)
     166
     167        print operator.apply_triangle_areas
     168       
     169        n = operator.n
     170
     171        q_in = Quantity(operator.domain)
     172        q_in.set_values(1.0)
     173        q_in.set_boundary_values(1.0)
     174        operator.update_elliptic_matrix()
     175
     176       
     177        A = num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)])
     178
     179
     180        q_1 = operator.elliptic_multiply(q_in)
     181
     182        q_2 = operator.elliptic_multiply(q_in, quantity_out = q_in)
     183
     184        assert id(q_in) == id(q_2)
     185
     186        assert num.allclose(q_1.centroid_values,q_2.centroid_values)
     187
     188        assert num.allclose( num.zeros((n,), num.float), q_1.centroid_values )
     189
     190        #Now have different boundary values
     191
     192        q_in.set_values(1.0)
     193        q_in.set_boundary_values(0.0)
     194        operator.update_elliptic_matrix()
     195
     196
     197        A = num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)])
     198
     199
     200        q_1 = operator.elliptic_multiply(q_in)
     201
     202        assert num.allclose( [-6.0-12.0/sqrt(5)], q_1.centroid_values )
     203       
     204    def test_elliptic_multiply_exclude_boundary_one_triangle(self):
     205        operator = self.operator1()
     206        operator.set_triangle_areas(False)
     207
     208        print operator.apply_triangle_areas
     209        #n = operator.n
     210
     211        q_in = Quantity(operator.domain)
     212        q_in.set_values(1.0)
     213        q_in.set_boundary_values(1.0)
     214        operator.update_elliptic_matrix()
     215
     216
     217        A = num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)])
     218
     219
     220        q_1 = operator.elliptic_multiply(q_in, include_boundary=False)
     221
     222
     223        assert num.allclose( [-6.0-12.0/sqrt(5)], q_1.centroid_values )
     224
     225    def test_elliptic_multiply_include_boundary_one_triangle(self):
     226        operator = self.operator1()
     227        operator.set_triangle_areas(True)
     228
     229        n = operator.n
     230
     231        q_in = Quantity(operator.domain)
     232        q_in.set_values(1.0)
     233        q_in.set_boundary_values(1.0)
     234        operator.update_elliptic_matrix()
     235
     236
     237        A = num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)])
     238
     239
     240        q_1 = operator.elliptic_multiply(q_in)
     241
     242        q_2 = operator.elliptic_multiply(q_in, quantity_out = q_in)
     243
     244        assert id(q_in) == id(q_2)
     245
     246        assert num.allclose(q_1.centroid_values,q_2.centroid_values)
     247
     248        assert num.allclose( num.zeros((n,), num.float), q_1.centroid_values )
     249
     250        #Now have different boundary values
     251
     252        q_in.set_values(1.0)
     253        q_in.set_boundary_values(0.0)
     254        operator.update_elliptic_matrix()
     255
     256
     257        A = num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)])
     258
     259
     260        q_1 = operator.elliptic_multiply(q_in)
     261
     262        assert num.allclose( [-12.0-24.0/sqrt(5)], q_1.centroid_values )
     263
     264    def test_elliptic_multiply_exclude_boundary_one_triangle(self):
     265        operator = self.operator1()
     266        operator.set_triangle_areas(True)
     267
     268        q_in = Quantity(operator.domain)
     269        q_in.set_values(1.0)
     270        q_in.set_boundary_values(1.0)
     271        operator.update_elliptic_matrix()
     272
     273
     274        A = num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)])
     275
     276
     277        q_1 = operator.elliptic_multiply(q_in, include_boundary=False)
     278
     279
     280        assert num.allclose( [-12.0-24.0/sqrt(5)], q_1.centroid_values )
     281
     282
    155283
    156284    def test_mul(self):
    157         operator1 = self.operator1()
    158         operator1.apply_stage_heights(num.array([[1.0]])) #h=1
    159         operator1.set_qty_considered(1)
    160         operator1.build_boundary_vector()
     285        operator = self.operator1()
     286
     287        q = Quantity(operator.domain)
     288        q.set_values(2.0)
     289        #q boundary_values should equal 0.0
     290
     291        operator.build_elliptic_boundary_term()
    161292        A = num.array([-6.0-12.0/sqrt(5), 6.0,  6.0/sqrt(5), 6.0/sqrt(5)])
    162293        V1 = num.array([2.0]) #(uh)=2
    163294        U1 = num.array([[2.0],[0.0],[0.0],[0.0]])
    164         assert num.allclose(operator1 * V1, 2*num.array(num.mat(A)*num.mat(U1)).reshape(1,))
     295        assert num.allclose(operator * q, 2*num.array(num.mat(A)*num.mat(U1)).reshape(1,))
    165296
    166297    def test_cg_solve(self):
Note: See TracChangeset for help on using the changeset viewer.