Saturday, May 9, 2009
Final Post
We took the last exam in class yesterday, and I'm not quite sure how it went. As far as the two questions about reflection in Java and Python, I did fine, but I'm afraid I didn't quite remember everything I needed to for the "Tease apart inheritance" question. Maybe that's because I didn't bother to review the Refactoring book too much in depth. We got the final grade for our TA4 submission, and I was very pleased with that. It's been a very productive semester, and I feel like I've learned quite a lot from this class. Farewell CS373...
Thursday, May 7, 2009
May 4 - 8
NEWS FLASH: Well, I got my official job offer from IBM this past Tuesday, so I'm pretty excited about that. I'm looking taking a break from class and having my evenings and weekends free again. This constant grind of HW, projects, and studying for exams this semester has really drained me, and I need a break.
This past weekend we made the final submission for the TA matching project. We already had a lot of the required functionality implemented from previous phases. However, we had a few outstanding bugs to fix with the SMP, and a couple other minor things.
Looking back after it's all done, this project was probably the most valuable experience of my undergraduate experience in the CS department. Working with a team to design, implement, and test a (somewhat simple) application is more closely related to what we'll be doing in the real world than the typical "black box" type of spec that we're so often given on projects.
I'm also glad the Downing forces everyone to use Subversion on every project. This was my first time to use Subversion, and I wrote a short post about basic Subversion Stuff if you're just getting started with it. By the way, I also wrote a post about setting up SVN+SSH with Subclipse. I wish I'd been shown some sort of version control tool in my first computer science class here, to be honest. Although, I probably wouldn't have appreciated the value of it at that time.
As far as the future of our webapp, I'd like to transfer it to my own google app engine account and possibly continue to work on it when I feel like it(now that I'm going to have a bit more free time!!!).
This past weekend we made the final submission for the TA matching project. We already had a lot of the required functionality implemented from previous phases. However, we had a few outstanding bugs to fix with the SMP, and a couple other minor things.
Looking back after it's all done, this project was probably the most valuable experience of my undergraduate experience in the CS department. Working with a team to design, implement, and test a (somewhat simple) application is more closely related to what we'll be doing in the real world than the typical "black box" type of spec that we're so often given on projects.
I'm also glad the Downing forces everyone to use Subversion on every project. This was my first time to use Subversion, and I wrote a short post about basic Subversion Stuff if you're just getting started with it. By the way, I also wrote a post about setting up SVN+SSH with Subclipse. I wish I'd been shown some sort of version control tool in my first computer science class here, to be honest. Although, I probably wouldn't have appreciated the value of it at that time.
As far as the future of our webapp, I'd like to transfer it to my own google app engine account and possibly continue to work on it when I feel like it(now that I'm going to have a bit more free time!!!).
April 27 - May 1
CHANGE OF PLANS... Well, originally I had decided to take summer class and graduate December 09. However, in response to a recent email about a 7-month co-op at IBM from June 1 to December 31, I emailed my resume to the guys at IBM here in Austin. Later that afternoon I found myself in an 80 minute phone interview, and a few days later I made a visit to the IBM campus in north Austin. The job is a "Software Support Engineer", and the 7-month work experience will be a much needed break from class. If this opportunity pans out, I'll won't return to classes here until Spring 2010, but I'll return refreshed and re-energized for my last semester. I'll keep my fingers crossed.
April 20 - 24
We submitted the third phase of the project last Sunday, and this week we began looking at a few Design Patterns. We're tying refactoring in with these well-known design patterns. Apparently, Downing explores that design patterns book a lot more in-depth in his CS371p - Object Oriented Programming course. In fact I signed up to take that course in the upcoming fall semester. Glenn Downing definitely has a unique teaching style that I really appreciate, and I look forward to the opportunity of taking another of his courses.
April 13 - 17
This week was another day of presentations and some SQL stuff. Our TA app is definitely the most minimalistic as far as looks go, but at least we have a solid backend. Oh well, that's what graphic designers are for, to make things look pretty...my job is just to make it work.
April 6 - 10
Last weekend my team submitted the second phase of our TA project. After hours and hours of refactoring, we had a code-base that was much more scalable and provided for a more rapid development cycle when we needed to add functionality. Google conveniently includes the Django template system as an option in the app engine. We decided to add this to our application because it allowed us to separate the presentation and business logic a bit more.
New additions to this phase were:
Due to the overwhelming amount of refactoring that we did, we ended up missing the deadline on this submission by a day. Oh well, it was worth it to have more readable and modular code.
New additions to this phase were:
- introduce the notion of time into the app
- admin initializes system by choosing a semester, setting up classes (uniques), etc.
- applicants apply
- instructors apply
- admin asks for a match, overrides some matches, and finalizes (for now, fake the match)
- create a database schema consisting of models with properties and relationships and the ability to make queries:
- models
- properties
- relationships
- queries
- enter a set of test data
- pick a member of the group to give a demo in class of your models, properties, and relationships
Due to the overwhelming amount of refactoring that we did, we ended up missing the deadline on this submission by a day. Oh well, it was worth it to have more readable and modular code.
March 30 - April 3
This week consisted of presentations from the teams in my section of the course. I really like seeing the other teams' apps because it lets us each show off the various technologies and frameworks that we used. Unfortunately, none of us on my team had really done any real web programming, so needless to say, our app was a bit lacking in many areas. Once we saw the other apps and got some better ideas, we decided to put our newly acquired Refactoring knowledge to work and do a major overhaul of our code. More on that next week...
March 23 - 27
We began reading the Refactoring text this week. It seems like the techniques in the text will be very helpful, especially as the TA matching projects progress.
My group made our first submission for the TA project this Wednesday. Luckily the deadline was extended an extra three days because of spring break. In spite of the extension, my group ended up spending the majority of Monday, Tuesday, and Wednesday in the Taylor basement lab trying to finish all of the requirements. The objectives of this phase were to:
By the way, every group's webapp will be developed on the Google App Engine platform using Python. Here's a pointer to my team's app.
My group made our first submission for the TA project this Wednesday. Luckily the deadline was extended an extra three days because of spring break. In spite of the extension, my group ended up spending the majority of Monday, Tuesday, and Wednesday in the Taylor basement lab trying to finish all of the requirements. The objectives of this phase were to:
- convert the informal description into a formal specification
- create a series of web forms to collect the required input
- create a series of unit tests of each field of every form
- pick a member of the group to give a demo in class of your forms and their validation
By the way, every group's webapp will be developed on the Google App Engine platform using Python. Here's a pointer to my team's app.
March 9 - 13
We finally finished up the DB Design book this week. Marcus Marler, a web designer for the UT Comp. Sciences dept., also came to class to demo his TA matching system that he designed for the department. The significance of his visit is that he was showing us the fully implemented system that each group in our class would (attempt to) recreate over the next eight weeks.
The purpose of the system is to match graduate students who apply to TA a CS course with courses that need TA's, using the Stable Marriage Problem algorithm, which happened to be individual project that everyone just submitted last weekend. The system has three types of users: Administrators, Faculty, and TA applicants. As the project unfolds, I'll share more.
The purpose of the system is to match graduate students who apply to TA a CS course with courses that need TA's, using the Stable Marriage Problem algorithm, which happened to be individual project that everyone just submitted last weekend. The system has three types of users: Administrators, Faculty, and TA applicants. As the project unfolds, I'll share more.
March 2 - 6
We're STILL reading that wonderful DB design book this week. On a happier note, I've learned about another nifty little gem in the language of Python this week's examples. We looked at Python generator types(amongst other things). Similar to my last post, using Python Generators gives another type of persistent storage for the function. Take a look at this example:
This simple function generates all the integers in the range [b, e). This "yield" machinery seems to function like a sort of stored program counter for the function. Each time the generator is prompted for the next value, the program counter resumes where it left off.
def f (b, e) :
while b < e :
yield b
b += 1
This simple function generates all the integers in the range [b, e). This "yield" machinery seems to function like a sort of stored program counter for the function. Each time the generator is prompted for the next value, the program counter resumes where it left off.
Feb. 23 - 27
The reading assignments for this week are still from the online "DB Design" text. One of the more interesting Python examples we've looked at this week was about function defaults. Take a look at this example:
Now, if I were to make these two calls:
This mutable default array lives on as a sort of memory for the function, which isn't exactly what I would have expected.
Now, if I were to make another call to
and one final call:
results in
So it seems that the persistent array is actually persistent and surfaces any time an argument isn't give.
Although this strange behaviour might introduce a bug into an unsuspecting programmer's function, it can also be a very attractive way of giving a function some persistent storage...possibly a useful feature if you wanted to write something like a primes generator.
def h1 (y = []) :
assert(type(y) is types.ListType)
y += [2]
return y
Now, if I were to make these two calls:
h = h1()
h = h1()
h
would be equal to [2, 2]
This mutable default array lives on as a sort of memory for the function, which isn't exactly what I would have expected.
Now, if I were to make another call to
h1([1])
with an argument, the default array gets wiped away. To be clear, at the end of this sequence of statements:
h = h1()
h = h1()
h = h1([1])
h
now equals [1, 2]
and one final call:
h = h1()
results in
h
equal to [2, 2, 2]
So it seems that the persistent array is actually persistent and surfaces any time an argument isn't give.
Although this strange behaviour might introduce a bug into an unsuspecting programmer's function, it can also be a very attractive way of giving a function some persistent storage...possibly a useful feature if you wanted to write something like a primes generator.
Monday, April 13, 2009
Django Templates
For the first leg of the TA matching project, we made a rather lacking attempt at the project. Our submission was not lacking in the sense of required functionality, but rather maintainability and sound software engineering practices. Our Python code was cluttered throughout with snippets of:
Then I discovered the magic of Django templates. Django is an open-source web application framework. Apparently it features "...an object-relational mapper which mediates between data models (defined as Python classes) and a relational database, a regular-expression-based URL dispatcher, a view system for processing requests, and a templating system." (source Wikipedia). My group only used the templating system, which is include in the Google
For you to get started with Django templates in the Google App Engine infrastructure, this is what you need to know:
First of all, add the following two import statements to your Python file.
Now create your html template file, mine is called
Now the other part of the puzzle is your actual login.py file. When we started the second sprint of the TA matching project, our top priority was to refactor our code into something that was maintainable and scalable. Part of that refactoring included adding the Django template system to our project. Another crucial part that tied into the templates very well was the creation of a Field class for all html form input fields. Basically, there are subclasses of Field for each type of input field, and each field holds:
Let's look at the login.py file section by section:
Here's the login.py file in its entirety.
The combined use of Django templates and our own Field class together has made additions and modifications of our existing code a very rapid and streamlined process.
self.response.out.write("""<form action="/login" method="post" >""")
self.response.out.write("""<input type="text" name="user_name">User Name:</input>
Then I discovered the magic of Django templates. Django is an open-source web application framework. Apparently it features "...an object-relational mapper which mediates between data models (defined as Python classes) and a relational database, a regular-expression-based URL dispatcher, a view system for processing requests, and a templating system." (source Wikipedia). My group only used the templating system, which is include in the Google
webapp
module. For you to get started with Django templates in the Google App Engine infrastructure, this is what you need to know:
First of all, add the following two import statements to your Python file.
import os
from google.appengine.ext.webapp import template
Now create your html template file, mine is called
mainTemplate.html
, in the same directory as your Python file, with the following content.
<html>
<body>
<b>Hello {{ user }}</>
<form action="/login" method="post">
{% for field in formFields %}
{{ field.printHTML }}
{% endfor %}
<input type="submit" />
</form>
</body>
</html>
Now the other part of the puzzle is your actual login.py file. When we started the second sprint of the TA matching project, our top priority was to refactor our code into something that was maintainable and scalable. Part of that refactoring included adding the Django template system to our project. Another crucial part that tied into the templates very well was the creation of a Field class for all html form input fields. Basically, there are subclasses of Field for each type of input field, and each field holds:
- title text to display info about the field
- an error message to display when the input isn't valid
- the html name that's embedded in the html
- a particular Validator object that validates the input (there are many subclasses of Validator that validate specific type of input)
- a method to print the html for the field
- a method to get the user's input from the request
Let's look at the login.py file section by section:
- import statements (already gone over this)
- constructor
class MainPage (webapp.RequestHandler) :
def __init__ (self) :
# holds all the Fields for the form
self.formFields = []
self.user = ""
# -----
# login
# -----
self.loginTextBox = Field.TextBoxField()
self.loginTextBox.fieldTitle = "Please log in"
self.loginTextBox.errorMessage = " -- Invalid login"
self.loginTextBox.htmlName = "login"
self.loginTextBox.validator = Validator.NameValidator()
self.formFields.append(self.loginTextBox)
Create the formFields list that gets linked to the html template, and add as many Field objects to it as you like (here I've only added one for simplicity).
- get method
def get (self) :
# create a dictionary of values to be referenced in the template
template_values = { 'formFields': self.formFields, 'user' : self.user }
# point to the login.html file you created
path = os.path.join(os.path.dirname(__file__), 'login.html')
# takes a file path to the template file and a dictionary
# of values, and returns the rendered text.
self.response.out.write(template.render(path, template_values))
Here's where the list of fields 'self.formFields' in my python file gets linked to 'formFields' in the html template and 'self.user' gets linked to 'user' in the html template. So when the template is evaluated,{% for field in formFields %}
gets executed just like a regular for loop in python, and{{ field.printHTML }}
references theprintHTML
attribute of each Field object. In many cases, you can also pass Google datastore model objects directly as values, and access their properties from templates.
- post method
def post (self) :
"""
Captures User input
"""
# assume all fields are valid, and set to false if one isn't valid
allFieldsValid = True
for field in self.formFields :
field.getUserInput(self.request)
field.validate()
if not field.isValid :
allFieldsValid = False
if allFieldsValid :
self.user = self.formFields[0].input
self.get()
else :
self.get()
Here I'm just processing the user's input from the text field, but note the lineself.user = self.formFields[0].input
. This line now gives real meaning to the{{ user }}
in the login.html file. If the page validates correctly, the resulting page now dynamically inserts the validated user's name into the html.
Here's the login.py file in its entirety.
# --------
# login.py
# --------
import cgi
from google.appengine.ext import webapp
# need these two lines templates to work
import os
from google.appengine.ext.webapp import template
import Field # our own Field class
import Validator # our own Validator class
class MainPage (webapp.RequestHandler) :
def __init__ (self) :
# holds all the Fields for the form
self.formFields = []
self.user = ""
# -----
# login
# -----
self.loginTextBox = Field.TextBoxField()
self.loginTextBox.fieldTitle = "Please log in"
self.loginTextBox.errorMessage = " -- Invalid login"
self.loginTextBox.htmlName = "login"
self.loginTextBox.validator = Validator.NameValidator()
self.formFields.append(self.loginTextBox)
def get (self) :
# create a dictionary of values to be referenced in the template
template_values = { 'formFields': self.formFields, 'user' : self.user }
# point to the login.html file you created
path = os.path.join(os.path.dirname(__file__), 'login.html')
# takes a file path to the template file and a dictionary
# of values, and returns the rendered text.
self.response.out.write(template.render(path, template_values))
def post (self) :
"""
Captures User input
"""
# assume all fields are valid, and set to false if one isn't valid
allFieldsValid = True
for field in self.formFields :
field.getUserInput(self.request)
field.validate()
if not field.isValid :
allFieldsValid = False
if allFieldsValid :
self.user = self.formFields[0].input
self.get()
else :
self.get()
if __name__ == "__main__":
main()
The combined use of Django templates and our own Field class together has made additions and modifications of our existing code a very rapid and streamlined process.
Tuesday, February 24, 2009
Dead week
Well, with the first midterm and project 3 both out of the way, I'm really concentrating on my other class. The timing of projects and and exams is working out well so far between my Automata Theory, Graphics, Software Engineering, and Algebraic Structures classes.
Project 2, the Australian Voting Problem, was done with Joel Gardner. I hosted our repository on my home linux server once again (which makes collaboration very simple), and everything went smoothly. This was the first project that I've ever produced a UML doc, but after about five minutes of experimenting with DIA, we were able to begin creating our doc.
This next project, the Stable Marriage Problem, is an interesting problem that supposedly arose from the need to pair graduating medical school interns with available hospital jobs. I'll be working with Felicia Hopperstad, and hopefully we'll get an early start on it and knock it out a week early.
Looking a bit into the future, after project 4, the remaining four projects will be done as a team of 3-5 people. So far, Felicia, Joel, Sam Kinard, and myself are grouped up. I'm not sure if another group member or two will be added, but either way, I think we'll have quite a productive group. As required by the spec, our projects will be hosted at code.google.com. Speaking of Google's project hosting, Raleigh Schickel and I were chatting today in the Taylor basement, and the subject of project hosting came up. We were checking out Google's project hosting, and I was curious if the source code of your project public as soon as it's hosted in Google's repository (seems like it would since number 7 on Google's Corporate Philosophy is: "7. There's always more information out there."). Well, it turns out, that a read-only copy of your source code is, in fact available to anyone with internet access. A quick search pulls up three hosted projects, and by the user names, I'm almost certain the projects are fellow members of this class. Seeing as academic dishonesty is a very serious matter, and we are explicitly told "You may not share code in any way with your fellow students," I would be very wary of hosting these first four projects through Google, because a fellow student who just can't crack that tough algorithm an hour before the next project is due might just give in and attempt to use yours. You didn't directly give it to them, but you made yourself liable by hosting your project in a way that it's fair game to anyone.
Project 2, the Australian Voting Problem, was done with Joel Gardner. I hosted our repository on my home linux server once again (which makes collaboration very simple), and everything went smoothly. This was the first project that I've ever produced a UML doc, but after about five minutes of experimenting with DIA, we were able to begin creating our doc.
This next project, the Stable Marriage Problem, is an interesting problem that supposedly arose from the need to pair graduating medical school interns with available hospital jobs. I'll be working with Felicia Hopperstad, and hopefully we'll get an early start on it and knock it out a week early.
Looking a bit into the future, after project 4, the remaining four projects will be done as a team of 3-5 people. So far, Felicia, Joel, Sam Kinard, and myself are grouped up. I'm not sure if another group member or two will be added, but either way, I think we'll have quite a productive group. As required by the spec, our projects will be hosted at code.google.com. Speaking of Google's project hosting, Raleigh Schickel and I were chatting today in the Taylor basement, and the subject of project hosting came up. We were checking out Google's project hosting, and I was curious if the source code of your project public as soon as it's hosted in Google's repository (seems like it would since number 7 on Google's Corporate Philosophy is: "7. There's always more information out there."). Well, it turns out, that a read-only copy of your source code is, in fact available to anyone with internet access. A quick search pulls up three hosted projects, and by the user names, I'm almost certain the projects are fellow members of this class. Seeing as academic dishonesty is a very serious matter, and we are explicitly told "You may not share code in any way with your fellow students," I would be very wary of hosting these first four projects through Google, because a fellow student who just can't crack that tough algorithm an hour before the next project is due might just give in and attempt to use yours. You didn't directly give it to them, but you made yourself liable by hosting your project in a way that it's fair game to anyone.
Sunday, February 15, 2009
SVN+SSH with Subclipse
Now that I've got Subversion working like a charm on my server, I'd like to share my experience with setting up Subclipse on my Windows 7 desktop. Although most of my dev work is done on my Asus netbook running Ubuntu 8.10, I occasionally like to take advantage of my 28" widescreen attached to my desktop. I installed TortoiseSVN, available here. Then, I installed subclipse, available here. Make sure you download the appropriate version of subclipse for your version of eclipse. Once that's installed, you need to do a couple things:
1. Set the following environment variable (by right-clicking on Computer, Properties, Advanced system settings, Environment Variables, New):-
to use SVNKit(Pure Java) SVNKit v1.2.1.5297.
3. I restarted Eclipse.
Upon restarting everything worked flawlessly. To create a new project by SVN+SSH using a repository on my server, press Ctrl-N, click on SVN, and "Checkout Projects from SVN". I entered the URL to my repository, and had a working copy on my local machine soon after.
1. Set the following environment variable (by right-clicking on Computer, Properties, Advanced system settings, Environment Variables, New):-
Variable name: SVN_SSH
Variable value: C:\\Program Files\\TortoiseSVN\\bin\\TortoisePlink.exe
to use SVNKit(Pure Java) SVNKit v1.2.1.5297.
3. I restarted Eclipse.
Upon restarting everything worked flawlessly. To create a new project by SVN+SSH using a repository on my server, press Ctrl-N, click on SVN, and "Checkout Projects from SVN". I entered the URL to my repository, and had a working copy on my local machine soon after.
Saturday, February 7, 2009
Week 2
Note to self: "Read the assigned readings more thoroughly, pay attention to a few obscure things from the readings, and be prepared to answer rather vague questions over said readings for the quizzes." I'm quickly realizing that the daily quizzes are going to be my biggest problem in the course because the questions tend to be very vague.
On another note, string indexing and capturing a substring is rather interesting in Python.
Substrings can be captured in a manner that probably seems normal to most people. The indexing is an inclusive/exclusive index (the beginning index is included, but the ending index is not).
On another note, string indexing and capturing a substring is rather interesting in Python.
Substrings can be captured in a manner that probably seems normal to most people. The indexing is an inclusive/exclusive index (the beginning index is included, but the ending index is not).
s = "abCbA"
self.assert_(s[1:4] == "bCb")
self.assert_(s[1:4] is not "bCb")
self.assert_(s[1: ] == "bCbA")
self.assert_(s[1: ] is not "bCbA")
self.assert_(s[ :4] == "abCb")
self.assert_(s[ :4] is not "abCb")
self.assert_(s[0:5] == s)
self.assert_(s[0:5] is s)
self.assert_(s[ : ] == s)
self.assert_(s[ : ] is s)
Here's where things are slightly different. Python allows you to specify a step size in your indexing.
s = "abCbA"Stranger still, Python allows negative indexing. The strings do not, however, function as a circularly linked list. For this example an index of -7 as an ending index would be invalid.
self.assert_(s[1:4: 2] == "bb")
self.assert_(s[0:5: 2] == "aCA")
self.assert_(s[ : : 2] == "aCA")
s = "abCbA"
self.assert_(s[-1] is "A")
self.assert_(s[-4] is "b")
self.assert_(s[-5] is "a")
# self.assert_(s[-6] is "") // out of range
s = "abCbA"
self.assert_(s[-2:-5:-2] == "bb")
self.assert_(s[-1:-6:-2] == "ACa")
self.assert_(s[ : :-2] == "ACa")
Sunday, February 1, 2009
Subversion stuff
Since this is my first project to use subversion on, I figured I could give a few thoughts on how it's gone for me. I'm hosting my repository on an Ubuntu server set up at my apartment.
First of all, installing subversion on my Ubuntu laptop and server was as simple as the command
$ sudo apt-get install subversion
So far, the plan is to setup a separate repository for each project, so I created a directory on my server to hold each of the project repositories in my home folder. (this command was done via ssh on my server)
$ mkdir ~/cs373
Creating a repository in this new directory is now as simple as: (this command was also done via ssh on my server)
$ svnadmin create ~/cs373/proj1
On my laptop I then created a directory called proj1 with all of the initial skeleton files in it.
Once all the files were there, I imported this directory into my repository. The -m flag is a message that's logged with the import
(this was done on my laptop, and proj1 is a folder in the current directory. I've omitted my username and hostname of my server)
$ svn import proj1 svn+ssh://@/home//cs373/proj1 -m "First Import"
Then, when I'm ready to checkout my project to work on it, I ented (on my laptop)
$ svn checkout svn+ssh://@/home//cs373/proj1 proj1
This checks out my project into my current working directory.
When I'm satisfied with changes to my project, I simply enter (on my laptop):
$ svn commit
Well, I guess those are the basics. As I learn more about subversion throughout the semester, I'll share what I learn.
First of all, installing subversion on my Ubuntu laptop and server was as simple as the command
$ sudo apt-get install subversion
So far, the plan is to setup a separate repository for each project, so I created a directory on my server to hold each of the project repositories in my home folder. (this command was done via ssh on my server)
$ mkdir ~/cs373
Creating a repository in this new directory is now as simple as: (this command was also done via ssh on my server)
$ svnadmin create ~/cs373/proj1
On my laptop I then created a directory called proj1 with all of the initial skeleton files in it.
Once all the files were there, I imported this directory into my repository. The -m flag is a message that's logged with the import
(this was done on my laptop, and proj1 is a folder in the current directory. I've omitted my username and hostname of my server)
$ svn import proj1 svn+ssh://
Then, when I'm ready to checkout my project to work on it, I ented (on my laptop)
$ svn checkout svn+ssh://
This checks out my project into my current working directory.
When I'm satisfied with changes to my project, I simply enter (on my laptop):
$ svn commit
Well, I guess those are the basics. As I learn more about subversion throughout the semester, I'll share what I learn.
First Post
This first post is actually my first ever blog post, period. As the course progresses, hopefully I'll have some good experiences to share with the world.
Subscribe to:
Posts (Atom)