From 99d6296b994d049a6ad8d3f13c0b3f2edda1a2fe Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Wed, 29 Aug 2012 15:39:03 -0400 Subject: [PATCH 01/35] Set the course static file dir to /static directories in course repos if the folder exists, and fall back to including the whole course folder otherwise --- lms/envs/common.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index ce08bf9666..61a3abab6d 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -260,12 +260,22 @@ STATICFILES_DIRS = [ PROJECT_ROOT / "askbot" / "skins", ] if os.path.isdir(DATA_DIR): + # Add the full course repo if there is no static directory STATICFILES_DIRS += [ # TODO (cpennington): When courses are stored in a database, this # should no longer be added to STATICFILES (course_dir, DATA_DIR / course_dir) for course_dir in os.listdir(DATA_DIR) - if os.path.isdir(DATA_DIR / course_dir) + if (os.path.isdir(DATA_DIR / course_dir) and + not os.path.isdir(DATA_DIR / course_dir / 'static')) + ] + # Otherwise, add only the static directory from the course dir + STATICFILES_DIRS += [ + # TODO (cpennington): When courses are stored in a database, this + # should no longer be added to STATICFILES + (course_dir, DATA_DIR / course_dir / 'static') + for course_dir in os.listdir(DATA_DIR) + if (os.path.isdir(DATA_DIR / course_dir / 'static')) ] # Locale/Internationalization From 08b233f6037940f50b26f395931549c74b2aaa78 Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Tue, 28 Aug 2012 22:38:56 -0700 Subject: [PATCH 02/35] Ensure that js files are returned in alphabetical order by django-pipeline. On most systems this seems to be the default behavior, but glob2.glob's order isn't guaranteed and on my machine the order seems to be random, which causes javascript which depends on include order to fail. --- lms/envs/common.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lms/envs/common.py b/lms/envs/common.py index ce08bf9666..37d65e78f9 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -429,7 +429,7 @@ main_vendor_js = [ 'js/vendor/jquery.qtip.min.js', ] -discussion_js = glob2.glob(PROJECT_ROOT / 'static/coffee/src/discussion/*.coffee') +discussion_js = sorted(glob2.glob(PROJECT_ROOT / 'static/coffee/src/discussion/*.coffee')) # Load javascript from all of the available xmodules, and # prep it for use in pipeline js @@ -497,10 +497,10 @@ PIPELINE_JS = { 'source_filenames': [ pth.replace(COMMON_ROOT / 'static/', '') for pth - in glob2.glob(COMMON_ROOT / 'static/coffee/src/**/*.coffee') + in sorted(glob2.glob(COMMON_ROOT / 'static/coffee/src/**/*.coffee')) ] + [ pth.replace(PROJECT_ROOT / 'static/', '') - for pth in glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee')\ + for pth in sorted(glob2.glob(PROJECT_ROOT / 'static/coffee/src/**/*.coffee'))\ if pth not in courseware_only_js and pth not in discussion_js ] + [ 'js/form.ext.js', From e58af44620e495e4006938d865a8a994ff1fc0d1 Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Wed, 29 Aug 2012 19:41:46 -0700 Subject: [PATCH 03/35] Annotate comment content wrappers with classes for the roles of the author. --- lms/djangoapps/django_comment_client/utils.py | 5 +++++ lms/templates/discussion/mustache/_content.mustache | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index 516344d79b..dc4a4e21e1 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -8,7 +8,9 @@ from django.utils import simplejson from django.db import connection from django.conf import settings from django.core.urlresolvers import reverse +from django.contrib.auth.models import User from django_comment_client.permissions import check_permissions_by_view +from django_comment_client.models import Role from mitxmako import middleware import logging @@ -212,11 +214,14 @@ def permalink(content): args=[content['course_id'], content['commentable_id'], content['thread_id']]) + '#' + content['id'] def extend_content(content): + user = User.objects.get(pk=content['user_id']) + roles = dict([('name', role.name.lower()) for role in user.roles.filter(course_id=content['course_id'])]) content_info = { 'displayed_title': content.get('highlighted_title') or content.get('title', ''), 'displayed_body': content.get('highlighted_body') or content.get('body', ''), 'raw_tags': ','.join(content.get('tags', [])), 'permalink': permalink(content), + 'roles': roles, } return merge_dict(content, content_info) diff --git a/lms/templates/discussion/mustache/_content.mustache b/lms/templates/discussion/mustache/_content.mustache index b4f3176931..03a9211b35 100644 --- a/lms/templates/discussion/mustache/_content.mustache +++ b/lms/templates/discussion/mustache/_content.mustache @@ -1,5 +1,5 @@
-
+
{{content.votes.point}}
From e1aa30b36f84abf55bf1bed1b9e9f3937dd8d575 Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Wed, 29 Aug 2012 21:52:22 -0700 Subject: [PATCH 04/35] Style comments from moderators in light blue, and admins in light green. Also puts text after the author's name (there should be something for colorblind people, not sure what form it should take though). --- lms/static/sass/_discussion.scss | 15 +++++++++++++++ .../discussion/mustache/_content.mustache | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index 6c1988684e..869148a795 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -385,6 +385,14 @@ $tag-text-color: #5b614f; color: #dea03e; } } + + .author-moderator:after{ + content: " (moderator)" + } + + .author-administrator:after{ + content: " (administrator)" + } } .discussion-content { @@ -410,6 +418,13 @@ $tag-text-color: #5b614f; } } + // Role based styles + .role-moderator{ + background-color: #eafcfc; + } + .role-administrator{ + background-color: #eafcea; + } //COMMENT STYLES .comments { overflow: hidden; diff --git a/lms/templates/discussion/mustache/_content.mustache b/lms/templates/discussion/mustache/_content.mustache index 03a9211b35..f6b375dc88 100644 --- a/lms/templates/discussion/mustache/_content.mustache +++ b/lms/templates/discussion/mustache/_content.mustache @@ -1,5 +1,5 @@ -
-
+
+
{{content.votes.point}}
@@ -34,7 +34,7 @@ anonymous {{/content.anonymous}} {{^content.anonymous}} - {{content.username}} + {{content.username}} {{/content.anonymous}}
From b80ceecf87e2c9663ac9a88b1e4f420683d2c929 Mon Sep 17 00:00:00 2001 From: Ibrahim Awwal Date: Thu, 30 Aug 2012 00:19:42 -0700 Subject: [PATCH 05/35] Note when posts have been updated, and put the creation date in the title text. --- lms/djangoapps/django_comment_client/utils.py | 1 + lms/static/coffee/src/discussion/content.coffee | 3 +++ lms/templates/discussion/mustache/_content.mustache | 5 ++++- 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index dc4a4e21e1..89d3cd99ba 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -222,6 +222,7 @@ def extend_content(content): 'raw_tags': ','.join(content.get('tags', [])), 'permalink': permalink(content), 'roles': roles, + 'updated': content['created_at']!=content['updated_at'], } return merge_dict(content, content_info) diff --git a/lms/static/coffee/src/discussion/content.coffee b/lms/static/coffee/src/discussion/content.coffee index 73c3688c2a..9f95c201f4 100644 --- a/lms/static/coffee/src/discussion/content.coffee +++ b/lms/static/coffee/src/discussion/content.coffee @@ -374,6 +374,9 @@ if Backbone? MathJax.Hub.Queue ["Typeset", MathJax.Hub, $contentBody.attr("id")] initTimeago: -> + @$("span.timeago").each (index, element) -> + elem = $(element) + elem.html("posted on #{$.timeago.parse(elem.html()).toLocaleString()}") @$("span.timeago").timeago() renderPartial: -> diff --git a/lms/templates/discussion/mustache/_content.mustache b/lms/templates/discussion/mustache/_content.mustache index f6b375dc88..264115919b 100644 --- a/lms/templates/discussion/mustache/_content.mustache +++ b/lms/templates/discussion/mustache/_content.mustache @@ -29,7 +29,10 @@ {{/thread}}
- sometime by + {{#content.updated}} + updated + {{/content.updated}} + {{content.created_at}} by {{#content.anonymous}} anonymous {{/content.anonymous}} From 6cfe52680bb781873e920586eacec3f92c14fccd Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 5 Sep 2012 12:53:14 -0400 Subject: [PATCH 06/35] Fix XModule __unicode__ method - was referencing no-longer-present attributes --- common/lib/xmodule/xmodule/x_module.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index be61498de6..000d64e60b 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -212,7 +212,7 @@ class XModule(HTMLSnippet): return self.metadata.get('display_name', self.url_name.replace('_', ' ')) def __unicode__(self): - return '' % (self.name, self.category, self.id) + return ''.format(self.id) def get_children(self): ''' From 4481adb0413dd143764ac87c9207b99858d4f131 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Tue, 4 Sep 2012 00:16:15 -0400 Subject: [PATCH 07/35] Track current chapter. - courseware index view now redirects to most recent chapter, or first - simplify the view a bit --- common/lib/xmodule/xmodule/seq_module.py | 2 + common/lib/xmodule/xmodule/x_module.py | 10 ++ lms/djangoapps/courseware/module_render.py | 43 ++------ lms/djangoapps/courseware/tests/tests.py | 104 +++++++++++++++--- lms/djangoapps/courseware/views.py | 117 +++++++++++++++------ 5 files changed, 193 insertions(+), 83 deletions(-) diff --git a/common/lib/xmodule/xmodule/seq_module.py b/common/lib/xmodule/xmodule/seq_module.py index fee4d53700..65f692957c 100644 --- a/common/lib/xmodule/xmodule/seq_module.py +++ b/common/lib/xmodule/xmodule/seq_module.py @@ -29,6 +29,8 @@ class SequenceModule(XModule): shared_state=None, **kwargs): XModule.__init__(self, system, location, definition, descriptor, instance_state, shared_state, **kwargs) + # NOTE: Position is 1-indexed. This is silly, but there are now student + # positions saved on prod, so it's not easy to fix. self.position = 1 if instance_state is not None: diff --git a/common/lib/xmodule/xmodule/x_module.py b/common/lib/xmodule/xmodule/x_module.py index 000d64e60b..e21e858baa 100644 --- a/common/lib/xmodule/xmodule/x_module.py +++ b/common/lib/xmodule/xmodule/x_module.py @@ -465,6 +465,16 @@ class XModuleDescriptor(Plugin, HTMLSnippet): return self._child_instances + + def get_child_by_url_name(self, url_name): + """ + Return a child XModuleDescriptor with the specified url_name, if it exists, and None otherwise. + """ + for c in self.get_children(): + if c.url_name == url_name: + return c + return None + def xmodule_constructor(self, system): """ Returns a constructor for an XModule. This constructor takes two diff --git a/lms/djangoapps/courseware/module_render.py b/lms/djangoapps/courseware/module_render.py index b8416426b6..88368c4a80 100644 --- a/lms/djangoapps/courseware/module_render.py +++ b/lms/djangoapps/courseware/module_render.py @@ -52,7 +52,7 @@ def make_track_function(request): return f -def toc_for_course(user, request, course, active_chapter, active_section, course_id=None): +def toc_for_course(user, request, course, active_chapter, active_section): ''' Create a table of contents from the module store @@ -75,13 +75,13 @@ def toc_for_course(user, request, course, active_chapter, active_section, course ''' student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( - course_id, user, course, depth=2) - course = get_module(user, request, course.location, student_module_cache, course_id) - if course is None: + course.id, user, course, depth=2) + course_module = get_module(user, request, course.location, student_module_cache, course.id) + if course_module is None: return None chapters = list() - for chapter in course.get_display_items(): + for chapter in course_module.get_display_items(): hide_from_toc = chapter.metadata.get('hide_from_toc','false').lower() == 'true' if hide_from_toc: continue @@ -109,36 +109,6 @@ def toc_for_course(user, request, course, active_chapter, active_section, course return chapters -def get_section(course_module, chapter, section): - """ - Returns the xmodule descriptor for the name course > chapter > section, - or None if this doesn't specify a valid section - - course: Course url - chapter: Chapter url_name - section: Section url_name - """ - - if course_module is None: - return - - chapter_module = None - for _chapter in course_module.get_children(): - if _chapter.url_name == chapter: - chapter_module = _chapter - break - - if chapter_module is None: - return - - section_module = None - for _section in chapter_module.get_children(): - if _section.url_name == section: - section_module = _section - break - - return section_module - def get_module(user, request, location, student_module_cache, course_id, position=None): """ Get an instance of the xmodule class identified by location, @@ -293,9 +263,10 @@ def _get_module(user, request, location, student_module_cache, course_id, positi return module +# TODO (vshnayder): Rename this? It's very confusing. def get_instance_module(course_id, user, module, student_module_cache): """ - Returns instance_module is a StudentModule specific to this module for this student, + Returns the StudentModule specific to this module for this student, or None if this is an anonymous user """ if user.is_authenticated(): diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index d7fffb7499..0d295f5c0f 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -7,6 +7,7 @@ import time from nose import SkipTest from path import path from pprint import pprint +from urlparse import urlsplit, urlunsplit from django.contrib.auth.models import User, Group from django.test import TestCase @@ -83,6 +84,27 @@ REAL_DATA_MODULESTORE = mongo_store_config(REAL_DATA_DIR) class ActivateLoginTestCase(TestCase): '''Check that we can activate and log in''' + def assertRedirectsNoFollow(self, response, expected_url): + """ + http://devblog.point2.com/2010/04/23/djangos-assertredirects-little-gotcha/ + + Don't check that the redirected-to page loads--there should be other tests for that. + + Some of the code taken from django.test.testcases.py + """ + self.assertEqual(response.status_code, 302, + 'Response status code was {0} instead of 302'.format(response.status_code)) + url = response['Location'] + + e_scheme, e_netloc, e_path, e_query, e_fragment = urlsplit( + expected_url) + if not (e_scheme or e_netloc): + expected_url = urlunsplit(('http', 'testserver', e_path, + e_query, e_fragment)) + + self.assertEqual(url, expected_url, "Response redirected to '{0}', expected '{1}'".format( + url, expected_url)) + def setUp(self): email = 'view@test.com' password = 'foo' @@ -193,6 +215,25 @@ class PageLoader(ActivateLoginTestCase): data = parse_json(resp) self.assertTrue(data['success']) + + def check_for_get_code(self, code, url): + """ + Check that we got the expected code. Hacks around our broken 404 + handling. + """ + resp = self.client.get(url) + # HACK: workaround the bug that returns 200 instead of 404. + # TODO (vshnayder): once we're returning 404s, get rid of this if. + if code != 404: + self.assertEqual(resp.status_code, code) + # And 'page not found' shouldn't be in the returned page + self.assertTrue(resp.content.lower().find('page not found') == -1) + else: + # look for "page not found" instead of the status code + #print resp.content + self.assertTrue(resp.content.lower().find('page not found') != -1) + + def check_pages_load(self, course_name, data_dir, modstore): """Make all locations in course load""" print "Checking course {0} in {1}".format(course_name, data_dir) @@ -204,7 +245,7 @@ class PageLoader(ActivateLoginTestCase): course = courses[0] self.enroll(course) course_id = course.id - + n = 0 num_bad = 0 all_ok = True @@ -245,6 +286,54 @@ class TestCoursesLoadTestCase(PageLoader): self.check_pages_load('full', TEST_DATA_DIR, modulestore()) +@override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) +class TestNavigation(PageLoader): + """Check that navigation state is saved properly""" + + def setUp(self): + xmodule.modulestore.django._MODULESTORES = {} + courses = modulestore().get_courses() + + def find_course(name): + """Assumes the course is present""" + return [c for c in courses if c.location.course==name][0] + + self.full = find_course("full") + self.toy = find_course("toy") + + # Create two accounts + self.student = 'view@test.com' + self.student2 = 'view2@test.com' + self.password = 'foo' + self.create_account('u1', self.student, self.password) + self.create_account('u2', self.student2, self.password) + self.activate_user(self.student) + self.activate_user(self.student2) + + def test_accordion_state(self): + """Make sure that the accordion remembers where you were properly""" + self.login(self.student, self.password) + self.enroll(self.toy) + self.enroll(self.full) + + # First request should redirect to Overview + resp = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id})) + + self.assertRedirectsNoFollow(resp, reverse( + 'courseware_chapter', kwargs={'course_id': self.toy.id, 'chapter': 'Overview'})) + + # Now we directly navigate to a section in a different chapter + self.check_for_get_code(200, reverse('courseware_section', + kwargs={'course_id': self.toy.id, + 'chapter':'secret:magic', 'section':'toyvideo'})) + + # And now hitting the courseware tab should redirect to 'secret:magic' + resp = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id})) + self.assertRedirects(resp, reverse('courseware_chapter', + kwargs={'course_id': self.toy.id, 'chapter': 'secret:magic'}), + status_code = 302, target_status_code = 200) + + @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) class TestViewAuth(PageLoader): """Check that view authentication works properly""" @@ -272,19 +361,6 @@ class TestViewAuth(PageLoader): self.activate_user(self.student) self.activate_user(self.instructor) - def check_for_get_code(self, code, url): - resp = self.client.get(url) - # HACK: workaround the bug that returns 200 instead of 404. - # TODO (vshnayder): once we're returning 404s, get rid of this if. - if code != 404: - self.assertEqual(resp.status_code, code) - # And 'page not found' shouldn't be in the returned page - self.assertTrue(resp.content.lower().find('page not found') == -1) - else: - # look for "page not found" instead of the status code - #print resp.content - self.assertTrue(resp.content.lower().find('page not found') != -1) - def test_instructor_pages(self): """Make sure only instructors for the course or staff can load the instructor dashboard, the grade views, and student profile pages""" diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 60279d34c9..e0c73f1b4b 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -23,7 +23,7 @@ from courseware import grades from courseware.access import has_access from courseware.courses import (get_course_with_access, get_courses_by_university) from models import StudentModuleCache -from module_render import toc_for_course, get_module, get_section +from module_render import toc_for_course, get_module, get_instance_module from student.models import UserProfile from multicourse import multicourse_settings @@ -40,9 +40,6 @@ from xmodule.modulestore.search import path_to_location import comment_client - - - log = logging.getLogger("mitx.courseware") template_imports = {'urllib': urllib} @@ -83,7 +80,7 @@ def courses(request): return render_to_response("courses.html", {'universities': universities}) -def render_accordion(request, course, chapter, section, course_id=None): +def render_accordion(request, course, chapter, section): ''' Draws navigation bar. Takes current position in accordion as parameter. @@ -94,7 +91,7 @@ def render_accordion(request, course, chapter, section, course_id=None): Returns the html string''' # grab the table of contents - toc = toc_for_course(request.user, request, course, chapter, section, course_id=course_id) + toc = toc_for_course(request.user, request, course, chapter, section) context = dict([('toc', toc), ('course_id', course.id), @@ -102,16 +99,55 @@ def render_accordion(request, course, chapter, section, course_id=None): return render_to_string('accordion.html', context) +def redirect_to_course_position(course_module): + """ + Load the course state for the user, and return a redirect to the + appropriate place in the course: either the first element if there + is no state, or their previous place if there is. + """ + chapters = course_module.get_display_items() + # position is 1-indexed. + if course_module.position - 1 < len(chapters): + chapter = chapters[course_module.position - 1] + elif len(chapters) > 0: + # Something is wrong. Default to first chapter. + chapter = chapters[0] + else: + # oops. Something bad has happened. + raise Http404 + + return redirect(reverse('courseware_chapter', kwargs={'course_id': course_module.descriptor.id, + 'chapter': chapter.url_name})) + +def save_course_position(course_module, chapter, instance_module): + """ + chapter: url_name of the chapter + instance_module: the StudentModule object for the course_module + """ + for i, c in enumerate(course_module.get_display_items()): + if c.url_name == chapter: + # Position is 1-indexed + position = i + 1 + # Only save if position changed + if position != course_module.position: + course_module.position = position + instance_module.state = course_module.get_instance_state() + instance_module.save() + @login_required @ensure_csrf_cookie @cache_control(no_cache=True, no_store=True, must_revalidate=True) def index(request, course_id, chapter=None, section=None, position=None): """ - Displays courseware accordion, and any associated content. - If course, chapter, and section aren't all specified, just returns - the accordion. If they are specified, returns an error if they don't - point to a valid module. + Displays courseware accordion and associated content. If course, chapter, + and section are all specified, renders the page, or returns an error if they + are invalid. + + If section is not specified, displays the accordion opened to the right chapter. + + If neither chapter or section are specified, redirects to user's most recent + chapter, or the first chapter if this is the user's first visit. Arguments: @@ -134,9 +170,20 @@ def index(request, course_id, chapter=None, section=None, return redirect(reverse('about_course', args=[course.id])) try: + student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( + course.id, request.user, course, depth=1) + course_module = get_module(request.user, request, course.location, student_module_cache, course.id) + if course_module is None: + log.warning('If you see this, something went wrong: if we got this' + ' far, should have gotten a course module for this user') + return redirect(reverse('about_course', args=[course.id])) + + if chapter is None and section is None: + return redirect_to_course_position(course_module) + context = { 'csrf': csrf(request)['csrf_token'], - 'accordion': render_accordion(request, course, chapter, section, course_id=course_id), + 'accordion': render_accordion(request, course, chapter, section), 'COURSE_TITLE': course.title, 'course': course, 'init': '', @@ -144,28 +191,32 @@ def index(request, course_id, chapter=None, section=None, 'staff_access': staff_access, } - look_for_module = chapter is not None and section is not None - if look_for_module: - section_descriptor = get_section(course, chapter, section) - if section_descriptor is not None: - student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( - course_id, request.user, section_descriptor) - module = get_module(request.user, request, - section_descriptor.location, - student_module_cache, course_id, position) - if module is None: - # User is probably being clever and trying to access something - # they don't have access to. - raise Http404 - context['content'] = module.get_html() - else: - log.warning("Couldn't find a section descriptor for course_id '{0}'," - "chapter '{1}', section '{2}'".format( - course_id, chapter, section)) - else: - if request.user.is_staff: - # Add a list of all the errors... - context['course_errors'] = modulestore().get_item_errors(course.location) + chapter_descriptor = course.get_child_by_url_name(chapter) + if chapter_descriptor is not None: + instance_module = get_instance_module(course_id, request.user, course_module, student_module_cache) + save_course_position(course_module, chapter, instance_module) + + if section is not None: + section_descriptor = chapter_descriptor.get_child_by_url_name(section) + if section_descriptor is None: + # Specifically asked-for section doesn't exist + raise Http404 + + student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( + course_id, request.user, section_descriptor) + module = get_module(request.user, request, + section_descriptor.location, + student_module_cache, course_id, position) + if module is None: + # User may be trying to be clever and access something + # they don't have access to. + raise Http404 + + context['content'] = module.get_html() + + # if request.user.is_staff: + # # Add a list of all the errors... + # context['course_errors'] = modulestore().get_item_errors(course.location) result = render_to_response('courseware/courseware.html', context) except: From c354a120d8cdc2770a6c9f669a27d62b2616019b Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 5 Sep 2012 15:53:59 -0400 Subject: [PATCH 08/35] Track accordion state: - on first visit to courseware, go straight to first section of first chapter - after, clicking on courseware tab sends you most recent chapter, with a link to the most recent section (not to section because that might be confusing, and you might want to do something else (e.g. do homework instead of watch videos) - Moved course errors into instructor tab. --- lms/djangoapps/courseware/views.py | 107 +++++++++++++----- lms/djangoapps/instructor/views.py | 1 + lms/templates/courseware/courseware.html | 13 --- .../courseware/instructor_dashboard.html | 13 +++ lms/templates/courseware/welcome-back.html | 3 + 5 files changed, 93 insertions(+), 44 deletions(-) create mode 100644 lms/templates/courseware/welcome-back.html diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index e0c73f1b4b..105d4cea9e 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -99,39 +99,62 @@ def render_accordion(request, course, chapter, section): return render_to_string('accordion.html', context) -def redirect_to_course_position(course_module): +def get_current_child(xmodule): + """ + Get the xmodule.position's display item of an xmodule that has a position and + children. Returns None if the xmodule doesn't have a position, or if there + are no children. Otherwise, if position is out of bounds, returns the first child. + """ + if not hasattr(xmodule, 'position'): + return None + + children = xmodule.get_display_items() + # position is 1-indexed. + if 0 <= xmodule.position - 1 < len(children): + child = children[xmodule.position - 1] + elif len(children) > 0: + # Something is wrong. Default to first child + child = children[0] + else: + child = None + return child + + +def redirect_to_course_position(course_module, first_time): """ Load the course state for the user, and return a redirect to the appropriate place in the course: either the first element if there is no state, or their previous place if there is. + + If this is the user's first time, send them to the first section instead. """ - chapters = course_module.get_display_items() - # position is 1-indexed. - if course_module.position - 1 < len(chapters): - chapter = chapters[course_module.position - 1] - elif len(chapters) > 0: - # Something is wrong. Default to first chapter. - chapter = chapters[0] - else: + course_id = course_module.descriptor.id + chapter = get_current_child(course_module) + if chapter is None: # oops. Something bad has happened. raise Http404 + if not first_time: + return redirect(reverse('courseware_chapter', kwargs={'course_id': course_id, + 'chapter': chapter.url_name})) + # Relying on default of returning first child + section = get_current_child(chapter) + return redirect(reverse('courseware_section', kwargs={'course_id': course_id, + 'chapter': chapter.url_name, + 'section': section.url_name})) - return redirect(reverse('courseware_chapter', kwargs={'course_id': course_module.descriptor.id, - 'chapter': chapter.url_name})) - -def save_course_position(course_module, chapter, instance_module): +def save_child_position(seq_module, child_name, instance_module): """ - chapter: url_name of the chapter - instance_module: the StudentModule object for the course_module + child_name: url_name of the child + instance_module: the StudentModule object for the seq_module """ - for i, c in enumerate(course_module.get_display_items()): - if c.url_name == chapter: + for i, c in enumerate(seq_module.get_display_items()): + if c.url_name == child_name: # Position is 1-indexed position = i + 1 # Only save if position changed - if position != course_module.position: - course_module.position = position - instance_module.state = course_module.get_instance_state() + if position != seq_module.position: + seq_module.position = position + instance_module.state = seq_module.get_instance_state() instance_module.save() @login_required @@ -171,7 +194,11 @@ def index(request, course_id, chapter=None, section=None, try: student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( - course.id, request.user, course, depth=1) + course.id, request.user, course, depth=2) + + # Has this student been in this course before? + first_time = student_module_cache.lookup(course_id, 'course', course.location.url()) is None + course_module = get_module(request.user, request, course.location, student_module_cache, course.id) if course_module is None: log.warning('If you see this, something went wrong: if we got this' @@ -179,7 +206,7 @@ def index(request, course_id, chapter=None, section=None, return redirect(reverse('about_course', args=[course.id])) if chapter is None and section is None: - return redirect_to_course_position(course_module) + return redirect_to_course_position(course_module, first_time) context = { 'csrf': csrf(request)['csrf_token'], @@ -194,7 +221,10 @@ def index(request, course_id, chapter=None, section=None, chapter_descriptor = course.get_child_by_url_name(chapter) if chapter_descriptor is not None: instance_module = get_instance_module(course_id, request.user, course_module, student_module_cache) - save_course_position(course_module, chapter, instance_module) + save_child_position(course_module, chapter, instance_module) + + chapter_module = get_module(request.user, request, chapter_descriptor.location, + student_module_cache, course_id) if section is not None: section_descriptor = chapter_descriptor.get_child_by_url_name(section) @@ -202,21 +232,36 @@ def index(request, course_id, chapter=None, section=None, # Specifically asked-for section doesn't exist raise Http404 - student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( + section_student_module_cache = StudentModuleCache.cache_for_descriptor_descendents( course_id, request.user, section_descriptor) - module = get_module(request.user, request, + section_module = get_module(request.user, request, section_descriptor.location, - student_module_cache, course_id, position) - if module is None: + section_student_module_cache, course_id, position) + if section_module is None: # User may be trying to be clever and access something # they don't have access to. raise Http404 - context['content'] = module.get_html() + # Save where we are in the chapter + instance_module = get_instance_module(course_id, request.user, chapter_module, student_module_cache) + save_child_position(chapter_module, section, instance_module) - # if request.user.is_staff: - # # Add a list of all the errors... - # context['course_errors'] = modulestore().get_item_errors(course.location) + + context['content'] = section_module.get_html() + else: + # section is none, so display a message + prev_section = get_current_child(chapter_module) + if prev_section is None: + # Something went wrong -- perhaps this chapter has no sections visible to the user + raise Http404 + prev_section_url = reverse('courseware_section', kwargs={'course_id': course_id, + 'chapter': chapter_descriptor.url_name, + 'section': prev_section.url_name}) + context['content'] = render_to_string('courseware/welcome-back.html', + {'course': course, + 'chapter_module': chapter_module, + 'prev_section': prev_section, + 'prev_section_url': prev_section_url}) result = render_to_response('courseware/courseware.html', context) except: diff --git a/lms/djangoapps/instructor/views.py b/lms/djangoapps/instructor/views.py index 47ae88510c..f4e9c27991 100644 --- a/lms/djangoapps/instructor/views.py +++ b/lms/djangoapps/instructor/views.py @@ -194,6 +194,7 @@ def instructor_dashboard(request, course_id): 'instructor_access': instructor_access, 'datatable': datatable, 'msg': msg, + 'course_errors': modulestore().get_item_errors(course.location), } return render_to_response('courseware/instructor_dashboard.html', context) diff --git a/lms/templates/courseware/courseware.html b/lms/templates/courseware/courseware.html index 2950aa827e..6d7fa5193c 100644 --- a/lms/templates/courseware/courseware.html +++ b/lms/templates/courseware/courseware.html @@ -56,19 +56,6 @@
${content} - - % if course_errors is not UNDEFINED: -

Course errors

-
-
    - % for (msg, err) in course_errors: -
  • ${msg | h} -
    • ${err | h}
    -
  • - % endfor -
-
- % endif
diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html index b927302bed..2d9ab853eb 100644 --- a/lms/templates/courseware/instructor_dashboard.html +++ b/lms/templates/courseware/instructor_dashboard.html @@ -104,6 +104,19 @@ table.stat_table td {

${msg}

%endif +% if course_errors is not UNDEFINED: +

Course errors

+
+
    + % for (summary, err) in course_errors: +
  • ${summary | h} +
    • ${err | h}
    +
  • + % endfor +
+
+% endif +
diff --git a/lms/templates/courseware/welcome-back.html b/lms/templates/courseware/welcome-back.html new file mode 100644 index 0000000000..a817d303a9 --- /dev/null +++ b/lms/templates/courseware/welcome-back.html @@ -0,0 +1,3 @@ +

${chapter_module.display_name}

+ +

You were last in ${prev_section.display_name}. If you're done with that, choose another section on the left.

\ No newline at end of file From 5738a8cdbb35cfe959d1089b605224f4e7f040ea Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 5 Sep 2012 16:15:48 -0400 Subject: [PATCH 09/35] update tests --- lms/djangoapps/courseware/tests/tests.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 0d295f5c0f..65a234aa41 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -316,11 +316,13 @@ class TestNavigation(PageLoader): self.enroll(self.toy) self.enroll(self.full) - # First request should redirect to Overview + # First request should redirect to ToyVideos resp = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id})) self.assertRedirectsNoFollow(resp, reverse( - 'courseware_chapter', kwargs={'course_id': self.toy.id, 'chapter': 'Overview'})) + 'courseware_section', kwargs={'course_id': self.toy.id, + 'chapter': 'Overview', + 'section': 'Toy_Videos'})) # Now we directly navigate to a section in a different chapter self.check_for_get_code(200, reverse('courseware_section', @@ -368,11 +370,15 @@ class TestViewAuth(PageLoader): # First, try with an enrolled student self.login(self.student, self.password) # shouldn't work before enroll - self.check_for_get_code(302, reverse('courseware', kwargs={'course_id': self.toy.id})) + response = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id})) + self.assertRedirectsNoFollow(response, reverse('about_course', args=[self.toy.id])) self.enroll(self.toy) self.enroll(self.full) - # should work now - self.check_for_get_code(200, reverse('courseware', kwargs={'course_id': self.toy.id})) + # should work now -- redirect to first page + response = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id})) + self.assertRedirectsNoFollow(response, reverse('courseware_section', kwargs={'course_id': self.toy.id, + 'chapter': 'Overview', + 'section': 'Toy_Videos'})) def instructor_urls(course): "list of urls that only instructors/staff should be able to see" @@ -465,7 +471,7 @@ class TestViewAuth(PageLoader): list of urls that students should be able to see only after launch, but staff should see before """ - urls = reverse_urls(['info', 'courseware', 'progress'], course) + urls = reverse_urls(['info', 'progress'], course) urls.extend([ reverse('book', kwargs={'course_id': course.id, 'book_index': book.title}) for book in course.textbooks @@ -493,7 +499,7 @@ class TestViewAuth(PageLoader): def check_non_staff(course): """Check that access is right for non-staff in course""" print '=== Checking non-staff access for {0}'.format(course.id) - for url in instructor_urls(course) + dark_student_urls(course): + for url in instructor_urls(course) + dark_student_urls(course) + reverse_urls(['courseware'], course): print 'checking for 404 on {0}'.format(url) self.check_for_get_code(404, url) @@ -520,6 +526,10 @@ class TestViewAuth(PageLoader): print 'checking for 404 on view-as-student: {0}'.format(url) self.check_for_get_code(404, url) + # The courseware url should redirect, not 200 + url = reverse_urls(['courseware'], course)[0] + self.check_for_get_code(302, url) + # First, try with an enrolled student print '=== Testing student access....' From 7c34b02e3bfba003da0116ecc1f6a416cc851d94 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Wed, 5 Sep 2012 17:25:16 -0400 Subject: [PATCH 10/35] use a NoFollow redirect check --- lms/djangoapps/courseware/tests/tests.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 65a234aa41..92c09187f6 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -331,9 +331,8 @@ class TestNavigation(PageLoader): # And now hitting the courseware tab should redirect to 'secret:magic' resp = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id})) - self.assertRedirects(resp, reverse('courseware_chapter', - kwargs={'course_id': self.toy.id, 'chapter': 'secret:magic'}), - status_code = 302, target_status_code = 200) + self.assertRedirectsNoFollow(resp, reverse('courseware_chapter', + kwargs={'course_id': self.toy.id, 'chapter': 'secret:magic'})) @override_settings(MODULESTORE=TEST_DATA_XML_MODULESTORE) From 91d0fe8a1b3cc20b284f38386a605d67fa5e5ceb Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Sep 2012 10:25:56 -0400 Subject: [PATCH 11/35] extend test to cover first-chapter state --- lms/djangoapps/courseware/tests/tests.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index 92c09187f6..a62e9ab0cd 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -319,11 +319,17 @@ class TestNavigation(PageLoader): # First request should redirect to ToyVideos resp = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id})) - self.assertRedirectsNoFollow(resp, reverse( + # Don't use no-follow, because state should only be saved once we actually hit the section + self.assertRedirects(resp, reverse( 'courseware_section', kwargs={'course_id': self.toy.id, 'chapter': 'Overview', 'section': 'Toy_Videos'})) + # Hitting the couseware tab again should redirect to the first chapter: 'Overview' + resp = self.client.get(reverse('courseware', kwargs={'course_id': self.toy.id})) + self.assertRedirectsNoFollow(resp, reverse('courseware_chapter', + kwargs={'course_id': self.toy.id, 'chapter': 'Overview'})) + # Now we directly navigate to a section in a different chapter self.check_for_get_code(200, reverse('courseware_section', kwargs={'course_id': self.toy.id, From 6ab80fb4a85b6aa95d639c57dd8058f48187d623 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Sep 2012 12:06:51 -0400 Subject: [PATCH 12/35] debug msg to diagnose jenkins --- lms/djangoapps/courseware/views.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 105d4cea9e..897775cc4e 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -205,6 +205,9 @@ def index(request, course_id, chapter=None, section=None, ' far, should have gotten a course module for this user') return redirect(reverse('about_course', args=[course.id])) + log.debug("TEMP: course_id {}, chap {}, sec {}, first_time {}, course position = {}" + .format(course_id, chapter, section, first_time, course_module.position)) + if chapter is None and section is None: return redirect_to_course_position(course_module, first_time) From d4c0516c8b4f0b441242f9db27661defdd6251fa Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Sep 2012 14:41:09 -0400 Subject: [PATCH 13/35] another attempt to see what's broken on jenkins --- lms/djangoapps/courseware/views.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 897775cc4e..ec96a38d3d 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -205,7 +205,7 @@ def index(request, course_id, chapter=None, section=None, ' far, should have gotten a course module for this user') return redirect(reverse('about_course', args=[course.id])) - log.debug("TEMP: course_id {}, chap {}, sec {}, first_time {}, course position = {}" + log.warning("TEMP: course_id {}, chap {}, sec {}, first_time {}, course position = {}" .format(course_id, chapter, section, first_time, course_module.position)) if chapter is None and section is None: From 032869744b47c02e1d2423542abfe838c50c7b71 Mon Sep 17 00:00:00 2001 From: kimth Date: Thu, 6 Sep 2012 15:12:52 -0400 Subject: [PATCH 14/35] cktsim-in-schematic --- .../xmodule/xmodule/js/src/capa/schematic.js | 1960 +++++++++++++++++ 1 file changed, 1960 insertions(+) diff --git a/common/lib/xmodule/xmodule/js/src/capa/schematic.js b/common/lib/xmodule/xmodule/js/src/capa/schematic.js index 92bc32441b..e07b98d63c 100644 --- a/common/lib/xmodule/xmodule/js/src/capa/schematic.js +++ b/common/lib/xmodule/xmodule/js/src/capa/schematic.js @@ -1,3 +1,1963 @@ +////////////////////////////////////////////////////////////////////////////// +// +// Circuit simulator +// +////////////////////////////////////////////////////////////////////////////// + +// Copyright (C) 2011 Massachusetts Institute of Technology + + +// create a circuit for simulation using "new cktsim.Circuit()" + +// for modified nodal analysis (MNA) stamps see +// http://www.analog-electronics.eu/analog-electronics/modified-nodal-analysis/modified-nodal-analysis.xhtml + +cktsim = (function() { + + /////////////////////////////////////////////////////////////////////////////// + // + // Circuit + // + ////////////////////////////////////////////////////////////////////////////// + + // types of "nodes" in the linear system + T_VOLTAGE = 0; + T_CURRENT = 1; + + v_newt_lim = 0.3; // Voltage limited Newton great for Mos/diodes + v_abstol = 1e-6; // Absolute voltage error tolerance + i_abstol = 1e-12; // Absolute current error tolerance + eps = 1.0e-12; // A very small number compared to one. + dc_max_iters = 1000; // max iterations before giving pu + max_tran_iters = 20; // max iterations before giving up + time_step_increase_factor = 2.0; // How much can lte let timestep grow. + lte_step_decrease_factor = 8; // Limit lte one-iter timestep shrink. + nr_step_decrease_factor = 4; // Newton failure timestep shink. + reltol = 0.0001; // Relative tol to max observed value + lterel = 10; // LTE/Newton tolerance ratio (> 10!) + res_check_abs = Math.sqrt(i_abstol); // Loose Newton residue check + res_check_rel = Math.sqrt(reltol); // Loose Newton residue check + + function Circuit() { + this.node_map = new Array(); + this.ntypes = []; + this.initial_conditions = []; // ic's for each element + + this.devices = []; // list of devices + this.device_map = new Array(); // map name -> device + this.voltage_sources = []; // list of voltage sources + this.current_sources = []; // list of current sources + + this.finalized = false; + this.diddc = false; + this.node_index = -1; + + this.periods = 1 + } + + // index of ground node + Circuit.prototype.gnd_node = function() { + return -1; + } + + // allocate a new node index + Circuit.prototype.node = function(name,ntype,ic) { + this.node_index += 1; + if (name) this.node_map[name] = this.node_index; + this.ntypes.push(ntype); + this.initial_conditions.push(ic); + return this.node_index; + } + + // call to finalize the circuit in preparation for simulation + Circuit.prototype.finalize = function() { + if (!this.finalized) { + this.finalized = true; + this.N = this.node_index + 1; // number of nodes + + // give each device a chance to finalize itself + for (var i = this.devices.length - 1; i >= 0; --i) + this.devices[i].finalize(this); + + // set up augmented matrix and various temp vectors + this.matrix = mat_make(this.N, this.N+1); + this.Gl = mat_make(this.N, this.N); // Matrix for linear conductances + this.G = mat_make(this.N, this.N); // Complete conductance matrix + this.C = mat_make(this.N, this.N); // Matrix for linear L's and C's + + this.soln_max = new Array(this.N); // max abs value seen for each unknown + this.abstol = new Array(this.N); + this.solution = new Array(this.N); + this.rhs = new Array(this.N); + for (var i = this.N - 1; i >= 0; --i) { + this.soln_max[i] = 0.0; + this.abstol[i] = this.ntypes[i] == T_VOLTAGE ? v_abstol : i_abstol; + this.solution[i] = 0.0; + this.rhs[i] = 0.0; + } + + // Load up the linear elements once and for all + for (var i = this.devices.length - 1; i >= 0; --i) { + this.devices[i].load_linear(this) + } + + // Check for voltage source loops. + n_vsrc = this.voltage_sources.length; + if (n_vsrc > 0) { // At least one voltage source + var GV = mat_make(n_vsrc, this.N); // Loop check + for (var i = n_vsrc - 1; i >= 0; --i) { + var branch = this.voltage_sources[i].branch; + for (var j = this.N - 1; j >= 0; j--) + GV[i][j] = this.Gl[branch][j]; + } + var rGV = mat_rank(GV); + if (rGV < n_vsrc) { + alert('Warning!!! Circuit has a voltage source loop or a source or current probe shorted by a wire, please remove the source or the wire causing the short.'); + alert('Warning!!! Simulator might produce meaningless results or no result with illegal circuits.'); + return false; + } + } + } + return true; + } + + // load circuit from JSON netlist (see schematic.js) + Circuit.prototype.load_netlist = function(netlist) { + // set up mapping for all ground connections + for (var i = netlist.length - 1; i >= 0; --i) { + var component = netlist[i]; + var type = component[0]; + if (type == 'g') { + var connections = component[3]; + this.node_map[connections[0]] = this.gnd_node(); + } + } + + // process each component in the JSON netlist (see schematic.js for format) + var found_ground = false; + for (var i = netlist.length - 1; i >= 0; --i) { + var component = netlist[i]; + var type = component[0]; + + // ignore wires, ground connections, scope probes and view info + if (type == 'view' || type == 'w' || type == 'g' || type == 's' || type == 'L') { + continue; + } + + var properties = component[2]; + var name = properties['name']; + if (name==undefined || name=='') + name = '_' + properties['_json_'].toString(); + + // convert node names to circuit indicies + var connections = component[3]; + for (var j = connections.length - 1; j >= 0; --j) { + var node = connections[j]; + var index = this.node_map[node]; + if (index == undefined) index = this.node(node,T_VOLTAGE); + else if (index == this.gnd_node()) found_ground = true; + connections[j] = index; + } + + // process the component + if (type == 'r') // resistor + this.r(connections[0],connections[1],properties['r'],name); + else if (type == 'd') // diode + this.d(connections[0],connections[1],properties['area'],properties['type'],name); + else if (type == 'c') // capacitor + this.c(connections[0],connections[1],properties['c'],name); + else if (type == 'l') // inductor + this.l(connections[0],connections[1],properties['l'],name); + else if (type == 'v') // voltage source + this.v(connections[0],connections[1],properties['value'],name); + else if (type == 'i') // current source + this.i(connections[0],connections[1],properties['value'],name); + else if (type == 'o') // op amp + this.opamp(connections[0],connections[1],connections[2],connections[3],properties['A'],name); + else if (type == 'n') // n fet + this.n(connections[0],connections[1],connections[2],properties['W/L'],name); + else if (type == 'p') // p fet + this.p(connections[0],connections[1],connections[2],properties['W/L'],name); + else if (type == 'a') // current probe == 0-volt voltage source + this.v(connections[0],connections[1],'0',name); + } + + if (!found_ground) { // No ground on schematic + alert('Please make at least one connection to ground (inverted T symbol)'); + return false; + } + return true; + + } + + // if converges: updates this.solution, this.soln_max, returns iter count + // otherwise: return undefined and set this.problem_node + // Load should compute -f and df/dx (note the sign pattern!) + Circuit.prototype.find_solution = function(load,maxiters) { + var soln = this.solution; + var rhs = this.rhs; + var d_sol = new Array(); + var abssum_compare; + var converged,abssum_old=0, abssum_rhs; + var use_limiting = false; + var down_count = 0; + + // iteratively solve until values convere or iteration limit exceeded + for (var iter = 0; iter < maxiters; iter++) { + // set up equations + load(this,soln,rhs); + + // Compute norm of rhs, assume variables of v type go with eqns of i type + abssum_rhs = 0; + for (var i = this.N - 1; i >= 0; --i) + if (this.ntypes[i] == T_VOLTAGE) + abssum_rhs += Math.abs(rhs[i]); + + if ((iter > 0) && (use_limiting == false) && (abssum_old < abssum_rhs)) { + // Old rhsnorm was better, undo last iter and turn on limiting + for (var i = this.N - 1; i >= 0; --i) + soln[i] -= d_sol[i]; + iter -= 1; + use_limiting = true; + } + else { // Compute the Newton delta + //d_sol = mat_solve(this.matrix,rhs); + d_sol = mat_solve_rq(this.matrix,rhs); + + // If norm going down for ten iters, stop limiting + if (abssum_rhs < abssum_old) + down_count += 1; + else + down_count = 0; + if (down_count > 10) { + use_limiting = false; + down_count = 0; + } + + // Update norm of rhs + abssum_old = abssum_rhs; + } + + // Update the worst case abssum for comparison. + if ((iter == 0) || (abssum_rhs > abssum_compare)) + abssum_compare = abssum_rhs; + + // Check residue convergence, but loosely, and give up + // on last iteration + if ( (iter < (maxiters - 1)) && + (abssum_rhs > (res_check_abs+res_check_rel*abssum_compare))) + converged = false; + else converged = true; + + + // Update solution and check delta convergence + for (var i = this.N - 1; i >= 0; --i) { + // Simple voltage step limiting to encourage Newton convergence + if (use_limiting) { + if (this.ntypes[i] == T_VOLTAGE) { + d_sol[i] = (d_sol[i] > v_newt_lim) ? v_newt_lim : d_sol[i]; + d_sol[i] = (d_sol[i] < -v_newt_lim) ? -v_newt_lim : d_sol[i]; + } + } + soln[i] += d_sol[i]; + thresh = this.abstol[i] + reltol*this.soln_max[i]; + if (Math.abs(d_sol[i]) > thresh) { + converged = false; + this.problem_node = i; + } + } + + //alert(numeric.prettyPrint(this.solution);) + if (converged == true) { + for (var i = this.N - 1; i >= 0; --i) + if (Math.abs(soln[i]) > this.soln_max[i]) + this.soln_max[i] = Math.abs(soln[i]); + + return iter+1; + } + } + return undefined; + } + + // DC analysis + Circuit.prototype.dc = function() { + + // Allocation matrices for linear part, etc. + if (this.finalize() == false) + return undefined; + + // Define -f and df/dx for Newton solver + function load_dc(ckt,soln,rhs) { + // rhs is initialized to -Gl * soln + mat_v_mult(ckt.Gl, soln, rhs, -1.0); + // G matrix is initialized with linear Gl + mat_copy(ckt.Gl,ckt.G); + // Now load up the nonlinear parts of rhs and G + for (var i = ckt.devices.length - 1; i >= 0; --i) + ckt.devices[i].load_dc(ckt,soln,rhs); + // G matrix is copied in to the system matrix + mat_copy(ckt.G,ckt.matrix); + } + + // find the operating point + var iterations = this.find_solution(load_dc,dc_max_iters); + + if (typeof iterations == 'undefined') { + // too many iterations + if (this.current_sources.length > 0) { + alert('Newton Method Failed, do your current sources have a conductive path to ground?'); + } else { + alert('Newton Method Failed, it may be your circuit or it may be our simulator.'); + } + + return undefined + } else { + // Note that a dc solution was computed + this.diddc = true; + // create solution dictionary + var result = new Array(); + // capture node voltages + for (var name in this.node_map) { + var index = this.node_map[name]; + result[name] = (index == -1) ? 0 : this.solution[index]; + } + // capture branch currents from voltage sources + for (var i = this.voltage_sources.length - 1; i >= 0; --i) { + var v = this.voltage_sources[i]; + result['I('+v.name+')'] = this.solution[v.branch]; + } + return result; + } + } + + // Transient analysis (needs work!) + Circuit.prototype.tran = function(ntpts, tstart, tstop, probenames, no_dc) { + + // Define -f and df/dx for Newton solver + function load_tran(ckt,soln,rhs) { + // Crnt is initialized to -Gl * soln + mat_v_mult(ckt.Gl, soln, ckt.c,-1.0); + // G matrix is initialized with linear Gl + mat_copy(ckt.Gl,ckt.G); + // Now load up the nonlinear parts of crnt and G + for (var i = ckt.devices.length - 1; i >= 0; --i) + ckt.devices[i].load_tran(ckt,soln,ckt.c,ckt.time); + // Exploit the fact that storage elements are linear + mat_v_mult(ckt.C, soln, ckt.q, 1.0); + // -rhs = c - dqdt + for (var i = ckt.N-1; i >= 0; --i) { + var dqdt = ckt.alpha0*ckt.q[i] + ckt.alpha1*ckt.oldq[i] + + ckt.alpha2*ckt.old2q[i]; + //alert(numeric.prettyPrint(dqdt)); + rhs[i] = ckt.beta0[i]*ckt.c[i] + ckt.beta1[i]*ckt.oldc[i] - dqdt; + } + // matrix = beta0*G + alpha0*C. + mat_scale_add(ckt.G,ckt.C,ckt.beta0,ckt.alpha0,ckt.matrix); + } + + var p = new Array(3); + function interp_coeffs(t, t0, t1, t2) { + // Poly coefficients + var dtt0 = (t - t0); + var dtt1 = (t - t1); + var dtt2 = (t - t2); + var dt0dt1 = (t0 - t1); + var dt0dt2 = (t0 - t2); + var dt1dt2 = (t1 - t2); + p[0] = (dtt1*dtt2)/(dt0dt1 * dt0dt2); + p[1] = (dtt0*dtt2)/(-dt0dt1 * dt1dt2); + p[2] = (dtt0*dtt1)/(dt0dt2 * dt1dt2); + return p; + } + + function pick_step(ckt, step_index) { + var min_shrink_factor = 1.0/lte_step_decrease_factor; + var max_growth_factor = time_step_increase_factor; + var N = ckt.N; + var p = interp_coeffs(ckt.time, ckt.oldt, ckt.old2t, ckt.old3t); + var trapcoeff = 0.5*(ckt.time - ckt.oldt)/(ckt.time - ckt.old3t); + var maxlteratio = 0.0; + for (var i = ckt.N-1; i >= 0; --i) { + if (ckt.ltecheck[i]) { // Check lte on variable + var pred = p[0]*ckt.oldsol[i] + p[1]*ckt.old2sol[i] + p[2]*ckt.old3sol[i]; + var lte = Math.abs((ckt.solution[i] - pred))*trapcoeff; + var lteratio = lte/(lterel*(ckt.abstol[i] + reltol*ckt.soln_max[i])); + maxlteratio = Math.max(maxlteratio, lteratio); + } + } + var new_step; + var lte_step_ratio = 1.0/Math.pow(maxlteratio,1/3); // Cube root because trap + if (lte_step_ratio < 1.0) { // Shrink the timestep to make lte + lte_step_ratio = Math.max(lte_step_ratio,min_shrink_factor); + new_step = (ckt.time - ckt.oldt)*0.75*lte_step_ratio; + new_step = Math.max(new_step, ckt.min_step); + } else { + lte_step_ratio = Math.min(lte_step_ratio, max_growth_factor); + if (lte_step_ratio > 1.2) /* Increase timestep due to lte. */ + new_step = (ckt.time - ckt.oldt) * lte_step_ratio / 1.2; + else + new_step = (ckt.time - ckt.oldt); + new_step = Math.min(new_step, ckt.max_step); + } + return new_step; + } + + // Standard to do a dc analysis before transient + // Otherwise, do the setup also done in dc. + no_dc = false; + if ((this.diddc == false) && (no_dc == false)) { + if (this.dc() == undefined) { // DC failed, realloc mats and vects. + alert('DC failed, trying transient analysis from zero.'); + this.finalized = false; // Reset the finalization. + if (this.finalize() == false) + return undefined; + } + } + else { + if (this.finalize() == false) // Allocate matrices and vectors. + return undefined; + } + + // Tired of typing this, and using "with" generates hate mail. + var N = this.N; + + // build array to hold list of results for each variable + // last entry is for timepoints. + var response = new Array(N + 1); + for (var i = N; i >= 0; --i) response[i] = new Array(); + + // Allocate back vectors for up to a second order method + this.old3sol = new Array(this.N); + this.old3q = new Array(this.N); + this.old2sol = new Array(this.N); + this.old2q = new Array(this.N); + this.oldsol = new Array(this.N); + this.oldq = new Array(this.N); + this.q = new Array(this.N); + this.oldc = new Array(this.N); + this.c = new Array(this.N); + this.alpha0 = 1.0; + this.alpha1 = 0.0; + this.alpha2 = 0.0; + this.beta0 = new Array(this.N); + this.beta1 = new Array(this.N); + + // Mark a set of algebraic variable (don't miss hidden ones!). + this.ar = this.algebraic(this.C); + + // Non-algebraic variables and probe variables get lte + this.ltecheck = new Array(this.N); + for (var i = N; i >= 0; --i) + this.ltecheck[i] = (this.ar[i] == 0); + + for (var name in this.node_map) { + var index = this.node_map[name]; + for (var i = probenames.length; i >= 0; --i) { + if (name == probenames[i]) { + this.ltecheck[index] = true; + break; + } + } + } + + // Check for periodic sources + var period = tstop - tstart; + for (var i = this.voltage_sources.length - 1; i >= 0; --i) { + var per = this.voltage_sources[i].src.period; + if (per > 0) + period = Math.min(period, per); + } + for (var i = this.current_sources.length - 1; i >= 0; --i) { + var per = this.current_sources[i].src.period; + if (per > 0) + period = Math.min(period, per); + } + this.periods = Math.ceil((tstop - tstart)/period); + //alert('number of periods ' + this.periods); + + + this.time = tstart; + // ntpts adjusted by numbers of periods in input + this.max_step = (tstop - tstart)/(this.periods*ntpts); + this.min_step = this.max_step/1e8; + var new_step = this.max_step/1e6; + this.oldt = this.time - new_step; + + // Initialize old crnts, charges, and solutions. + load_tran(this,this.solution,this.rhs) + for (var i = N-1; i >= 0; --i) { + this.old3sol[i] = this.solution[i]; + this.old2sol[i] = this.solution[i]; + this.oldsol[i] = this.solution[i]; + this.old3q[i] = this.q[i]; + this.old2q[i] = this.q[i]; + this.oldq[i] = this.q[i]; + this.oldc[i] = this.c[i]; + } + + + var beta0,beta1; + // Start with two pseudo-Euler steps, maximum 50000 steps/period + var max_nsteps = this.periods*50000; + for(var step_index = -3; step_index < max_nsteps; step_index++) { + // Save the just computed solution, and move back q and c. + for (var i = this.N - 1; i >= 0; --i) { + if (step_index >= 0) + response[i].push(this.solution[i]); + this.oldc[i] = this.c[i]; + this.old3sol[i] = this.old2sol[i]; + this.old2sol[i] = this.oldsol[i]; + this.oldsol[i] = this.solution[i]; + this.old3q[i] = this.oldq[i]; + this.old2q[i] = this.oldq[i]; + this.oldq[i] = this.q[i]; + + } + + if (step_index < 0) { // Take a prestep using BE + this.old3t = this.old2t - (this.oldt-this.old2t) + this.old2t = this.oldt - (tstart-this.oldt) + this.oldt = tstart - (this.time - this.oldt); + this.time = tstart; + beta0 = 1.0; + beta1 = 0.0; + } else { // Take a regular step + // Save the time, and rotate time wheel + response[this.N].push(this.time); + this.old3t = this.old2t; + this.old2t = this.oldt; + this.oldt = this.time; + // Make sure we come smoothly in to the interval end. + if (this.time >= tstop) break; // We're done. + else if(this.time + new_step > tstop) + this.time = tstop; + else if(this.time + 1.5*new_step > tstop) + this.time += (2/3)*(tstop - this.time); + else + this.time += new_step; + + // Use trap (average old and new crnts. + beta0 = 0.5; + beta1 = 0.5; + } + + // For trap rule, turn off current avging for algebraic eqns + for (var i = this.N - 1; i >= 0; --i) { + this.beta0[i] = beta0 + this.ar[i]*beta1; + this.beta1[i] = (1.0 - this.ar[i])*beta1; + } + + // Loop to find NR converging timestep with okay LTE + while (true) { + // Set the timestep coefficients (alpha2 is for bdf2). + this.alpha0 = 1.0/(this.time - this.oldt); + this.alpha1 = -this.alpha0; + this.alpha2 = 0; + + // If timestep is 1/10,000th of tstop, just use BE. + if ((this.time-this.oldt) < 1.0e-4*tstop) { + for (var i = this.N - 1; i >= 0; --i) { + this.beta0[i] = 1.0; + this.beta1[i] = 0.0; + } + } + // Use Newton to compute the solution. + var iterations = this.find_solution(load_tran,max_tran_iters); + + // If NR succeeds and stepsize is at min, accept and newstep=maxgrowth*minstep. + // Else if Newton Fails, shrink step by a factor and try again + // Else LTE picks new step, if bigger accept current step and go on. + if ((iterations != undefined) && + (step_index <= 0 || (this.time-this.oldt) < (1+reltol)*this.min_step)) { + if (step_index > 0) new_step = time_step_increase_factor*this.min_step; + break; + } else if (iterations == undefined) { // NR nonconvergence, shrink by factor + //alert('timestep nonconvergence ' + this.time + ' ' + step_index); + this.time = this.oldt + + (this.time - this.oldt)/nr_step_decrease_factor; + } else { // Check the LTE and shrink step if needed. + new_step = pick_step(this, step_index); + if (new_step < (1.0 - reltol)*(this.time - this.oldt)) { + this.time = this.oldt + new_step; // Try again + } + else + break; // LTE okay, new_step for next step + } + } + } + + // create solution dictionary + var result = new Array(); + for (var name in this.node_map) { + var index = this.node_map[name]; + result[name] = (index == -1) ? 0 : response[index]; + } + // capture branch currents from voltage sources + for (var i = this.voltage_sources.length - 1; i >= 0; --i) { + var v = this.voltage_sources[i]; + result['I('+v.name+')'] = response[v.branch]; + } + + result['_time_'] = response[this.N]; + return result; + } + + // AC analysis: npts/decade for freqs in range [fstart,fstop] + // result['_frequencies_'] = vector of log10(sample freqs) + // result['xxx'] = vector of dB(response for node xxx) + // NOTE: Normalization removed in schematic.js, jkw. + Circuit.prototype.ac = function(npts,fstart,fstop,source_name) { + + if (this.dc() == undefined) { // DC failed, realloc mats and vects. + return undefined; + } + + var N = this.N; + var G = this.G; + var C = this.C; + + // Complex numbers, we're going to need a bigger boat + var matrixac = mat_make(2*N, (2*N)+1); + + // Get the source used for ac + if (this.device_map[source_name] === undefined) { + alert('AC analysis refers to unknown source ' + source_name); + return 'AC analysis failed, unknown source'; + } + this.device_map[source_name].load_ac(this,this.rhs); + + // build array to hold list of magnitude and phases for each node + // last entry is for frequency values + var response = new Array(2*N + 1); + for (var i = 2*N; i >= 0; --i) response[i] = new Array(); + + // multiplicative frequency increase between freq points + var delta_f = Math.exp(Math.LN10/npts); + + var phase_offset = new Array(N); + for (var i = N-1; i >= 0; --i) phase_offset[i] = 0; + + var f = fstart; + fstop *= 1.0001; // capture that last freq point! + while (f <= fstop) { + var omega = 2 * Math.PI * f; + response[2*N].push(f); // 2*N for magnitude and phase + + // Find complex x+jy that sats Gx-omega*Cy=rhs; omega*Cx+Gy=0 + // Note: solac[0:N-1]=x, solac[N:2N-1]=y + for (var i = N-1; i >= 0; --i) { + // First the rhs, replicated for real and imaginary + matrixac[i][2*N] = this.rhs[i]; + matrixac[i+N][2*N] = 0; + + for (var j = N-1; j >= 0; --j) { + matrixac[i][j] = G[i][j]; + matrixac[i+N][j+N] = G[i][j]; + matrixac[i][j+N] = -omega*C[i][j]; + matrixac[i+N][j] = omega*C[i][j]; + } + } + + // Compute the small signal response + var solac = mat_solve(matrixac); + + // Save magnitude and phase + for (var i = N - 1; i >= 0; --i) { + var mag = Math.sqrt(solac[i]*solac[i] + solac[i+N]*solac[i+N]); + response[i].push(mag); + + // Avoid wrapping phase, add or sub 180 for each jump + var phase = 180*(Math.atan2(solac[i+N],solac[i])/Math.PI); + var phasei = response[i+N]; + var L = phasei.length; + // Look for a one-step jump greater than 90 degrees + if (L > 1) { + var phase_jump = phase + phase_offset[i] - phasei[L-1]; + if (phase_jump > 90) { + phase_offset[i] -= 360; + } else if (phase_jump < -90) { + phase_offset[i] += 360; + } + } + response[i+N].push(phase + phase_offset[i]); + } + f *= delta_f; // increment frequency + } + + // create solution dictionary + var result = new Array(); + for (var name in this.node_map) { + var index = this.node_map[name]; + result[name] = (index == -1) ? 0 : response[index]; + result[name+'_phase'] = (index == -1) ? 0 : response[index+N]; + } + result['_frequencies_'] = response[2*N]; + return result; + } + + + // Helper for adding devices to a circuit, warns on duplicate device names. + Circuit.prototype.add_device = function(d,name) { + // Add device to list of devices and to device map + this.devices.push(d); + d.name = name; + if (name) { + if (this.device_map[name] === undefined) + this.device_map[name] = d; + else { + alert('Warning: two circuit elements share the same name ' + name); + this.device_map[name] = d; + } + } + return d; + } + + Circuit.prototype.r = function(n1,n2,v,name) { + // try to convert string value into numeric value, barf if we can't + if ((typeof v) == 'string') { + v = parse_number(v,undefined); + if (v === undefined) return undefined; + } + + if (v != 0) { + var d = new Resistor(n1,n2,v); + return this.add_device(d, name); + } else return this.v(n1,n2,'0',name); // zero resistance == 0V voltage source + } + + Circuit.prototype.d = function(n1,n2,area,type,name) { + // try to convert string value into numeric value, barf if we can't + if ((typeof area) == 'string') { + area = parse_number(area,undefined); + if (area === undefined) return undefined; + } + + if (area != 0) { + var d = new Diode(n1,n2,area,type); + return this.add_device(d, name); + } // zero area diodes discarded. + } + + + Circuit.prototype.c = function(n1,n2,v,name) { + // try to convert string value into numeric value, barf if we can't + if ((typeof v) == 'string') { + v = parse_number(v,undefined); + if (v === undefined) return undefined; + } + var d = new Capacitor(n1,n2,v); + return this.add_device(d, name); + } + + Circuit.prototype.l = function(n1,n2,v,name) { + // try to convert string value into numeric value, barf if we can't + if ((typeof v) == 'string') { + v = parse_number(v,undefined); + if (v === undefined) return undefined; + } + var branch = this.node(undefined,T_CURRENT); + var d = new Inductor(n1,n2,branch,v); + return this.add_device(d, name); + } + + Circuit.prototype.v = function(n1,n2,v,name) { + var branch = this.node(undefined,T_CURRENT); + var d = new VSource(n1,n2,branch,v); + this.voltage_sources.push(d); + return this.add_device(d, name); + } + + Circuit.prototype.i = function(n1,n2,v,name) { + var d = new ISource(n1,n2,v); + this.current_sources.push(d); + return this.add_device(d, name); + } + + Circuit.prototype.opamp = function(np,nn,no,ng,A,name) { + // try to convert string value into numeric value, barf if we can't + if ((typeof A) == 'string') { + ratio = parse_number(A,undefined); + if (A === undefined) return undefined; + } + var branch = this.node(undefined,T_CURRENT); + var d = new Opamp(np,nn,no,ng,branch,A,name); + return this.add_device(d, name); + } + + Circuit.prototype.n = function(d,g,s, ratio, name) { + // try to convert string value into numeric value, barf if we can't + if ((typeof ratio) == 'string') { + ratio = parse_number(ratio,undefined); + if (ratio === undefined) return undefined; + } + var d = new Fet(d,g,s,ratio,name,'n'); + return this.add_device(d, name); + } + + Circuit.prototype.p = function(d,g,s, ratio, name) { + // try to convert string value into numeric value, barf if we can't + if ((typeof ratio) == 'string') { + ratio = parse_number(ratio,undefined); + if (ratio === undefined) return undefined; + } + var d = new Fet(d,g,s,ratio,name,'p'); + return this.add_device(d, name); + } + + /////////////////////////////////////////////////////////////////////////////// + // + // Support for creating conductance and capacitance matrices associated with + // modified nodal analysis (unknowns are node voltages and inductor and voltage + // source currents). + // The linearized circuit is written as + // C d/dt x = G x + rhs + // x - vector of node voltages and element currents + // rhs - vector of source values + // C - Matrix whose values are capacitances and inductances, has many zero rows. + // G - Matrix whose values are conductances and +-1's. + // + //////////////////////////////////////////////////////////////////////////////// + + // add val component between two nodes to matrix M + // Index of -1 refers to ground node + Circuit.prototype.add_two_terminal = function(i,j,g,M) { + if (i >= 0) { + M[i][i] += g; + if (j >= 0) { + M[i][j] -= g; + M[j][i] -= g; + M[j][j] += g; + } + } else if (j >= 0) + M[j][j] += g; + } + + // add val component between two nodes to matrix M + // Index of -1 refers to ground node + Circuit.prototype.get_two_terminal = function(i,j,x) { + var xi_minus_xj = 0; + if (i >= 0) xi_minus_xj = x[i]; + if (j >= 0) xi_minus_xj -= x[j]; + return xi_minus_xj + } + + Circuit.prototype.add_conductance_l = function(i,j,g) { + this.add_two_terminal(i,j,g, this.Gl) + } + + Circuit.prototype.add_conductance = function(i,j,g) { + this.add_two_terminal(i,j,g, this.G) + } + + Circuit.prototype.add_capacitance = function(i,j,c) { + this.add_two_terminal(i,j,c,this.C) + } + + // add individual conductance to Gl matrix + Circuit.prototype.add_to_Gl = function(i,j,g) { + if (i >=0 && j >= 0) + this.Gl[i][j] += g; + } + + // add individual conductance to Gl matrix + Circuit.prototype.add_to_G = function(i,j,g) { + if (i >=0 && j >= 0) + this.G[i][j] += g; + } + + // add individual capacitance to C matrix + Circuit.prototype.add_to_C = function(i,j,c) { + if (i >=0 && j >= 0) + this.C[i][j] += c; + } + + // add source info to rhs + Circuit.prototype.add_to_rhs = function(i,v,rhs) { + if (i >= 0) rhs[i] += v; + } + + + /////////////////////////////////////////////////////////////////////////////// + // + // Generic matrix support - making, copying, factoring, rank, etc + // Note, Matrices are stored using nested javascript arrays. + //////////////////////////////////////////////////////////////////////////////// + + // Allocate an NxM matrix + function mat_make(N,M) { + var mat = new Array(N); + for (var i = N - 1; i >= 0; --i) { + mat[i] = new Array(M); + for (var j = M - 1; j >= 0; --j) { + mat[i][j] = 0.0; + } + } + return mat; + } + + // Form b = scale*Mx + function mat_v_mult(M,x,b,scale) { + var n = M.length; + var m = M[0].length; + + if (n != b.length || m != x.length) + throw 'Rows of M mismatched to b or cols mismatch to x.'; + + for (var i = 0; i < n; i++) { + var temp = 0; + for (var j = 0; j < m; j++) temp += M[i][j]*x[j]; + b[i] = scale*temp; // Recall the neg in the name + } + } + + // C = scalea*A + scaleb*B, scalea, scaleb eithers numbers or arrays (row scaling) + function mat_scale_add(A, B, scalea, scaleb, C) { + var n = A.length; + var m = A[0].length; + + if (n > B.length || m > B[0].length) + throw 'Row or columns of A to large for B'; + if (n > C.length || m > C[0].length) + throw 'Row or columns of A to large for C'; + if ((typeof scalea == 'number') && (typeof scaleb == 'number')) + for (var i = 0; i < n; i++) + for (var j = 0; j < m; j++) + C[i][j] = scalea*A[i][j] + scaleb*B[i][j]; + else if ((typeof scaleb == 'number') && (scalea instanceof Array)) + for (var i = 0; i < n; i++) + for (var j = 0; j < m; j++) + C[i][j] = scalea[i]*A[i][j] + scaleb*B[i][j]; + else if ((typeof scaleb instanceof Array) && (scalea instanceof Array)) + for (var i = 0; i < n; i++) + for (var j = 0; j < m; j++) + C[i][j] = scalea[i]*A[i][j] + scaleb[i]*B[i][j]; + else + throw 'scalea and scaleb must be scalars or Arrays'; + } + + // Returns a vector of ones and zeros, ones denote algebraic + // variables (rows that can be removed without changing rank(M). + Circuit.prototype.algebraic = function(M) { + var Nr = M.length + Mc = mat_make(Nr, Nr); + mat_copy(M,Mc); + var R = mat_rank(Mc); + + var one_if_alg = new Array(Nr); + for (var row = 0; row < Nr; row++) { // psuedo gnd row small + for (var col = Nr - 1; col >= 0; --col) + Mc[row][col] = 0; + if (mat_rank(Mc) == R) // Zeroing row left rank unchanged + one_if_alg[row] = 1; + else { // Zeroing row changed rank, put back + for (var col = Nr - 1; col >= 0; --col) + Mc[row][col] = M[row][col]; + one_if_alg[row] = 0; + } + } + return one_if_alg; + } + + // Copy A -> using the bounds of A + function mat_copy(src,dest) { + var n = src.length; + var m = src[0].length; + if (n > dest.length || m > dest[0].length) + throw 'Rows or cols > rows or cols of dest'; + + for (var i = 0; i < n; i++) + for (var j = 0; j < m; j++) + dest[i][j] = src[i][j]; + } + + // Copy and transpose A -> using the bounds of A + function mat_copy_transposed(src,dest) { + var n = src.length; + var m = src[0].length; + if (n > dest[0].length || m > dest.length) + throw 'Rows or cols > cols or rows of dest'; + + for (var i = 0; i < n; i++) + for (var j = 0; j < m; j++) + dest[j][i] = src[i][j]; + } + + + // Uses GE to determine rank. + function mat_rank(Mo) { + var Nr = Mo.length; // Number of rows + var Nc = Mo[0].length; // Number of columns + var temp,i,j; + // Make a copy to avoid overwriting + M = mat_make(Nr, Nc); + mat_copy(Mo,M); + + // Find matrix maximum entry + var max_abs_entry = 0; + for(var row = Nr-1; row >= 0; --row) { + for(var col = Nr-1; col >= 0; --col) { + if (Math.abs(M[row][col]) > max_abs_entry) + max_abs_entry = Math.abs(M[row][col]); + } + } + + // Gaussian elimination to find rank + var the_rank = 0; + var start_col = 0; + for (var row = 0; row < Nr; row++) { + // Search for first nonzero column in the remaining rows. + for (var col = start_col; col < Nc; col++) { + var max_v = Math.abs(M[row][col]); + var max_row = row; + for (var i = row + 1; i < Nr; i++) { + temp = Math.abs(M[i][col]); + if (temp > max_v) { max_v = temp; max_row = i; } + } + // if max_v non_zero, column is nonzero, eliminate in subsequent rows + if (Math.abs(max_v) > eps*max_abs_entry) { + start_col = col+1; + the_rank += 1; + // Swap rows to get max in M[row][col] + temp = M[row]; + M[row] = M[max_row]; + M[max_row] = temp; + + // now eliminate this column for all subsequent rows + for (var i = row + 1; i < Nr; i++) { + temp = M[i][col]/M[row][col]; // multiplier for current row + if (temp != 0) // subtract + for (var j = col; j < Nc; j++) M[i][j] -= M[row][j]*temp; + } + // Now move on to the next row + break; + } + } + } + + // return the rank + return the_rank; + } + + // Solve Mx=b and return vector x using R^TQ^T factorization. + // Multiplication by R^T implicit, should be null-space free soln. + // M should have the extra column! + // Almost everything is in-lined for speed, sigh. + function mat_solve_rq(M, rhs) { + + var Nr = M.length; // Number of rows + var Nc = M[0].length; // Number of columns + + // Copy the rhs in to the last column of M if one is given. + if (rhs != null) { + for (var row = Nr - 1; row >= 0; --row) + M[row][Nc-1] = rhs[row]; + } + + var mat_scale = 0; // Sets the scale for comparison to zero. + var max_nonzero_row = Nr-1; // Assumes M nonsingular. + for (var row = 0; row < Nr; row++) { + // Find largest row with largest 2-norm + var max_row = row; + var maxsumsq = 0; + for (var rowp = row; rowp < Nr; rowp++) { + var Mr = M[rowp]; + var sumsq = 0; + for (var col = Nc-2; col >= 0; --col) // Last col=rhs + sumsq += Mr[col]*Mr[col]; + if ((row == rowp) || (sumsq > maxsumsq)) { + max_row = rowp; + maxsumsq = sumsq; + } + } + if (max_row > row) { // Swap rows if not max row + var temp = M[row]; + M[row] = M[max_row]; + M[max_row] = temp; + } + + // Calculate row norm, save if this is first (largest) + row_norm = Math.sqrt(maxsumsq); + if (row == 0) mat_scale = row_norm; + + // Check for all zero rows + if (row_norm > mat_scale*eps) + scale = 1.0/row_norm; + else { + max_nonzero_row = row - 1; // Rest will be nullspace of M + break; + } + + + // Nonzero row, eliminate from rows below + var Mr = M[row]; + for (var col = Nc-1; col >= 0; --col) // Scale rhs also + Mr[col] *= scale; + for (var rowp = row + 1; rowp < Nr; rowp++) { // Update. + var Mrp = M[rowp]; + var inner = 0; + for (var col = Nc-2; col >= 0; --col) // Project + inner += Mr[col]*Mrp[col]; + for (var col = Nc-1; col >= 0; --col) // Ortho (rhs also) + Mrp[col] -= inner *Mr[col]; + } + } + + // Last Column of M has inv(R^T)*rhs. Scale rows of Q to get x. + var x = new Array(Nc-1); + for (var col = Nc-2; col >= 0; --col) + x[col] = 0; + for (var row = max_nonzero_row; row >= 0; --row) { + Mr = M[row]; + for (var col = Nc-2; col >= 0; --col) { + x[col] += Mr[col]*Mr[Nc-1]; + } + } + + // Return solution. + return x; + } + + // solve Mx=b and return vector x given augmented matrix M = [A | b] + // Uses Gaussian elimination with partial pivoting + function mat_solve(M,rhs) { + var N = M.length; // augmented matrix M has N rows, N+1 columns + var temp,i,j; + + // Copy the rhs in to the last column of M if one is given. + if (rhs != null) { + for (var row = 0; row < N ; row++) + M[row][N] = rhs[row]; + } + + // gaussian elimination + for (var col = 0; col < N ; col++) { + // find pivot: largest abs(v) in this column of remaining rows + var max_v = Math.abs(M[col][col]); + var max_col = col; + for (i = col + 1; i < N; i++) { + temp = Math.abs(M[i][col]); + if (temp > max_v) { max_v = temp; max_col = i; } + } + + // if no value found, generate a small conductance to gnd + // otherwise swap current row with pivot row + if (max_v == 0) M[col][col] = eps; + else { + temp = M[col]; + M[col] = M[max_col]; + M[max_col] = temp; + } + + // now eliminate this column for all subsequent rows + for (i = col + 1; i < N; i++) { + temp = M[i][col]/M[col][col]; // multiplier we'll use for current row + if (temp != 0) + // subtract current row from row we're working on + // remember to process b too! + for (j = col; j <= N; j++) M[i][j] -= M[col][j]*temp; + } + } + + // matrix is now upper triangular, so solve for elements of x starting + // with the last row + var x = new Array(N); + for (i = N-1; i >= 0; --i) { + temp = M[i][N]; // grab b[i] from augmented matrix as RHS + // subtract LHS term from RHS using known x values + for (j = N-1; j > i; --j) temp -= M[i][j]*x[j]; + // now compute new x value + x[i] = temp/M[i][i]; + } + + // return solution + return x; + } + + // test solution code, expect x = [2,3,-1] + //M = [[2,1,-1,8],[-3,-1,2,-11],[-2,1,2,-3]]; + //x = mat_solve(M); + //y = 1; // so we have place to set a breakpoint :) + + /////////////////////////////////////////////////////////////////////////////// + // + // Device base class + // + //////////////////////////////////////////////////////////////////////////////// + + function Device() { + } + + // complete initial set up of device + Device.prototype.finalize = function() { + } + + // Load the linear elements in to Gl and C + Device.prototype.load_linear = function(ckt) { + } + + // load linear system equations for dc analysis + // (inductors shorted and capacitors opened) + Device.prototype.load_dc = function(ckt,soln,rhs) { + } + + // load linear system equations for tran analysis + Device.prototype.load_tran = function(ckt,soln) { + } + + // load linear system equations for ac analysis: + // current sources open, voltage sources shorted + // linear models at operating point for everyone else + Device.prototype.load_ac = function(ckt,rhs) { + } + + // return time of next breakpoint for the device + Device.prototype.breakpoint = function(time) { + return undefined; + } + + /////////////////////////////////////////////////////////////////////////////// + // + // Parse numbers in engineering notation + // + /////////////////////////////////////////////////////////////////////////////// + + // convert first character of argument into an integer + function ord(ch) { + return ch.charCodeAt(0); + } + + // convert string argument to a number, accepting usual notations + // (hex, octal, binary, decimal, floating point) plus engineering + // scale factors (eg, 1k = 1000.0 = 1e3). + // return default if argument couldn't be interpreted as a number + function parse_number(s,default_v) { + var slen = s.length; + var multiplier = 1; + var result = 0; + var index = 0; + + // skip leading whitespace + while (index < slen && s.charAt(index) <= ' ') index += 1; + if (index == slen) return default_v; + + // check for leading sign + if (s.charAt(index) == '-') { + multiplier = -1; + index += 1; + } else if (s.charAt(index) == '+') + index += 1; + var start = index; // remember where digits start + + // if leading digit is 0, check for hex, octal or binary notation + if (index >= slen) return default_v; + else if (s.charAt(index) == '0') { + index += 1; + if (index >= slen) return 0; + if (s.charAt(index) == 'x' || s.charAt(index) == 'X') { // hex + while (true) { + index += 1; + if (index >= slen) break; + if (s.charAt(index) >= '0' && s.charAt(index) <= '9') + result = result*16 + ord(s.charAt(index)) - ord('0'); + else if (s.charAt(index) >= 'A' && s.charAt(index) <= 'F') + result = result*16 + ord(s.charAt(index)) - ord('A') + 10; + else if (s.charAt(index) >= 'a' && s.charAt(index) <= 'f') + result = result*16 + ord(s.charAt(index)) - ord('a') + 10; + else break; + } + return result*multiplier; + } else if (s.charAt(index) == 'b' || s.charAt(index) == 'B') { // binary + while (true) { + index += 1; + if (index >= slen) break; + if (s.charAt(index) >= '0' && s.charAt(index) <= '1') + result = result*2 + ord(s.charAt(index)) - ord('0'); + else break; + } + return result*multiplier; + } else if (s.charAt(index) != '.') { // octal + while (true) { + if (s.charAt(index) >= '0' && s.charAt(index) <= '7') + result = result*8 + ord(s.charAt(index)) - ord('0'); + else break; + index += 1; + if (index >= slen) break; + } + return result*multiplier; + } + } + + // read decimal integer or floating-point number + while (true) { + if (s.charAt(index) >= '0' && s.charAt(index) <= '9') + result = result*10 + ord(s.charAt(index)) - ord('0'); + else break; + index += 1; + if (index >= slen) break; + } + + // fractional part? + if (index < slen && s.charAt(index) == '.') { + while (true) { + index += 1; + if (index >= slen) break; + if (s.charAt(index) >= '0' && s.charAt(index) <= '9') { + result = result*10 + ord(s.charAt(index)) - ord('0'); + multiplier *= 0.1; + } else break; + } + } + + // if we haven't seen any digits yet, don't check + // for exponents or scale factors + if (index == start) return default_v; + + // type of multiplier determines type of result: + // multiplier is a float if we've seen digits past + // a decimal point, otherwise it's an int or long. + // Up to this point result is an int or long. + result *= multiplier; + + // now check for exponent or engineering scale factor. If there + // is one, result will be a float. + if (index < slen) { + var scale = s.charAt(index); + index += 1; + if (scale == 'e' || scale == 'E') { + var exponent = 0; + multiplier = 10.0; + if (index < slen) { + if (s.charAt(index) == '+') index += 1; + else if (s.charAt(index) == '-') { + index += 1; + multiplier = 0.1; + } + } + while (index < slen) { + if (s.charAt(index) >= '0' && s.charAt(index) <= '9') { + exponent = exponent*10 + ord(s.charAt(index)) - ord('0'); + index += 1; + } else break; + } + while (exponent > 0) { + exponent -= 1; + result *= multiplier; + } + } else if (scale == 't' || scale == 'T') result *= 1e12; + else if (scale == 'g' || scale == 'G') result *= 1e9; + else if (scale == 'M') result *= 1e6; + else if (scale == 'k' || scale == 'K') result *= 1e3; + else if (scale == 'm') result *= 1e-3; + else if (scale == 'u' || scale == 'U') result *= 1e-6; + else if (scale == 'n' || scale == 'N') result *= 1e-9; + else if (scale == 'p' || scale == 'P') result *= 1e-12; + else if (scale == 'f' || scale == 'F') result *= 1e-15; + } + // ignore any remaining chars, eg, 1kohms returns 1000 + return result; + } + + Circuit.prototype.parse_number = parse_number; // make it easy to call from outside + + /////////////////////////////////////////////////////////////////////////////// + // + // Sources + // + /////////////////////////////////////////////////////////////////////////////// + + // argument is a string describing the source's value (see comments for details) + // source types: dc,step,square,triangle,sin,pulse,pwl,pwl_repeating + + // returns an object with the following attributes: + // fun -- name of source function + // args -- list of argument values + // value(t) -- compute source value at time t + // inflection_point(t) -- compute time after t when a time point is needed + // dc -- value at time 0 + // period -- repeat period for periodic sources (0 if not periodic) + + function parse_source(v) { + // generic parser: parse v as either or (,...) + var src = new Object(); + src.period = 0; // Default not periodic + src.value = function(t) { return 0; } // overridden below + src.inflection_point = function(t) { return undefined; }; // may be overridden below + + // see if there's a "(" in the description + var index = v.indexOf('('); + var ch; + if (index >= 0) { + src.fun = v.slice(0,index); // function name is before the "(" + src.args = []; // we'll push argument values onto this list + var end = v.indexOf(')',index); + if (end == -1) end = v.length; + + index += 1; // start parsing right after "(" + while (index < end) { + // figure out where next argument value starts + ch = v.charAt(index); + if (ch <= ' ') { index++; continue; } + // and where it ends + var arg_end = v.indexOf(',',index); + if (arg_end == -1) arg_end = end; + // parse and save result in our list of arg values + src.args.push(parse_number(v.slice(index,arg_end),undefined)); + index = arg_end + 1; + } + } else { + src.fun = 'dc'; + src.args = [parse_number(v,0)]; + } + + // post-processing for constant sources + // dc(v) + if (src.fun == 'dc') { + var v = arg_value(src.args,0,0); + src.args = [v]; + src.value = function(t) { return v; } // closure + } + + // post-processing for impulse sources + // impulse(height,width) + else if (src.fun == 'impulse') { + var h = arg_value(src.args,0,1); // default height: 1 + var w = Math.abs(arg_value(src.args,2,1e-9)); // default width: 1ns + src.args = [h,w]; // remember any defaulted values + pwl_source(src,[0,0,w/2,h,w,0],false); + } + + // post-processing for step sources + // step(v_init,v_plateau,t_delay,t_rise) + else if (src.fun == 'step') { + var v1 = arg_value(src.args,0,0); // default init value: 0V + var v2 = arg_value(src.args,1,1); // default plateau value: 1V + var td = Math.max(0,arg_value(src.args,2,0)); // time step starts + var tr = Math.abs(arg_value(src.args,3,1e-9)); // default rise time: 1ns + src.args = [v1,v2,td,tr]; // remember any defaulted values + pwl_source(src,[td,v1,td+tr,v2],false); + } + + // post-processing for square wave + // square(v_init,v_plateau,freq,duty_cycle) + else if (src.fun == 'square') { + var v1 = arg_value(src.args,0,0); // default init value: 0V + var v2 = arg_value(src.args,1,1); // default plateau value: 1V + var freq = Math.abs(arg_value(src.args,2,1)); // default frequency: 1Hz + var duty_cycle = Math.min(100,Math.abs(arg_value(src.args,3,50))); // default duty cycle: 0.5 + src.args = [v1,v2,freq,duty_cycle]; // remember any defaulted values + + var per = freq == 0 ? Infinity : 1/freq; + var t_change = 0.01 * per; // rise and fall time + var t_pw = .01 * duty_cycle * 0.98 * per; // fraction of cycle minus rise and fall time + pwl_source(src,[0,v1,t_change,v2,t_change+t_pw, + v2,t_change+t_pw+t_change,v1,per,v1],true); + } + + // post-processing for triangle + // triangle(v_init,v_plateua,t_period) + else if (src.fun == 'triangle') { + var v1 = arg_value(src.args,0,0); // default init value: 0V + var v2 = arg_value(src.args,1,1); // default plateau value: 1V + var freq = Math.abs(arg_value(src.args,2,1)); // default frequency: 1s + src.args = [v1,v2,freq]; // remember any defaulted values + + var per = freq == 0 ? Infinity : 1/freq; + pwl_source(src,[0,v1,per/2,v2,per,v1],true); + } + + // post-processing for pwl and pwlr sources + // pwl[r](t1,v1,t2,v2,...) + else if (src.fun == 'pwl' || src.fun == 'pwl_repeating') { + pwl_source(src,src.args,src.fun == 'pwl_repeating'); + } + + // post-processing for pulsed sources + // pulse(v_init,v_plateau,t_delay,t_rise,t_fall,t_width,t_period) + else if (src.fun == 'pulse') { + var v1 = arg_value(src.args,0,0); // default init value: 0V + var v2 = arg_value(src.args,1,1); // default plateau value: 1V + var td = Math.max(0,arg_value(src.args,2,0)); // time pulse starts + var tr = Math.abs(arg_value(src.args,3,1e-9)); // default rise time: 1ns + var tf = Math.abs(arg_value(src.args,4,1e-9)); // default rise time: 1ns + var pw = Math.abs(arg_value(src.args,5,1e9)); // default pulse width: "infinite" + var per = Math.abs(arg_value(src.args,6,1e9)); // default period: "infinite" + src.args = [v1,v2,td,tr,tf,pw,per]; + + var t1 = td; // time when v1 -> v2 transition starts + var t2 = t1 + tr; // time when v1 -> v2 transition ends + var t3 = t2 + pw; // time when v2 -> v1 transition starts + var t4 = t3 + tf; // time when v2 -> v1 transition ends + + pwl_source(src,[t1,v1, t2,v2, t3,v2, t4,v1, per,v1],true); + } + + // post-processing for sinusoidal sources + // sin(v_offset,v_amplitude,freq_hz,t_delay,phase_offset_degrees) + else if (src.fun == 'sin') { + var voffset = arg_value(src.args,0,0); // default offset voltage: 0V + var va = arg_value(src.args,1,1); // default amplitude: -1V to 1V + var freq = Math.abs(arg_value(src.args,2,1)); // default frequency: 1Hz + src.period = 1.0/freq; + + var td = Math.max(0,arg_value(src.args,3,0)); // default time delay: 0sec + var phase = arg_value(src.args,4,0); // default phase offset: 0 degrees + src.args = [voffset,va,freq,td,phase]; + + phase /= 360.0; + + // return value of source at time t + src.value = function(t) { // closure + if (t < td) return voffset + va*Math.sin(2*Math.PI*phase); + else return voffset + va*Math.sin(2*Math.PI*(freq*(t - td) + phase)); + } + + // return time of next inflection point after time t + src.inflection_point = function(t) { // closure + if (t < td) return td; + else return undefined; + } + } + + // object has all the necessary info to compute the source value and inflection points + src.dc = src.value(0); // DC value is value at time 0 + return src; + } + + function pwl_source(src,tv_pairs,repeat) { + var nvals = tv_pairs.length; + if (repeat) + src.period = tv_pairs[nvals-2]; // Repeat period of source + if (nvals % 2 == 1) npts -= 1; // make sure it's even! + + if (nvals <= 2) { + // handle degenerate case + src.value = function(t) { return nvals == 2 ? tv_pairs[1] : 0; } + src.inflection_point = function(t) { return undefined; } + } else { + src.value = function(t) { // closure + if (repeat) + // make time periodic if values are to be repeated + t = Math.fmod(t,tv_pairs[nvals-2]); + var last_t = tv_pairs[0]; + var last_v = tv_pairs[1]; + if (t > last_t) { + var next_t,next_v; + for (var i = 2; i < nvals; i += 2) { + next_t = tv_pairs[i]; + next_v = tv_pairs[i+1]; + if (next_t > last_t) // defend against bogus tv pairs + if (t < next_t) + return last_v + (next_v - last_v)*(t - last_t)/(next_t - last_t); + last_t = next_t; + last_v = next_v; + } + } + return last_v; + } + src.inflection_point = function(t) { // closure + if (repeat) + // make time periodic if values are to be repeated + t = Math.fmod(t,tv_pairs[nvals-2]); + for (var i = 0; i < nvals; i += 2) { + var next_t = tv_pairs[i]; + if (t < next_t) return next_t; + } + return undefined; + } + } + } + + // helper function: return args[index] if present, else default_v + function arg_value(args,index,default_v) { + if (index < args.length) { + var result = args[index]; + if (result === undefined) result = default_v; + return result; + } else return default_v; + } + + // we need fmod in the Math library! + Math.fmod = function(numerator,denominator) { + var quotient = Math.floor(numerator/denominator); + return numerator - quotient*denominator; + } + + /////////////////////////////////////////////////////////////////////////////// + // + // Sources + // + /////////////////////////////////////////////////////////////////////////////// + + function VSource(npos,nneg,branch,v) { + Device.call(this); + + this.src = parse_source(v); + this.npos = npos; + this.nneg = nneg; + this.branch = branch; + } + VSource.prototype = new Device(); + VSource.prototype.constructor = VSource; + + // load linear part for source evaluation + VSource.prototype.load_linear = function(ckt) { + // MNA stamp for independent voltage source + ckt.add_to_Gl(this.branch,this.npos,1.0); + ckt.add_to_Gl(this.branch,this.nneg,-1.0); + ckt.add_to_Gl(this.npos,this.branch,1.0); + ckt.add_to_Gl(this.nneg,this.branch,-1.0); + } + + // Source voltage added to b. + VSource.prototype.load_dc = function(ckt,soln,rhs) { + ckt.add_to_rhs(this.branch,this.src.dc,rhs); + } + + // Load time-dependent value for voltage source for tran + VSource.prototype.load_tran = function(ckt,soln,rhs,time) { + ckt.add_to_rhs(this.branch,this.src.value(time),rhs); + } + + // return time of next breakpoint for the device + VSource.prototype.breakpoint = function(time) { + return this.src.inflection_point(time); + } + + // small signal model ac value + VSource.prototype.load_ac = function(ckt,rhs) { + ckt.add_to_rhs(this.branch,1.0,rhs); + } + + function ISource(npos,nneg,v) { + Device.call(this); + + this.src = parse_source(v); + this.npos = npos; + this.nneg = nneg; + } + ISource.prototype = new Device(); + ISource.prototype.constructor = ISource; + + ISource.prototype.load_linear = function(ckt) { + // Current source is open when off, no linear contribution + } + + // load linear system equations for dc analysis + ISource.prototype.load_dc = function(ckt,soln,rhs) { + var is = this.src.dc; + + // MNA stamp for independent current source + ckt.add_to_rhs(this.npos,-is,rhs); // current flow into npos + ckt.add_to_rhs(this.nneg,is,rhs); // and out of nneg + } + + // load linear system equations for tran analysis (just like DC) + ISource.prototype.load_tran = function(ckt,soln,rhs,time) { + var is = this.src.value(time); + + // MNA stamp for independent current source + ckt.add_to_rhs(this.npos,-is,rhs); // current flow into npos + ckt.add_to_rhs(this.nneg,is,rhs); // and out of nneg + } + + // return time of next breakpoint for the device + ISource.prototype.breakpoint = function(time) { + return this.src.inflection_point(time); + } + + // small signal model: open circuit + ISource.prototype.load_ac = function(ckt,rhs) { + // MNA stamp for independent current source + ckt.add_to_rhs(this.npos,-1.0,rhs); // current flow into npos + ckt.add_to_rhs(this.nneg,1.0,rhs); // and out of nneg + } + + /////////////////////////////////////////////////////////////////////////////// + // + // Resistor + // + /////////////////////////////////////////////////////////////////////////////// + + function Resistor(n1,n2,v) { + Device.call(this); + this.n1 = n1; + this.n2 = n2; + this.g = 1.0/v; + } + Resistor.prototype = new Device(); + Resistor.prototype.constructor = Resistor; + + Resistor.prototype.load_linear = function(ckt) { + // MNA stamp for admittance g + ckt.add_conductance_l(this.n1,this.n2,this.g); + } + + Resistor.prototype.load_dc = function(ckt) { + // Nothing to see here, move along. + } + + Resistor.prototype.load_tran = function(ckt,soln) { + } + + Resistor.prototype.load_ac = function(ckt) { + } + + /////////////////////////////////////////////////////////////////////////////// + // + // Diode + // + /////////////////////////////////////////////////////////////////////////////// + + function Diode(n1,n2,v,type) { + Device.call(this); + this.anode = n1; + this.cathode = n2; + this.area = v; + this.type = type; // 'normal' or 'ideal' + this.is = 1.0e-14; + this.ais = this.area * this.is; + this.vt = (type == 'normal') ? 25.8e-3 : 0.1e-3; // 26mv or .1mv + this.exp_arg_max = 50; // less than single precision max. + this.exp_max = Math.exp(this.exp_arg_max); + } + Diode.prototype = new Device(); + Diode.prototype.constructor = Diode; + + Diode.prototype.load_linear = function(ckt) { + // Diode is not linear, has no linear piece. + } + + Diode.prototype.load_dc = function(ckt,soln,rhs) { + var vd = ckt.get_two_terminal(this.anode, this.cathode, soln); + var exp_arg = vd / this.vt; + var temp1, temp2; + // Estimate exponential with a quadratic if arg too big. + var abs_exp_arg = Math.abs(exp_arg); + var d_arg = abs_exp_arg - this.exp_arg_max; + if (d_arg > 0) { + var quad = 1 + d_arg + 0.5*d_arg*d_arg; + temp1 = this.exp_max * quad; + temp2 = this.exp_max * (1 + d_arg); + } else { + temp1 = Math.exp(abs_exp_arg); + temp2 = temp1; + } + if (exp_arg < 0) { // Use exp(-x) = 1.0/exp(x) + temp1 = 1.0/temp1; + temp2 = (temp1*temp2)*temp1; + } + var id = this.ais * (temp1 - 1); + var gd = this.ais * (temp2 / this.vt); + + // MNA stamp for independent current source + ckt.add_to_rhs(this.anode,-id,rhs); // current flows into anode + ckt.add_to_rhs(this.cathode,id,rhs); // and out of cathode + ckt.add_conductance(this.anode,this.cathode,gd); + } + + Diode.prototype.load_tran = function(ckt,soln,rhs,time) { + this.load_dc(ckt,soln,rhs); + } + + Diode.prototype.load_ac = function(ckt) { + } + + + /////////////////////////////////////////////////////////////////////////////// + // + // Capacitor + // + /////////////////////////////////////////////////////////////////////////////// + + function Capacitor(n1,n2,v) { + Device.call(this); + this.n1 = n1; + this.n2 = n2; + this.value = v; + } + Capacitor.prototype = new Device(); + Capacitor.prototype.constructor = Capacitor; + + Capacitor.prototype.load_linear = function(ckt) { + // MNA stamp for capacitance matrix + ckt.add_capacitance(this.n1,this.n2,this.value); + } + + Capacitor.prototype.load_dc = function(ckt,soln,rhs) { + } + + Capacitor.prototype.load_ac = function(ckt) { + } + + Capacitor.prototype.load_tran = function(ckt) { + } + + /////////////////////////////////////////////////////////////////////////////// + // + // Inductor + // + /////////////////////////////////////////////////////////////////////////////// + + function Inductor(n1,n2,branch,v) { + Device.call(this); + this.n1 = n1; + this.n2 = n2; + this.branch = branch; + this.value = v; + } + Inductor.prototype = new Device(); + Inductor.prototype.constructor = Inductor; + + Inductor.prototype.load_linear = function(ckt) { + // MNA stamp for inductor linear part + // L on diag of C because L di/dt = v(n1) - v(n2) + ckt.add_to_Gl(this.n1,this.branch,1); + ckt.add_to_Gl(this.n2,this.branch,-1); + ckt.add_to_Gl(this.branch,this.n1,-1); + ckt.add_to_Gl(this.branch,this.n2,1); + ckt.add_to_C(this.branch,this.branch,this.value) + } + + Inductor.prototype.load_dc = function(ckt,soln,rhs) { + // Inductor is a short at dc, so is linear. + } + + Inductor.prototype.load_ac = function(ckt) { + } + + Inductor.prototype.load_tran = function(ckt) { + } + + + + /////////////////////////////////////////////////////////////////////////////// + // + // Simple Voltage-Controlled Voltage Source Op Amp model + // + /////////////////////////////////////////////////////////////////////////////// + + function Opamp(np,nn,no,ng,branch,A,name) { + Device.call(this); + this.np = np; + this.nn = nn; + this.no = no; + this.ng = ng; + this.branch = branch; + this.gain = A; + this.name = name; + } + + Opamp.prototype = new Device(); + Opamp.prototype.constructor = Opamp; + + Opamp.prototype.load_linear = function(ckt) { + // MNA stamp for VCVS: 1/A(v(no) - v(ng)) - (v(np)-v(nn))) = 0. + var invA = 1.0/this.gain; + ckt.add_to_Gl(this.no,this.branch,1); + ckt.add_to_Gl(this.ng,this.branch,-1); + ckt.add_to_Gl(this.branch,this.no,invA); + ckt.add_to_Gl(this.branch,this.ng,-invA); + ckt.add_to_Gl(this.branch,this.np,-1); + ckt.add_to_Gl(this.branch,this.nn,1); + } + + Opamp.prototype.load_dc = function(ckt,soln,rhs) { + // Op-amp is linear. + } + + Opamp.prototype.load_ac = function(ckt) { + } + + Opamp.prototype.load_tran = function(ckt) { + } + + + + /////////////////////////////////////////////////////////////////////////////// + // + // Simplified MOS FET with no bulk connection and no body effect. + // + /////////////////////////////////////////////////////////////////////////////// + + + function Fet(d,g,s,ratio,name,type) { + Device.call(this); + this.d = d; + this.g = g; + this.s = s; + this.name = name; + this.ratio = ratio; + if (type != 'n' && type != 'p') + { throw 'fet type is not n or p'; + } + this.type_sign = (type == 'n') ? 1 : -1; + this.vt = 0.5; + this.kp = 20e-6; + this.beta = this.kp * this.ratio; + this.lambda = 0.05; + } + Fet.prototype = new Device(); + Fet.prototype.constructor = Fet; + + Fet.prototype.load_linear = function(ckt) { + // FET's are nonlinear, just like javascript progammers + } + + Fet.prototype.load_dc = function(ckt,soln,rhs) { + var vds = this.type_sign * ckt.get_two_terminal(this.d, this.s, soln); + if (vds < 0) { // Drain and source have swapped roles + var temp = this.d; + this.d = this.s; + this.s = temp; + vds = this.type_sign * ckt.get_two_terminal(this.d, this.s, soln); + } + var vgs = this.type_sign * ckt.get_two_terminal(this.g, this.s, soln); + var vgst = vgs - this.vt; + with (this) { + var gmgs,ids,gds; + if (vgst > 0.0 ) { // vgst < 0, transistor off, no subthreshold here. + if (vgst < vds) { /* Saturation. */ + gmgs = beta * (1 + (lambda * vds)) * vgst; + ids = type_sign * 0.5 * gmgs * vgst; + gds = 0.5 * beta * vgst * vgst * lambda; + } else { /* Linear region */ + gmgs = beta * (1 + lambda * vds); + ids = type_sign * gmgs * vds * (vgst - 0.50 * vds); + gds = gmgs * (vgst - vds) + beta * lambda * vds * (vgst - 0.5 * vds); + gmgs *= vds; + } + ckt.add_to_rhs(d,-ids,rhs); // current flows into the drain + ckt.add_to_rhs(s, ids,rhs); // and out the source + ckt.add_conductance(d,s,gds); + ckt.add_to_G(s,s, gmgs); + ckt.add_to_G(d,s,-gmgs); + ckt.add_to_G(d,g, gmgs); + ckt.add_to_G(s,g,-gmgs); + } + } + } + + Fet.prototype.load_tran = function(ckt,soln,rhs) { + this.load_dc(ckt,soln,rhs); + } + + Fet.prototype.load_ac = function(ckt) { + } + + + /////////////////////////////////////////////////////////////////////////////// + // + // Module definition + // + /////////////////////////////////////////////////////////////////////////////// + var module = { + 'Circuit': Circuit, + 'parse_number': parse_number, + 'parse_source': parse_source, + } + return module; + }()); + ///////////////////////////////////////////////////////////////////////////// // // Simple schematic capture From 02ddeed72a4c17c490271cd51fa4df9237199346 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Sep 2012 16:24:59 -0400 Subject: [PATCH 15/35] better error handling in index view --- lms/djangoapps/courseware/views.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index ec96a38d3d..334b335d26 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -208,7 +208,7 @@ def index(request, course_id, chapter=None, section=None, log.warning("TEMP: course_id {}, chap {}, sec {}, first_time {}, course position = {}" .format(course_id, chapter, section, first_time, course_module.position)) - if chapter is None and section is None: + if chapter is None: return redirect_to_course_position(course_module, first_time) context = { @@ -225,9 +225,14 @@ def index(request, course_id, chapter=None, section=None, if chapter_descriptor is not None: instance_module = get_instance_module(course_id, request.user, course_module, student_module_cache) save_child_position(course_module, chapter, instance_module) + else: + raise Http404 chapter_module = get_module(request.user, request, chapter_descriptor.location, student_module_cache, course_id) + if chapter_module is None: + # User may be trying to access a chapter that isn't live yet + raise Http404 if section is not None: section_descriptor = chapter_descriptor.get_child_by_url_name(section) @@ -267,7 +272,11 @@ def index(request, course_id, chapter=None, section=None, 'prev_section_url': prev_section_url}) result = render_to_response('courseware/courseware.html', context) - except: + except Exception as e: + if isinstance(e, Http404): + # let it propagate + raise + # In production, don't want to let a 500 out for any reason if settings.DEBUG: raise From d923be07868ef1b0cf71064ee420b2cf750b38fb Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Thu, 6 Sep 2012 15:59:15 -0400 Subject: [PATCH 16/35] Added outofocus for sign in and log in forms --- lms/static/coffee/src/main.coffee | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lms/static/coffee/src/main.coffee b/lms/static/coffee/src/main.coffee index d74035cd2a..ec5cbdec5b 100644 --- a/lms/static/coffee/src/main.coffee +++ b/lms/static/coffee/src/main.coffee @@ -29,3 +29,11 @@ $ -> window.postJSON = (url, data, callback) -> $.postWithPrefix url, data, callback + + $('#login').click -> + $('#login_form input[name="email"]').focus() + false + + $('#signup').click -> + $('#signup-modal input[name="email"]').focus() + false From fc793b94fcc4b795257d348350f8af38ae372bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Carlos=20Andr=C3=A9s=20Rocha?= Date: Thu, 6 Sep 2012 16:33:10 -0400 Subject: [PATCH 17/35] Added proctored exams news to the frontpage --- lms/static/images/press/diploma_109x65.jpg | Bin 0 -> 1744 bytes lms/templates/feed.rss | 10 +++++++++- 2 files changed, 9 insertions(+), 1 deletion(-) create mode 100644 lms/static/images/press/diploma_109x65.jpg diff --git a/lms/static/images/press/diploma_109x65.jpg b/lms/static/images/press/diploma_109x65.jpg new file mode 100644 index 0000000000000000000000000000000000000000..964a4952760dc553c4289fc1d4345ca62dcae523 GIT binary patch literal 1744 zcmb78X;hO37X7|_30Vq(k5!f=1|t|CD4Vhg0tF#ZBw=v~J&Y|AmOx|?TLqL&i-$$R zJ|Y1L1PF`NNJOcKfPy$eHGmSp1p$vnz$#&pqo!$pv@<{FzVqHW@7#CqyUHo$3qXtR zKp>GwItHc`QzFHLNFx4W0;;5FH8dWB!4q|1UE==@ud zyaSj30Eqe{?cacqs*D&Ap<*4h01$!@kQx$&g#Nq$5fBnU!D`w%WCq&Q#^bwWUE7j+ zy&ay}{TO{a=JzRAR5C*lfd1DLKtLc8rJ{&1fIz60|9S`_v>_O&L-w#$P6O%?s0s(c zfIBct$8_!;ubQti+J&cyGnqCeYps&E2fvLLdKZPJ#<(}~4$kPwr(U+2*93FbufNoC zUuX|D+}GxsxH>Cnc-kMn_(Z%M(AXmQbir#FPd)HBoGh$gye3?sGLX47FPp78)UPz9 z;?~Bh0?uP^bpO(VCb7y2GOqfON{;3C9e8m$PIf=50^$kw7wwKW?jQtAUrb_4gqdqj zoxPILFwLP?--e$}sM_xP&wJ%PJ{j_NiBVmcCm|7&Y02H?Bk6ed5sH>G$u2;}U(Eaj5VDN8ZGaHfcgueV4z|J&j0*0nLW@&x^bft;M0{bKq#@$;Z-QbJI&L|qug56nowM+n6= zg;^metMTH(*k8z3-?L7NXF7y&uIGZrIBv7jxmg1)GhaqaZm2M6p;+Sl6o!HO<>5bC zrZ5E;sK6gvSFQK7>uDMHFS0U+uVs+98i<;8nyE$k-~7j^F-t%xu2Mt zgr?7+7(4q3RBS=?4vXV!$i`ELB>wtI*v0Usu0G9`T`9|pfVwl3njiwvyyY1_rI`__l;Sc^f7gy^BN z(s_>MPr{r%o@Mg^wGZ#2{5g*Y9e>U2sR;?}TDA=~&>Xa4EIe(d%%x-JuBGX`;_RJl zbn$tD&r9q+O#4~i=T$hvcX-t=n(I@gVT|LBGIY%BFMyOv)?uxNiIRJv*aeP;T}Obm z5x=M!J z)ViOUUC-u9M*O)VDf4YUcB)RXRcF-uhCLQ{TciXqTg*B_&B$kYN4vsm#%pXpOabSM zZT#P8e4S_Pee1>C*X0_QI@fUT^30q;0VrY z)S^1xb84xu(ZLY!=v1M*S$&mUE_7i6&~0$epOHVBRi)1OIaQeKeFQcru#a1!;`d^A z5cLYy&&v8eYsxgl75FHWR z)0O*q@S$zoC=+Xse-$oN&r14YuB=2n9zUX&s;4P*ajif-ZDZx09MkyW{l`<8!mAf| z6Z}6YsGr^baryD EdX Blog 2012-07-16T14:08:12-07:00 + + tag:www.edx.org,2012:Post/4 + 2012-09-06T14:00:00-07:00 + 2012-09-06T14:00:00-07:00 + + EdX to offer learners option of taking proctored final exam + <img src="${static.url('images/press/diploma_109x65.jpg')}" /> + tag:www.edx.org,2012:Post/3 2012-07-16T14:08:12-07:00 2012-07-16T14:08:12-07:00 UC Berkeley joins edX - <img src="${static.url('images/edx.png')}" /> + <img src="${static.url('images/edx.png')}" /> <p>edX broadens course offerings</p> From 048dea0eff652b5fc7378a5e8f416a928ab3f3b6 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Sep 2012 16:38:33 -0400 Subject: [PATCH 18/35] make test course loading deterministic by using course_id --- common/test/data/full/course.xml | 2 +- lms/djangoapps/courseware/tests/tests.py | 16 ++++++++-------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/common/test/data/full/course.xml b/common/test/data/full/course.xml index c365e68cc1..4f093a1128 100644 --- a/common/test/data/full/course.xml +++ b/common/test/data/full/course.xml @@ -1 +1 @@ - + diff --git a/lms/djangoapps/courseware/tests/tests.py b/lms/djangoapps/courseware/tests/tests.py index a62e9ab0cd..62456d65d5 100644 --- a/lms/djangoapps/courseware/tests/tests.py +++ b/lms/djangoapps/courseware/tests/tests.py @@ -294,12 +294,12 @@ class TestNavigation(PageLoader): xmodule.modulestore.django._MODULESTORES = {} courses = modulestore().get_courses() - def find_course(name): + def find_course(course_id): """Assumes the course is present""" - return [c for c in courses if c.location.course==name][0] + return [c for c in courses if c.id==course_id][0] - self.full = find_course("full") - self.toy = find_course("toy") + self.full = find_course("edX/full/6.002_Spring_2012") + self.toy = find_course("edX/toy/2012_Fall") # Create two accounts self.student = 'view@test.com' @@ -352,12 +352,12 @@ class TestViewAuth(PageLoader): xmodule.modulestore.django._MODULESTORES = {} courses = modulestore().get_courses() - def find_course(name): + def find_course(course_id): """Assumes the course is present""" - return [c for c in courses if c.location.course==name][0] + return [c for c in courses if c.id==course_id][0] - self.full = find_course("full") - self.toy = find_course("toy") + self.full = find_course("edX/full/6.002_Spring_2012") + self.toy = find_course("edX/toy/2012_Fall") # Create two accounts self.student = 'view@test.com' From 8b7390c9665045c4fa1a9cc1d8f3e277daff438e Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Sep 2012 17:11:21 -0400 Subject: [PATCH 19/35] Turn of discussion service in test env - add a check for the feature flag before trying to save user info to the service --- common/djangoapps/student/models.py | 3 +++ lms/envs/test.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/common/djangoapps/student/models.py b/common/djangoapps/student/models.py index c37b9fce16..7e8658045e 100644 --- a/common/djangoapps/student/models.py +++ b/common/djangoapps/student/models.py @@ -282,6 +282,9 @@ def add_user_to_default_group(user, group): @receiver(post_save, sender=User) def update_user_information(sender, instance, created, **kwargs): + if not settings.MITX_FEATURES['ENABLE_DISCUSSION_SERVICE']: + # Don't try--it won't work, and it will fill the logs with lots of errors + return try: cc_user = cc.User.from_django_user(instance) cc_user.save() diff --git a/lms/envs/test.py b/lms/envs/test.py index 7cab4cb52c..b55cf2af5f 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -16,6 +16,9 @@ from path import path # can test everything else :) MITX_FEATURES['DISABLE_START_DATES'] = True +# Until we have discussion actually working in test mode, just turn it off +MITX_FEATURES['ENABLE_DISCUSSION_SERVICE'] = False + # Need wiki for courseware views to work. TODO (vshnayder): shouldn't need it. WIKI_ENABLED = True From 6017aa698b295ab8e6d3445c3f785fa645119a1c Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Thu, 6 Sep 2012 17:28:12 -0400 Subject: [PATCH 20/35] add ENABLE_DISCUSSION_SERVICE: "False" to cms config --- cms/envs/common.py | 1 + 1 file changed, 1 insertion(+) diff --git a/cms/envs/common.py b/cms/envs/common.py index 1767202141..f1440c7292 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -35,6 +35,7 @@ from path import path MITX_FEATURES = { 'USE_DJANGO_PIPELINE': True, 'GITHUB_PUSH': False, + 'ENABLE_DISCUSSION_SERVICE': False } # needed to use lms student app From 1e94ff19f3bbc884de971b103c4551e44d7ec8df Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 7 Sep 2012 08:20:59 -0400 Subject: [PATCH 21/35] remove debuging log message --- lms/djangoapps/courseware/views.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lms/djangoapps/courseware/views.py b/lms/djangoapps/courseware/views.py index 334b335d26..c474da8d8b 100644 --- a/lms/djangoapps/courseware/views.py +++ b/lms/djangoapps/courseware/views.py @@ -205,9 +205,6 @@ def index(request, course_id, chapter=None, section=None, ' far, should have gotten a course module for this user') return redirect(reverse('about_course', args=[course.id])) - log.warning("TEMP: course_id {}, chap {}, sec {}, first_time {}, course position = {}" - .format(course_id, chapter, section, first_time, course_module.position)) - if chapter is None: return redirect_to_course_position(course_module, first_time) @@ -276,7 +273,7 @@ def index(request, course_id, chapter=None, section=None, if isinstance(e, Http404): # let it propagate raise - + # In production, don't want to let a 500 out for any reason if settings.DEBUG: raise From b276d8b4b11c422ec9fade45e838e98b3772fb9f Mon Sep 17 00:00:00 2001 From: David Ormsbee Date: Fri, 7 Sep 2012 09:47:13 -0400 Subject: [PATCH 22/35] Changes as per cpennington's code review --- lms/djangoapps/django_comment_client/utils.py | 2 +- lms/static/sass/_discussion.scss | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lms/djangoapps/django_comment_client/utils.py b/lms/djangoapps/django_comment_client/utils.py index 89d3cd99ba..6da7130c52 100644 --- a/lms/djangoapps/django_comment_client/utils.py +++ b/lms/djangoapps/django_comment_client/utils.py @@ -215,7 +215,7 @@ def permalink(content): def extend_content(content): user = User.objects.get(pk=content['user_id']) - roles = dict([('name', role.name.lower()) for role in user.roles.filter(course_id=content['course_id'])]) + roles = dict(('name', role.name.lower()) for role in user.roles.filter(course_id=content['course_id'])) content_info = { 'displayed_title': content.get('highlighted_title') or content.get('title', ''), 'displayed_body': content.get('highlighted_body') or content.get('body', ''), diff --git a/lms/static/sass/_discussion.scss b/lms/static/sass/_discussion.scss index 869148a795..f19dbe36ff 100644 --- a/lms/static/sass/_discussion.scss +++ b/lms/static/sass/_discussion.scss @@ -391,7 +391,7 @@ $tag-text-color: #5b614f; } .author-administrator:after{ - content: " (administrator)" + content: " (instructor)" } } From 012edbfd40079586c8750c53672b496fbacf6de7 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 7 Sep 2012 11:14:26 -0400 Subject: [PATCH 23/35] Fix main navigation and footer links also make text not italic --- lms/static/sass/course/base/_base.scss | 1 + lms/static/sass/course/layout/_courseware_header.scss | 4 ++-- lms/static/sass/shared/_footer.scss | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index 1edb9aa7ba..9d6cf81c9f 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -2,6 +2,7 @@ body { min-width: 980px; min-height: 100%; background: url(../images/bg-texture.png) #d6d6d6; + font-style: normal; } body, h1, h2, h3, h4, h5, h6, p, p a:link, p a:visited, a, label { diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index 86b5fe58c5..f278339523 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -40,9 +40,9 @@ nav.course-material { } &.active { - // background: rgba(0, 0, 0, .2); + background-color: #333; + background-color: rgba(0, 0, 0, 0); @include linear-gradient(top, rgba(0, 0, 0, .4), rgba(0, 0, 0, .25)); - background-color: transparent; @include box-shadow(0 1px 0 rgba(255, 255, 255, .5), 0 1px 1px rgba(0, 0, 0, .3) inset); color: #fff; text-shadow: 0 1px 0 rgba(0, 0, 0, .4); diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss index d182ed4316..a418b887ad 100644 --- a/lms/static/sass/shared/_footer.scss +++ b/lms/static/sass/shared/_footer.scss @@ -110,6 +110,7 @@ footer { padding: 0; a { + @include inline-block; opacity: 0.3; @include transition(all, 0.1s, linear); From aec8cc42dcda83457b81986a0ce01ba3b7bc6655 Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 7 Sep 2012 12:59:30 -0400 Subject: [PATCH 24/35] Add ie fix for wiki --- lms/static/sass/course/base/_base.scss | 5 ----- lms/static/sass/course/layout/_courseware_header.scss | 3 +-- lms/static/sass/course/wiki/_wiki.scss | 2 +- lms/static/sass/ie.scss | 11 +++++++++++ lms/templates/main.html | 6 +++--- lms/templates/main_django.html | 8 +++++++- 6 files changed, 23 insertions(+), 12 deletions(-) diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index 9d6cf81c9f..902c8025fd 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -40,11 +40,6 @@ a { } } - - - - - form { label { display: block; diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index f278339523..103139b330 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -40,9 +40,8 @@ nav.course-material { } &.active { - background-color: #333; - background-color: rgba(0, 0, 0, 0); @include linear-gradient(top, rgba(0, 0, 0, .4), rgba(0, 0, 0, .25)); + background-color: transparent; @include box-shadow(0 1px 0 rgba(255, 255, 255, .5), 0 1px 1px rgba(0, 0, 0, .3) inset); color: #fff; text-shadow: 0 1px 0 rgba(0, 0, 0, .4); diff --git a/lms/static/sass/course/wiki/_wiki.scss b/lms/static/sass/course/wiki/_wiki.scss index 1bc38abd9a..84260e2a86 100644 --- a/lms/static/sass/course/wiki/_wiki.scss +++ b/lms/static/sass/course/wiki/_wiki.scss @@ -397,7 +397,7 @@ section.wiki { #hint_id_content { position: absolute; - top: 10px; + top: 4px; right: 0%; font-size: 12px; text-align:right; diff --git a/lms/static/sass/ie.scss b/lms/static/sass/ie.scss index 35997d95a7..ff5608deaf 100644 --- a/lms/static/sass/ie.scss +++ b/lms/static/sass/ie.scss @@ -133,3 +133,14 @@ header.global { .modal .inner-wrapper form label { display: block; } + +nav.course-material ol.course-tabs li a.active, nav.course-material .xmodule_SequenceModule nav.sequence-nav ol.course-tabs li a.seq_video.active, .xmodule_SequenceModule nav.sequence-nav nav.course-material ol.course-tabs li a.seq_video.active { + background-color: #333; + background-color: rgba(0, 0, 0, .4); +} + +header.global ol.user > li.primary a.dropdown { + padding-top: 6px; + padding-bottom: 6px; +} + diff --git a/lms/templates/main.html b/lms/templates/main.html index 51b8d6030e..ec0b907fdf 100644 --- a/lms/templates/main.html +++ b/lms/templates/main.html @@ -8,9 +8,6 @@ <%static:css group='application'/> - <%static:js group='main_vendor'/> <%block name="headextra"/> @@ -18,6 +15,9 @@ + diff --git a/lms/templates/main_django.html b/lms/templates/main_django.html index d300e666a9..fa59f0128b 100644 --- a/lms/templates/main_django.html +++ b/lms/templates/main_django.html @@ -11,7 +11,13 @@ {% block headextra %}{% endblock %} {% render_block "css" %} + + + + @@ -40,4 +46,4 @@ Inheriting from this file allows us to include apps that use the django templating system without rewriting all of their views in mako. -{% endcomment %} \ No newline at end of file +{% endcomment %} From 4d4e5824cdd0c11ebac601bf15a5fa99679232ee Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 7 Sep 2012 13:29:49 -0400 Subject: [PATCH 25/35] Change form style for login and signup modals so it is consistant in every browser and we have no usability problems --- lms/static/sass/ie.scss | 4 ---- lms/static/sass/shared/_modal.scss | 11 +++++++++-- lms/templates/login_modal.html | 4 ++-- lms/templates/signup_modal.html | 14 +++++++------- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/lms/static/sass/ie.scss b/lms/static/sass/ie.scss index ff5608deaf..a22d3935fd 100644 --- a/lms/static/sass/ie.scss +++ b/lms/static/sass/ie.scss @@ -130,10 +130,6 @@ header.global { background: #000; } -.modal .inner-wrapper form label { - display: block; -} - nav.course-material ol.course-tabs li a.active, nav.course-material .xmodule_SequenceModule nav.sequence-nav ol.course-tabs li a.seq_video.active, .xmodule_SequenceModule nav.sequence-nav nav.course-material ol.course-tabs li a.seq_video.active { background-color: #333; background-color: rgba(0, 0, 0, .4); diff --git a/lms/static/sass/shared/_modal.scss b/lms/static/sass/shared/_modal.scss index 409c6088ec..b7ea762ce8 100644 --- a/lms/static/sass/shared/_modal.scss +++ b/lms/static/sass/shared/_modal.scss @@ -147,10 +147,9 @@ } label { - display: none; + color: #999; &.field-error { - display: block; color: #8F0E0E; + input { @@ -164,6 +163,14 @@ margin-right: 5px; } + ::-webkit-input-placeholder { + color: #ddd; + } + + :-moz-placeholder { + color: #ddd; + } + textarea { background: rgb(255,255,255); display: block; diff --git a/lms/templates/login_modal.html b/lms/templates/login_modal.html index 8652f457e6..d7d327178c 100644 --- a/lms/templates/login_modal.html +++ b/lms/templates/login_modal.html @@ -10,9 +10,9 @@
From bce61cd6e50c2fe119e1270b6e8841d78a6adaab Mon Sep 17 00:00:00 2001 From: Kyle Fiedler Date: Fri, 7 Sep 2012 14:18:34 -0400 Subject: [PATCH 26/35] Revert "Merge pull request #642 from MITx/bug/kfiedler/ie" This reverts commit 2b5e8a49441bb75f356420fbd814401934990c01, reversing changes made to db0549be78679562dbab5a04401e450a1d24b619. --- lms/static/sass/course/base/_base.scss | 6 +++++- .../sass/course/layout/_courseware_header.scss | 1 + lms/static/sass/course/wiki/_wiki.scss | 2 +- lms/static/sass/ie.scss | 11 ++--------- lms/static/sass/shared/_footer.scss | 1 - lms/static/sass/shared/_modal.scss | 11 ++--------- lms/templates/login_modal.html | 4 ++-- lms/templates/main.html | 6 +++--- lms/templates/main_django.html | 8 +------- lms/templates/signup_modal.html | 14 +++++++------- 10 files changed, 24 insertions(+), 40 deletions(-) diff --git a/lms/static/sass/course/base/_base.scss b/lms/static/sass/course/base/_base.scss index 902c8025fd..1edb9aa7ba 100644 --- a/lms/static/sass/course/base/_base.scss +++ b/lms/static/sass/course/base/_base.scss @@ -2,7 +2,6 @@ body { min-width: 980px; min-height: 100%; background: url(../images/bg-texture.png) #d6d6d6; - font-style: normal; } body, h1, h2, h3, h4, h5, h6, p, p a:link, p a:visited, a, label { @@ -40,6 +39,11 @@ a { } } + + + + + form { label { display: block; diff --git a/lms/static/sass/course/layout/_courseware_header.scss b/lms/static/sass/course/layout/_courseware_header.scss index 103139b330..86b5fe58c5 100644 --- a/lms/static/sass/course/layout/_courseware_header.scss +++ b/lms/static/sass/course/layout/_courseware_header.scss @@ -40,6 +40,7 @@ nav.course-material { } &.active { + // background: rgba(0, 0, 0, .2); @include linear-gradient(top, rgba(0, 0, 0, .4), rgba(0, 0, 0, .25)); background-color: transparent; @include box-shadow(0 1px 0 rgba(255, 255, 255, .5), 0 1px 1px rgba(0, 0, 0, .3) inset); diff --git a/lms/static/sass/course/wiki/_wiki.scss b/lms/static/sass/course/wiki/_wiki.scss index 84260e2a86..1bc38abd9a 100644 --- a/lms/static/sass/course/wiki/_wiki.scss +++ b/lms/static/sass/course/wiki/_wiki.scss @@ -397,7 +397,7 @@ section.wiki { #hint_id_content { position: absolute; - top: 4px; + top: 10px; right: 0%; font-size: 12px; text-align:right; diff --git a/lms/static/sass/ie.scss b/lms/static/sass/ie.scss index a22d3935fd..35997d95a7 100644 --- a/lms/static/sass/ie.scss +++ b/lms/static/sass/ie.scss @@ -130,13 +130,6 @@ header.global { background: #000; } -nav.course-material ol.course-tabs li a.active, nav.course-material .xmodule_SequenceModule nav.sequence-nav ol.course-tabs li a.seq_video.active, .xmodule_SequenceModule nav.sequence-nav nav.course-material ol.course-tabs li a.seq_video.active { - background-color: #333; - background-color: rgba(0, 0, 0, .4); +.modal .inner-wrapper form label { + display: block; } - -header.global ol.user > li.primary a.dropdown { - padding-top: 6px; - padding-bottom: 6px; -} - diff --git a/lms/static/sass/shared/_footer.scss b/lms/static/sass/shared/_footer.scss index a418b887ad..d182ed4316 100644 --- a/lms/static/sass/shared/_footer.scss +++ b/lms/static/sass/shared/_footer.scss @@ -110,7 +110,6 @@ footer { padding: 0; a { - @include inline-block; opacity: 0.3; @include transition(all, 0.1s, linear); diff --git a/lms/static/sass/shared/_modal.scss b/lms/static/sass/shared/_modal.scss index b7ea762ce8..409c6088ec 100644 --- a/lms/static/sass/shared/_modal.scss +++ b/lms/static/sass/shared/_modal.scss @@ -147,9 +147,10 @@ } label { - color: #999; + display: none; &.field-error { + display: block; color: #8F0E0E; + input { @@ -163,14 +164,6 @@ margin-right: 5px; } - ::-webkit-input-placeholder { - color: #ddd; - } - - :-moz-placeholder { - color: #ddd; - } - textarea { background: rgb(255,255,255); display: block; diff --git a/lms/templates/login_modal.html b/lms/templates/login_modal.html index d7d327178c..8652f457e6 100644 --- a/lms/templates/login_modal.html +++ b/lms/templates/login_modal.html @@ -10,9 +10,9 @@ - + - +
From 8500a1b300bda258026fa09b6a3e37b760580d9d Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 7 Sep 2012 14:38:53 -0400 Subject: [PATCH 27/35] Make tests not use syslog when they're running --- lms/envs/test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/lms/envs/test.py b/lms/envs/test.py index b55cf2af5f..34108256e9 100644 --- a/lms/envs/test.py +++ b/lms/envs/test.py @@ -46,6 +46,7 @@ DATA_DIR = COURSES_ROOT LOGGING = get_logger_config(TEST_ROOT / "log", logging_env="dev", tracking_filename="tracking.log", + dev_env=True, debug=True) COMMON_TEST_DATA_ROOT = COMMON_ROOT / "test" / "data" From bbd57ef79b6fbbd8b923c9367c84fe1d18b9ad52 Mon Sep 17 00:00:00 2001 From: Victor Shnayder Date: Fri, 7 Sep 2012 15:10:26 -0400 Subject: [PATCH 28/35] don't show empty list if there is no error data --- .../courseware/instructor_dashboard.html | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/lms/templates/courseware/instructor_dashboard.html b/lms/templates/courseware/instructor_dashboard.html index 2d9ab853eb..8568490e5e 100644 --- a/lms/templates/courseware/instructor_dashboard.html +++ b/lms/templates/courseware/instructor_dashboard.html @@ -64,17 +64,17 @@ table.stat_table td { %if instructor_access:

- +

- - + +


%endif - + %if settings.MITX_FEATURES['ENABLE_MANUAL_GIT_RELOAD'] and admin_access:

- - + + %endif @@ -101,7 +101,7 @@ table.stat_table td {

%if msg: -

${msg}

+

${msg}

%endif % if course_errors is not UNDEFINED: @@ -110,13 +110,17 @@ table.stat_table td {
    % for (summary, err) in course_errors:
  • ${summary | h} + % if err:
    • ${err | h}
    + % else: +

     

    + % endif
  • % endfor
% endif - +
From 9ad9359d48d77cc929984d11ad3df0bfd567700f Mon Sep 17 00:00:00 2001 From: Lyla Fischer Date: Fri, 7 Sep 2012 15:57:43 -0400 Subject: [PATCH 29/35] quick wording change --- lms/templates/courseware/welcome-back.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lms/templates/courseware/welcome-back.html b/lms/templates/courseware/welcome-back.html index a817d303a9..5d4e0fe1e3 100644 --- a/lms/templates/courseware/welcome-back.html +++ b/lms/templates/courseware/welcome-back.html @@ -1,3 +1,3 @@

${chapter_module.display_name}

-

You were last in ${prev_section.display_name}. If you're done with that, choose another section on the left.

\ No newline at end of file +

You were most recently in ${prev_section.display_name}. If you're done with that, choose another section on the left.

From b85fefe61a146b6f39f7d99f2b620bd8bf5cdd74 Mon Sep 17 00:00:00 2001 From: Calen Pennington Date: Fri, 7 Sep 2012 17:02:00 -0400 Subject: [PATCH 30/35] Fixing tests that were failing to due static content directory change --- .../html/sound_labs/mosfet_amplifier.html | 124 -- .../data/full/html/sound_labs/rc_filters.html | 111 -- .../data/full/html/sound_labs/series_rlc.html | 111 -- .../full/{ => static}/circuits/120V60Hz.gif | Bin .../full/{ => static}/circuits/Lab1_1.png | Bin .../full/{ => static}/circuits/duality.gif | Bin .../{ => static}/circuits/heaters-bad.gif | Bin .../circuits/heaters-parallel.gif | Bin .../handouts/schematic_tutorial.pdf | Bin .../test/data/full/{ => static}/js/cktsim.js | 0 .../data/full/{ => static}/js/schematic.js | 0 .../data/full/static/js/sound_labs/circuit.js | 1247 ----------------- .../static/js/sound_labs/mosfet_amplifier.js | 658 --------- .../data/full/static/js/sound_labs/plotter.js | 1038 -------------- .../full/static/js/sound_labs/rc_filters.js | 938 ------------- .../full/static/js/sound_labs/series_rlc.js | 1150 --------------- .../data/full/static/js/sound_labs/sound.js | 407 ------ 17 files changed, 5784 deletions(-) delete mode 100644 common/test/data/full/html/sound_labs/mosfet_amplifier.html delete mode 100644 common/test/data/full/html/sound_labs/rc_filters.html delete mode 100644 common/test/data/full/html/sound_labs/series_rlc.html rename common/test/data/full/{ => static}/circuits/120V60Hz.gif (100%) rename common/test/data/full/{ => static}/circuits/Lab1_1.png (100%) rename common/test/data/full/{ => static}/circuits/duality.gif (100%) rename common/test/data/full/{ => static}/circuits/heaters-bad.gif (100%) rename common/test/data/full/{ => static}/circuits/heaters-parallel.gif (100%) rename common/test/data/full/{ => static}/handouts/schematic_tutorial.pdf (100%) rename common/test/data/full/{ => static}/js/cktsim.js (100%) rename common/test/data/full/{ => static}/js/schematic.js (100%) delete mode 100644 common/test/data/full/static/js/sound_labs/circuit.js delete mode 100644 common/test/data/full/static/js/sound_labs/mosfet_amplifier.js delete mode 100644 common/test/data/full/static/js/sound_labs/plotter.js delete mode 100644 common/test/data/full/static/js/sound_labs/rc_filters.js delete mode 100644 common/test/data/full/static/js/sound_labs/series_rlc.js delete mode 100644 common/test/data/full/static/js/sound_labs/sound.js diff --git a/common/test/data/full/html/sound_labs/mosfet_amplifier.html b/common/test/data/full/html/sound_labs/mosfet_amplifier.html deleted file mode 100644 index c7f2f1ad81..0000000000 --- a/common/test/data/full/html/sound_labs/mosfet_amplifier.html +++ /dev/null @@ -1,124 +0,0 @@ - - - - - -

LAB 5B: MOSFET AMPLIFIER EXPERIMENT

-
- -

Note: This part of the lab is just to develop your intuition about -amplifiers and biasing, and to have fun with music! There are no responses -that need to be checked.

-

The graph plots the selected voltages from the amplifier circuit below. You -can also listen to various signals by selecting from the radio buttons to -the right of the graph. This way you can both see and hear various signals. -You can use the sliders to the right of the amplifier circuit to control -various parameters of the MOSFET and the amplifier. The parameter \(V_{MAX}\) -sets the maximum range on the plots. You can also select an input voltage -type (e.g., sine wave, square wave, various types of music) using the drop -down menu to the right of the graph. When describing AC signals, the -voltages on the sliders refer to peak-to-peak values.

-

1. To begin your first experiment, go ahead and use the pull down menu to -select a sine wave input. Then, adjust the sliders to an approximate -baseline setting shown below.

-

Baseline setting of sliders: -
-\(V_{S}=1.6V\), \(v_{IN}=3V\), \(Frequency=1000Hz\), \(V_{BIAS}=2.5V\), \(R=10K\Omega\), \(k=1mA/V^{2}\), \(V_{T}=1V\), \(V_{MAX}=2V\).

-

You will observe in the plot that the baseline setting of the sliders for -the various amplifiers parameters produces a distorted sine wave signal for -\(v_{OUT}\). Next, go ahead and select one of the music signals as the input and -listen to each of \(v_{IN}\) and \(v_{OUT}\), and confirm for yourself that the -output sounds distorted for the chosen slider settings. You will notice -that the graph now plots the music signal waveforms. Think about all the -reasons why the amplifier is producing a distorted output.

-

2. For the second experiment, we will study the amplifier's small signal -behavior. Select a sine wave as the input signal. To study the small -signal behavior, reduce the value of \(v_{IN}\) to 0.1V (peak-to-peak) by -using the \(v_{IN}\) slider. Keeping the rest of the parameters at their -baseline settings, derive an appropriate value of \(V_{BIAS}\) that will ensure -saturation region operation for the MOSFET for the 0.1V peak-to-peak swing -for \(v_{IN}\). Make sure to think about both positive and negative excursions -of the signals.

-

Next, use the \(V_{BIAS}\) slider to choose your computed value for \(V_{BIAS}\) and -see if the observed plot of \(v_{OUT}\) is more or less distortion free. If -your calculation was right, then the output will indeed be distortion free.

-

Next, select one of the music signals as the input and listen to each of -\(v_{IN}\) and \(v_{OUT}\), and confirm for yourself that the output sounds much -better than in Step 1. Also, based on sound volume, confirm that \(v_{OUT}\) is -an amplified version of \(v_{IN}\).

-

3. Now go ahead and experiment with various other settings while listening -to the music signal at \(v_{OUT}\). Observe the plots and listen to \(v_{OUT}\) as -you change, for example, the bias voltage \(V_{BIAS}\). You will notice that -the amplifier distorts the input signal when \(V_{BIAS}\) becomes too small, or -when it becomes too large. You can also experiment with various values of -\(v_{IN}\), \(R_{L}\), etc., and see how they affect the amplification and distortion.

- -
- -
-
- -
-
- - - -
- -
-
-

Graph:

-
    -
  • -
  • -
  • -
-
- -
-

Listen to:

-
    -
  • -
  • -
  • -
-
-
- -
- -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Your browser must support the Canvas element and have JavaScript enabled to view this tool. - Your browser must support the Canvas element and have JavaScript enabled to view this tool. -
- -
diff --git a/common/test/data/full/html/sound_labs/rc_filters.html b/common/test/data/full/html/sound_labs/rc_filters.html deleted file mode 100644 index c52f8022a4..0000000000 --- a/common/test/data/full/html/sound_labs/rc_filters.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - -

LAB 10B: RC FILTERS WITH FREQUENCY RESPONSE EXPERIMENT

-
- -

Note: Use this part of the lab to build your intuition about filters and frequency response, and to have fun with music! There are no responses that need to be checked.

-

Recall from the audio lab in Week 5 that the graph plots the selected voltages from the circuit shown below. This week the circuit is an RC filter. You can also listen to various signals by selecting from the radio buttons to the right of the graph. This way you can both see and hear various signals. You can use the sliders to the right of the circuit to control various circuit and input signal parameters. (Note that you can get finer control of some of the slider values by clicking on the slider and using the arrow keys). Recall that the parameter \(V_{MAX}\) sets the maximum range on the graph. You can also select an input voltage type (e.g., sine wave, square wave, various types of music) using the drop down menu to the right of the graph. When describing AC signals, the voltages on the sliders refer to peak-to-peak values.

-

1. To begin your first experiment, use the pull down menu to select a sine wave input. Then, adjust the sliders to these approximate baseline settings: -
-\(v_{IN} = 3V\), \(Frequency = 1000 Hz\), \(V_{BIAS} = 0V\), \(R = 1K\Omega\), \(v_C(0) = 0V\), \(C = 110nF\), \(V_{MAX} = 2V\). -
-Observe the waveforms for \(v_{IN}\) and \(v_C\) in the graph. You can also listen to \(v_{IN}\) and \(v_C\). You will observe that the amplitude of \(v_C\) is slightly smaller than the amplitude of \(v_{IN}\). -
-Compute the break frequency of the filter circuit for the given circuit parameters. (Note that the break frequency is also called the cutoff frequency or the corner frequency). -
-Change the frequency of the sinusoid so that it is approximately 3 times the break frequency. -
-Observe the waveforms for \(v_{IN}\) and \(v_C\) in the graph. Also listen to \(v_{IN}\) and \(v_C\). Think about why the sinusoid at \(v_C\) is significantly more attenuated than the original 1KHz sinusoid. -
-Keeping the input signal unchanged, observe the waveforms for \(v_{IN}\) and \(v_R\) in the graph. Also listen to \(v_{IN}\) and \(v_R\). Think about why the sinusoid at \(v_R\) is significantly louder than the sinusoid at \(v_C\).

-

2. Next, use the pull down menu to select a music signal of your choice. Adjust the sliders to the approximate baseline settings: -
-\(v_{IN} = 3V\), \(V_{BIAS} = 0V\), \(R = 1K\Omega\), \(v_C(0) = 0V\), \(C = 110nF\), \(V_{MAX} = 2V\). -
-Listen to the signals at \(v_{IN}\) and \(v_C\). Notice any difference between the signals? -
-Next, increase the capacitance value and observe the difference in the sound of \(v_{IN}\) and \(v_C\) as the capacitance increases. You should notice that the higher frequency components of \(v_C\) are attenuated as the capacitance is increased. -Convince yourself that when the signal is taken at \(v_C\), the circuit behaves like a low-pass filter.

-

3. Re-adjust the sliders to the approximate baseline settings: -
-\(v_{IN} = 3V\), \(V_{BIAS} = 0V\), \(R = 1K\Omega\), \(v_C(0) = 0V\), \(C = 110nF\), \(V_{MAX} = 2V\). -
-Try to create a high-pass filter from the same circuit by taking the signal output across a different element and possibly changing some of the element values. -

- -
- -
-
-
-
- - -
- -
-
-

Graph:

-
    -
  • -
  • -
  • -
-
- -
-

Listen to:

-
    -
  • -
  • -
  • -
-
-
-
-
-
fC =
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - Your browser must support the Canvas element and have JavaScript enabled to view this tool. - Your browser must support the Canvas element and have JavaScript enabled to view this tool. - Your browser must support the Canvas element and have JavaScript enabled to view this tool. -
- - Your browser must support the Canvas element and have JavaScript enabled to view this tool. -
- -
diff --git a/common/test/data/full/html/sound_labs/series_rlc.html b/common/test/data/full/html/sound_labs/series_rlc.html deleted file mode 100644 index 4856d5c5ed..0000000000 --- a/common/test/data/full/html/sound_labs/series_rlc.html +++ /dev/null @@ -1,111 +0,0 @@ - - - - -

SERIES RLC CIRCUIT WITH FREQUENCY RESPONSE EXPERIMENT

-
- -

\(I(s) = \frac{1}{R + Ls + 1/Cs}V_{in}(s) = \frac{s/L}{s^2 + sR/L + 1/LC}V_{in}(s)\)

-

\(I(s) = \frac{s/L}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s)\)

-

\(\omega_0 = \frac{1}{\sqrt{LC}} , \alpha = \frac{R}{2L}\)

-

Band-Pass Filter:

-

\(V_r(s) = RI(s) = \frac{sR/L}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{2\alpha s}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{2\alpha s}{(s-s_1)(s-s_2)}V_{in}(s)\)

-

Gain magnitude: \(G_R = \frac{2\alpha w}{|j\omega - s_1||j\omega - s_2|}\)

-

Phase: \(\Phi_R = \pi/2-\Phi(j\omega - s_1) -\Phi(j\omega - s_2)\)

-

Low-Pass Filter:

-

\(V_c(s) = I(s)/sC = \frac{1/LC}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{\omega_0^2}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{\omega_0^2}{(s-s_1)(s-s_2)}V_{in}(s)\)

-

Gain magnitude: \(G_C = \frac{\omega_0^2}{|j\omega - s_1||j\omega - s_2|}\)

-

Phase: \(\Phi_C = -\Phi(j\omega - s_1) -\Phi(j\omega - s_2)\)

-

High-Pass Filter:

-

\(V_l(s) = sLI(s) = \frac{s^2}{s^2 + 2\alpha s + \omega_0^2}V_{in}(s) = \frac{s^2}{(s-s_1)(s-s_2)}V_{in}(s)\)

-

Gain magnitude: \(G_L = \frac{\omega^2}{|j\omega - s_1||j\omega - s_2|}\)

-

Phase: \(\Phi_L = -\Phi(j\omega - s_1) -\Phi(j\omega - s_2)\)

-
-

Under-Damped: \(\alpha < \omega_0\)

-

Complex roots: \(s_{1,2} = -\alpha \pm j\sqrt{\omega_0^2 - \alpha^2}\)

-

Critically-Damped: \(\alpha = \omega_0\)

-

Double real root: \(s_{1,2} = -\alpha\)

-

Over-Damped: \(\alpha > \omega_0\)

-

Real roots: \(s_{1,2} = -\alpha \pm\sqrt{\alpha^2 - \omega_0^2}\)

- -
- -
-
- -
-
- - -
- -
-
-

Graph:

-
    -
  • -
  • -
  • -
  • -
-
- -
-

Listen to:

-
    -
  • -
  • -
  • -
  • -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - Your browser must support the Canvas element and have JavaScript enabled to view this tool. - Your browser must support the Canvas element and have JavaScript enabled to view this tool. - Your browser must support the Canvas element and have JavaScript enabled to view this tool. -
- - Your browser must support the Canvas element and have JavaScript enabled to view this tool. - -
- -
diff --git a/common/test/data/full/circuits/120V60Hz.gif b/common/test/data/full/static/circuits/120V60Hz.gif similarity index 100% rename from common/test/data/full/circuits/120V60Hz.gif rename to common/test/data/full/static/circuits/120V60Hz.gif diff --git a/common/test/data/full/circuits/Lab1_1.png b/common/test/data/full/static/circuits/Lab1_1.png similarity index 100% rename from common/test/data/full/circuits/Lab1_1.png rename to common/test/data/full/static/circuits/Lab1_1.png diff --git a/common/test/data/full/circuits/duality.gif b/common/test/data/full/static/circuits/duality.gif similarity index 100% rename from common/test/data/full/circuits/duality.gif rename to common/test/data/full/static/circuits/duality.gif diff --git a/common/test/data/full/circuits/heaters-bad.gif b/common/test/data/full/static/circuits/heaters-bad.gif similarity index 100% rename from common/test/data/full/circuits/heaters-bad.gif rename to common/test/data/full/static/circuits/heaters-bad.gif diff --git a/common/test/data/full/circuits/heaters-parallel.gif b/common/test/data/full/static/circuits/heaters-parallel.gif similarity index 100% rename from common/test/data/full/circuits/heaters-parallel.gif rename to common/test/data/full/static/circuits/heaters-parallel.gif diff --git a/common/test/data/full/handouts/schematic_tutorial.pdf b/common/test/data/full/static/handouts/schematic_tutorial.pdf similarity index 100% rename from common/test/data/full/handouts/schematic_tutorial.pdf rename to common/test/data/full/static/handouts/schematic_tutorial.pdf diff --git a/common/test/data/full/js/cktsim.js b/common/test/data/full/static/js/cktsim.js similarity index 100% rename from common/test/data/full/js/cktsim.js rename to common/test/data/full/static/js/cktsim.js diff --git a/common/test/data/full/js/schematic.js b/common/test/data/full/static/js/schematic.js similarity index 100% rename from common/test/data/full/js/schematic.js rename to common/test/data/full/static/js/schematic.js diff --git a/common/test/data/full/static/js/sound_labs/circuit.js b/common/test/data/full/static/js/sound_labs/circuit.js deleted file mode 100644 index dfd756a4a2..0000000000 --- a/common/test/data/full/static/js/sound_labs/circuit.js +++ /dev/null @@ -1,1247 +0,0 @@ -var Circuit = (function() { - - var Color = - { - background : "rgb(0, 51, 102)", //0.0, 0.2, 0.4 - black : "rgb(0, 0, 0)", //0.0 - lodarkgray : "rgb(26, 26, 26)", //0.1 = 25.5 - darkgray : "rgb(51, 51, 51)", //0.2 - lomidgray : "rgb(102, 102, 102)", //0.4 - midgray : "rgb(128, 128, 128)", //0.5 = 127.5 - himidgray : "rgb(153, 153, 153)", //0.6 - litegray : "rgb(204, 204, 204)", //0.8 - white : "rgb(255, 255, 255)", //1.0 - - red : "rgb(255, 0, 0)", - green : "rgb(0, 255, 0)", - blue : "rgb(0, 0, 255)", - yellow : "rgb(255, 255, 0)", - cyan : "rgb(0, 255, 255)", - magenta : "rgb(255, 0, 255)" - }; - - var Utils = - { - TWO_PI: 2.0*Math.PI, - PI_DIV_2: Math.PI/2.0 - }; - - function distance(x1, y1, x2, y2) - { - var dx = x2 - x1; - var dy = y2 - y1; - - return Math.sqrt(dx * dx + dy * dy); - } - - function transform(x, y, xt, yt, rot) - { - //First translate - x -= xt; - y -= yt; - //Then rotate - return {x: x * Math.cos(rot) - y * Math.sin(rot), y: x * Math.sin(rot) + y * Math.cos(rot)}; - } - - function closestGridPoint(gridStep, x) - { - return gridStep * Math.round(x / gridStep); - } - - function getMousePosition(diagram, event) - { - var mouseX = event.pageX - (parseInt(diagram.element.offset().left) + parseInt(diagram.element.css('paddingLeft')) + parseInt(diagram.element.css('borderLeftWidth'))); - var mouseY = event.pageY - (parseInt(diagram.element.offset().top) + parseInt(diagram.element.css('paddingTop')) + parseInt(diagram.element.css('borderTopWidth'))); - return {x : mouseX, y : mouseY}; - } - - function diagramMouseDown(event) - { - if (!event) event = window.event; - else event.preventDefault(); - var canvas = (window.event) ? event.srcElement : event.target; - var diagram = canvas.diagram; - var mpos = getMousePosition(diagram, event); - - for(var i = 0, len = diagram.components.length; i < len; i++) - { - if(diagram.components[i].isInside(mpos.x, mpos.y)) - { - diagram.components[i].selected = true; - diagram.startx = closestGridPoint(diagram.gridStep, mpos.x); - diagram.starty = closestGridPoint(diagram.gridStep, mpos.y); - } - } - - return false; - } - - function diagramMouseMove(event) - { - if (!event) event = window.event; - else event.preventDefault(); - var canvas = (window.event) ? event.srcElement : event.target; - var diagram = canvas.diagram; - var mpos = getMousePosition(diagram, event); - var componentSelected = false; - - //First check if any component if selected - for(var i = 0, len = diagram.components.length; i < len; i++) - { - if(diagram.components[i].selected) - { - diagram.endx = closestGridPoint(diagram.gridStep, mpos.x); - diagram.components[i].x += (diagram.endx - diagram.startx); - diagram.startx = diagram.endx; - diagram.endy = closestGridPoint(diagram.gridStep, mpos.y); - diagram.components[i].y += (diagram.endy - diagram.starty); - diagram.starty = diagram.endy; - diagram.paint(); - componentSelected = true; - } - } - - if(!componentSelected) - { - for(var i = 0, len = diagram.components.length; i < len; i++) - { - if(diagram.components[i].isInside(mpos.x, mpos.y)) - diagram.components[i].selectable = true; - else - diagram.components[i].selectable = false; - //Repaint only once, on a mouse enter or mouse leave - if(diagram.components[i].previousSelectable != diagram.components[i].selectable) - { - diagram.components[i].previousSelectable = diagram.components[i].selectable; - diagram.paint(); - } - } - } - - return false; - } - - function diagramMouseUp(event) - { - if (!event) event = window.event; - else event.preventDefault(); - var canvas = (window.event) ? event.srcElement : event.target; - var diagram = canvas.diagram; - var mpos = getMousePosition(diagram, event); - - for(var i = 0, len = diagram.components.length; i < len; i++) - { - //Unselect all - diagram.components[i].selected = false; - } - diagram.startx = 0; - diagram.endx = diagram.startx; - diagram.starty = 0; - diagram.endx = diagram.starty; - - return false; - } - - function diagramDoubleClick(event) - { - if (!event) event = window.event; - else event.preventDefault(); - var canvas = (window.event) ? event.srcElement : event.target; - var diagram = canvas.diagram; - - alert(diagram.toString()); - - return false; - } - - function copyPrototype(descendant, parent) - { - var sConstructor = parent.toString(); - var aMatch = sConstructor.match(/\s*function (.*)\(/); - if(aMatch != null) - { - descendant.prototype[aMatch[1]] = parent; - } - for(var m in parent.prototype) - { - descendant.prototype[m] = parent.prototype[m]; - } - } - - function Diagram(element, frozen) - { - this.element = element; - this.frozen = frozen; - this.canvas = element[0]; - this.canvas.diagram = this; - this.width = this.canvas.width; - this.height = this.canvas.height; - this.ctx = this.canvas.getContext("2d"); - this.background = Color.black; - if (!this.frozen) - { - this.canvas.addEventListener('mousedown', diagramMouseDown, false); - this.canvas.addEventListener('mousemove', diagramMouseMove, false); - this.canvas.addEventListener('mouseup', diagramMouseUp, false); - this.canvas.addEventListener('dblclick', diagramDoubleClick, false); - } - //To disable text selection outside the canvas - this.canvas.onselectstart = function(){return false;}; - this.components = []; - this.gridStep = 5; - this.startx = 0; - this.endx = 0; - this.starty = 0; - this.endy = 0; - this.showGrid = false; - this.xGridMin = 10; - this.xGridMax = 500; - this.yGridMin = 10; - this.yGridMax = 500; - this.xOrigin = 0; - this.yOrigin = 0; - this.scale = 2; //Scaling is the same in x and y directions - this.fontSize = 6; - this.fontType = 'sans-serif'; - } - - Diagram.prototype.toString = function() - { - var result = ""; - for(var i = 0, len = this.components.length; i < len; i++) - { - result += this.components[i].toString(); - } - - return result; - } - - Diagram.prototype.addNode = function(x, y) - { - var n = new Node(x, y); - n.ctx = this.ctx; - n.diagram = this; - n.updateBoundingBox(); - this.components.push(n); - return n; - } - - Diagram.prototype.addWire = function(x1, y1, x2, y2) - { - var w = new Wire(x1, y1, x2, y2) - w.ctx = this.ctx; - w.diagram = this; - w.updateBoundingBox(); - this.components.push(w); - return w; - } - - Diagram.prototype.addLabel = function(x, y, value, textAlign) - { - var l = new Label(x, y, value, textAlign) - l.ctx = this.ctx; - l.diagram = this; - l.updateBoundingBox(); - this.components.push(l); - return l; - } - - Diagram.prototype.addResistor = function(x, y, value) - { - var r = new Resistor(x, y, value) - r.ctx = this.ctx; - r.diagram = this; - r.updateBoundingBox(); - this.components.push(r); - return r; - } - - Diagram.prototype.addInductor = function(x, y, value) - { - var l = new Inductor(x, y, value) - l.ctx = this.ctx; - l.diagram = this; - l.updateBoundingBox(); - this.components.push(l); - return l; - } - - Diagram.prototype.addCapacitor = function(x, y, value) - { - var c = new Capacitor(x, y, value) - c.ctx = this.ctx; - c.diagram = this; - c.updateBoundingBox(); - this.components.push(c); - return c; - } - - Diagram.prototype.addMosfet = function(x, y, value, type) - { - var m = new Mosfet(x, y, value, type) - m.ctx = this.ctx; - m.diagram = this; - m.updateBoundingBox(); - this.components.push(m); - return m; - } - - Diagram.prototype.addGround = function(x, y) - { - var g = new Ground(x, y) - g.ctx = this.ctx; - g.diagram = this; - g.updateBoundingBox(); - this.components.push(g); - return g; - } - - Diagram.prototype.addDiode = function(x, y, value) - { - var d = new Diode(x, y, value) - d.ctx = this.ctx; - d.diagram = this; - d.updateBoundingBox(); - this.components.push(d); - return d; - } - - Diagram.prototype.addSource = function(x, y, value, type) - { - var v = new Source(x, y, value, type) - v.ctx = this.ctx; - v.diagram = this; - v.updateBoundingBox(); - this.components.push(v); - return v; - } - - Diagram.prototype.paint = function() - { - this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height); - if (this.showGrid) - this.drawGrid(); - - for(var i = 0, len = this.components.length; i < len; i++) - { - this.components[i].paint(); - } - } - - Diagram.prototype.drawGrid = function() - { - this.ctx.fillStyle = Color.black; - for(x = this.xGridMin; x <= this.xGridMax; x += this.gridStep) - { - for( y = this.yGridMin; y <= this.yGridMax; y += this.gridStep) - { - this.drawPixel(this.ctx, x, y); - } - } - } - //Drawing routines from schematic - Diagram.prototype.drawLine = function(c, x1, y1, x2, y2) - { - c.beginPath(); - c.moveTo((x1 - this.xOrigin) * this.scale, (y1 - this.yOrigin) * this.scale); - c.lineTo((x2 - this.xOrigin) * this.scale, (y2 - this.yOrigin) * this.scale); - c.stroke(); - } - - Diagram.prototype.drawArc = function(c, x, y, radius,startRadians, endRadians, anticlockwise, width, filled) - { - c.lineWidth = width; - c.beginPath(); - c.arc((x - this.xOrigin)*this.scale, (y - this.yOrigin)*this.scale, radius*this.scale, startRadians, endRadians, anticlockwise); - if (filled) c.fill(); - else c.stroke(); - } - - Diagram.prototype.drawCircle = function(c, x, y, radius, filled) - { - this.drawArc(c, x, y, radius, 0, 2*Math.PI, false, 1, filled); - } - - Diagram.prototype.drawText = function(c, str, x, y) - { - c.font = this.scale*this.fontSize + "pt " + this.fontType; - c.fillText(str, (x - this.xOrigin) * this.scale, (y - this.yOrigin) * this.scale); - } - //End drawing routines - - Diagram.prototype.parseSubSuperScriptText = function(str) - { - /*var regExpSub = /_\{(.*?)\}/g; - var regExpSup = /\^\{(.*?)\}/g; - var subs = []; - var sups = []; - var text = []; - var finalText = []; - var isSub = false; - var isSup = false; - - subs = str.match(regExpSub); - for (var i = 0; i < subs.length; i++) - { - subs[i] = subs[i].substring(2, subs[i].length - 1); //Discard _{ and } - } - - sups = str.match(regExpSup); - for (var i = 0; i < sups.length; i++) - { - sups[i] = sups[i].substring(2, sups[i].length - 1); //Discard ^{ and } - }*/ - - var len = str.length; - var i = 0; - var start; - var end; - found = false; - var text = []; - var type; - var ntext = ""; - - while (i < len) - { - if (str[i] == "_") //Encountered a potential subscript _ - type = "sub"; - else if (str[i] == "^") //Encountered a potential superscript ^ - type = "sup"; - - if (type == "sub" || type == "sup") - { - if (str[i+1] == "{") - { - i += 2; //Discard _{ or ^{ - start = i; - found = false; - while (i < len) //Look for } - { - if (str[i] == "}") - { - found = true; - end = i; - break; - } - i++; - } - if (found && end > start) //Discard empty subscript ie _{} - { - //Store previous normal text if not empty and tag it as so - if (ntext.length != 0) - { - text.push({s: ntext, type: "normal"}); - ntext = ""; - } - //Store subscript or superscript and tag it as so - if (type == "sub") - text.push({s: str.substring(start, end), type: "sub"}); - else if (type == "sup") - text.push({s: str.substring(start, end), type: "sup"}); - i = end + 1; - } - else - i = start - 2; //Nothing was found, backtrack to _ or ^ - } - } - ntext += str[i]; - if (i == len - 1 && ntext.length != 0) //We've reached the end, store normal text if not empty and tag it as so - text.push({s: ntext, type: "normal"}); - i++; - } - - return text; - } - - Diagram.prototype.subSuperScriptLength = function(c, text) - { - var fontNormal = this.scale*this.fontSize + "pt " + this.fontType; - var fontSubSup = this.scale*(this.fontSize-2) + "pt " + this.fontType; - - var xpos = 0; - - for (var i = 0; i < text.length; i++) - { - if (text[i].type == "normal") - c.font = fontNormal; - else if (text[i].type == "sub") - c.font = fontSubSup; - else - c.font = fontSubSup; - xpos += c.measureText(text[i].s).width; - } - - return xpos; - } - - Diagram.prototype.drawSubSuperScript = function(c, str, x, y, way) - { - var fontNormal = this.scale*this.fontSize + "pt " + this.fontType; - var fontSubSup = this.scale*(this.fontSize-2) + "pt " + this.fontType; - - var text = this.parseSubSuperScriptText(str); - var len = this.subSuperScriptLength(c, text); - var xposIni = (x - this.xOrigin) * this.scale; - var yposIni = (y - this.yOrigin) * this.scale; - var xpos, ypos; - - if (way == "left") - xpos = xposIni; - else if (way == "right") - xpos = xposIni - len; - else if (way == "center") - xpos = xposIni - len/2; - - //Draw the text - for (var i = 0; i < text.length; i++) - { - if (text[i].type == "normal") - { - c.font = fontNormal; - ypos = yposIni; - } - else if (text[i].type == "sub") - { - c.font = fontSubSup; - ypos = yposIni + 3; - } - else - { - c.font = fontSubSup; - ypos = yposIni - 5; - } - c.fillText(text[i].s, xpos, ypos); - //Advance x position - xpos += c.measureText(text[i].s).width; - } - } - - //Draws a rectangle, top left corner x1, y1 and bottom right corner x2, y2 - Diagram.prototype.drawCrispLine = function(c, x1, y1, x2, y2) - { - c.beginPath(); - c.moveTo(x1 + 0.5, y1 + 0.5); - c.lineTo(x2 + 0.5, y2 + 0.5); - c.stroke(); - } - - Diagram.prototype.drawRect = function(c, x1, y1, x2, y2) - { - c.strokeRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0); - } - - Diagram.prototype.fillRect = function(c, x1, y1, x2, y2) - { - c.fillRect(x1, y1, x2 - x1 + 1.0, y2 - y1 + 1.0); - } - - Diagram.prototype.clearRect = function(c, x1, y1, x2, y2) - { - c.clearRect(x1 + 0.5, y1 + 0.5, x2 - x1 + 1.0, y2 - y1 + 1.0); - } - - Diagram.prototype.drawPixel = function(c, x, y) - { - c.fillRect(x, y, 1.0, 1.0); - } - - Diagram.prototype.drawPoint = function(c, x, y, radius) - { - c.beginPath(); - c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise - c.fill(); - } - - Diagram.prototype.drawHollowPoint = function(c, x, y, radius) - { - c.beginPath(); - c.arc(x + 0.5, y + 0.5, radius, 0, Utils.TWO_PI, true); //Last param is anticlockwise - c.stroke(); - } - - Diagram.prototype.drawTriangle = function(c, x1, y1, x2, y2, x3, y3) - { - c.beginPath(); - c.moveTo(x1 + 0.5, y1 + 0.5); - c.lineTo(x2 + 0.5, y2 + 0.5); - c.lineTo(x3 + 0.5, y3 + 0.5); - c.closePath(); - c.stroke(); - } - - Diagram.prototype.fillTriangle = function(c, x1, y1, x2, y2, x3, y3) - { - c.beginPath(); - c.moveTo(x1 + 0.5, y1 + 0.5); - c.lineTo(x2 + 0.5, y2 + 0.5); - c.lineTo(x3 + 0.5, y3 + 0.5); - c.closePath(); - c.fill(); - } - - Diagram.prototype.drawHalfCircle = function(c, x, y, radius, concaveDown) //For inductance only - { - c.beginPath(); - if (concaveDown) - c.arc(x + 0.5, y + 0.5, radius, 0, Math.PI, true); //Last param is anticlockwise - else - c.arc(x + 0.5, y + 0.5, radius, Math.PI, 0, true); //Last param is anticlockwise - c.stroke(); - } - - Diagram.prototype.drawDiamond = function(c, x, y, h) - { - var xc = x + 0.5; - var yc = y + 0.5; - - c.beginPath(); - c.moveTo(xc-h, yc); - c.lineTo(xc, yc-h); - c.lineTo(xc+h, yc); - c.lineTo(xc, yc+h); - c.closePath(); - - c.fill(); - } - - Diagram.prototype.drawX = function(c, x, y, h) - { - var xc = x + 0.5; - var yc = y + 0.5; - - c.beginPath(); - c.moveTo(xc+h, yc-h); - c.lineTo(xc-h, yc+h); - c.moveTo(xc-h, yc-h); - c.lineTo(xc+h, yc+h); - c.stroke(); - } - - Diagram.prototype.drawArrow = function(c, x1, y1, x2, y2, base, height) - { - var xs1 = x1 + 0.5; - var ys1 = y1 + 0.5; - var xs2 = x2 + 0.5; - var ys2 = y2 + 0.5; - var xv = x2 - x1; - var yv = y2 - y1; - var ang = Math.atan2(-yv, xv); - - c.beginPath(); - //Arrow line - c.moveTo(xs1, ys1); - c.lineTo(xs2, ys2); - c.stroke(); - //Arrow head, first draw a triangle with top on origin then translate/rotate to orient and fit on line - c.save(); - c.beginPath(); - c.translate(xs2, ys2); - c.rotate(Utils.PI_DIV_2-ang); - - c.moveTo(0, 0); - c.lineTo(-base, height); - c.lineTo(base, height); - c.closePath(); - c.fill(); - //c.stroke(); - c.restore(); - } - - //***** COMPONENT *****// - function Component(x, y, width, height) - { - this.x = x; - this.y = y; - - this.boundingBox = [0, 0, 0, 0]; - this.transBoundingBox = [0, 0, 0, 0]; - this.xMiddle = 0; - this.yMiddle = 0; - - this.previousSelectable = false; - this.selectable = false; - this.selected = false; - this.ctx; - this.diagram; - this.color = Color.white; - this.selectedColor = Color.red; - this.eventListeners = {}; - //Label to the left - this.label = {str: "", x: 0, y: 0, position: "left", show: true, color: Color.white}; //color: Color.lodarkgray - //String representing value to the right - this.valueString = {x: 0, y: 0, position: "right", show: true, suffix: "", decimal: -1, color: Color.white}; //color: Color.lodarkgray - - this.lineWidth = 1; - this.rotation = 0; - this.value = 0; - } - - Component.prototype.addEventListener = function(type, eventListener) - { - if(!(type in this.eventListeners)) - this.eventListeners[type] = eventListener; - } - - Component.prototype.removeEventListener = function(type, eventListener) - { - for(var i in this.eventListeners) - { - if(this.eventListeners[i] === eventListener) - delete this.eventListeners[i].eventListener; - } - } - - Component.prototype.fireEvent = function(event) - { - if( typeof event == "string") - (this.eventListeners[event])(); - else - throw new Error("Event object missing 'type' property."); - } - - Component.prototype.updateBoundingBox = function() - { - //Apply global transform - this.transBoundingBox[0] = (this.boundingBox[0] - this.diagram.xOrigin) * this.diagram.scale; - this.transBoundingBox[1] = (this.boundingBox[1] - this.diagram.yOrigin) * this.diagram.scale; - this.transBoundingBox[2] = (this.boundingBox[2] - this.diagram.xOrigin) * this.diagram.scale; - this.transBoundingBox[3] = (this.boundingBox[3] - this.diagram.yOrigin) * this.diagram.scale; - //this.getMiddle(); - this.label.x = this.transBoundingBox[0]- 5; - this.label.y = (this.transBoundingBox[3] - this.transBoundingBox[1]) / 2; - this.valueString.x = this.transBoundingBox[2] + 5; - this.valueString.y = (this.transBoundingBox[3] - this.transBoundingBox[1]) / 2; - } - - Component.prototype.initPaint = function() - { - if(this.selectable) - { - this.ctx.strokeStyle = this.selectedColor; - this.ctx.fillStyle = this.selectedColor; - } - else - { - this.ctx.strokeStyle = this.color; - this.ctx.fillStyle = this.color; - } - } - - Component.prototype.transform = function() - { - this.ctx.translate(this.x, this.y); - if(this.rotation != 0) - this.ctx.rotate(-this.rotation); - } - - Component.prototype.getMiddle = function() - { - this.xMiddle = (this.boundingBox[2] - this.boundingBox[0]) / 2; - this.yMiddle = (this.boundingBox[3] - this.boundingBox[1]) / 2; - } - - Component.prototype.drawLabel = function() - { - if (this.label.show) - { - var textAlign; - this.ctx.save(); - this.ctx.fillStyle = this.label.color; - this.ctx.textAlign = "left"; - if (this.rotation == 0) //Component is vertical - { - if (this.label.position == "left") //Label is on left - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - else if (this.label.position == "right") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - } - else if (this.rotation == Math.PI/2) //Component is horizontal - { - if (this.label.position == "left") //Label now on bottom - { - this.ctx.textBaseline = "top"; - textAlign = "center"; - } - else if (this.label.position == "right") //Label on top - { - this.ctx.textBaseline = "bottom"; - textAlign = "center"; - } - } - else if (this.rotation == Math.PI) //Component is horizontal - { - if (this.label.position == "left") //Label now on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - else if (this.label.position == "right") //Label now on left - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - } - else if (this.rotation == 2*Math.PI/3) //Component is vertical - { - if (this.label.position == "left") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - else if (this.label.position == "right") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - } - this.ctx.translate(this.label.x, this.label.y); - this.ctx.rotate(this.rotation); - this.diagram.drawSubSuperScript(this.ctx, this.label.str, 0, 0, textAlign); - this.ctx.restore(); - } - } - - Component.prototype.drawValueString = function() - { - if (this.valueString.show) - { - var textAlign; - this.ctx.save(); - this.ctx.fillStyle = this.valueString.color; - this.ctx.textAlign = "left"; - if (this.rotation == 0) //Component is vertical - { - if (this.valueString.position == "left") //Label is on left - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - else if (this.valueString.position == "right") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - } - else if (this.rotation == Math.PI/2) //Component is horizontal - { - if (this.valueString.position == "left") //Label now on bottom - { - this.ctx.textBaseline = "top"; - textAlign = "center"; - } - else if (this.valueString.position == "right") //Label on top - { - this.ctx.textBaseline = "bottom"; - textAlign = "center"; - } - } - else if (this.rotation == Math.PI) //Component is horizontal - { - if (this.valueString.position == "left") //Label now on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - else if (this.valueString.position == "right") //Label now on left - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - } - else if (this.rotation == 2*Math.PI/3) //Component is vertical - { - if (this.valueString.position == "left") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "left"; - } - else if (this.valueString.position == "right") //Label is on right - { - this.ctx.textBaseline = "middle"; - textAlign = "right"; - } - } - this.ctx.translate(this.valueString.x, this.valueString.y); - this.ctx.rotate(this.rotation); - var str; - if (this.valueString.decimal < 0) - str = this.value + " " + this.valueString.suffix; - else //Force a certain number of digits - str = (this.value).toFixed(this.valueString.decimal) + " " + this.valueString.suffix; - - this.diagram.drawSubSuperScript(this.ctx, str, 0, 0, textAlign); - this.ctx.restore(); - } - } - - Component.prototype.isInside = function(x, y) - { - var pt = transform(x, y, this.x, this.y, this.rotation); - if((this.transBoundingBox[0] <= pt.x) && (pt.x <= this.transBoundingBox[2]) && (this.transBoundingBox[1] <= pt.y) && (pt.y <= this.transBoundingBox[3])) - return true; - else - return false; - } - - //***** NODE COMPONENT *****// - function Node(x, y) - { - //Call super class - this.Component(x, y); - this.boundingBox = [-2, -2, 2, 2]; - this.nodeRadius = 2; - } - - copyPrototype(Node, Component); - Node.prototype.paint = function() - { - this.initPaint(); - this.ctx.save(); - this.transform(); - this.ctx.strokeStyle = this.color; - this.ctx.fillStyle = this.color; - this.diagram.drawCircle(this.ctx, 0, 0, this.nodeRadius, true); - this.drawLabel(); - this.ctx.restore(); - } - - Node.prototype.toString = function() - { - return ""; - } - - //***** WIRE COMPONENT *****// - function Wire(x1, y1, x2, y2) - { - //Call super class - this.Component(x1, y1); - this.dx = x2 - x1; - this.dy = y2 - y1; - this.boundingBox = [-5, -5, this.dx + 5, this.dy + 5]; - } - - copyPrototype(Wire, Component); - Wire.prototype.paint = function() - { - this.initPaint(); - this.ctx.save(); - this.transform(); - this.ctx.strokeStyle = this.color; - this.ctx.fillStyle = this.color; - this.diagram.drawLine(this.ctx, 0, 0, this.dx, this.dy); - this.ctx.restore(); - } - - Wire.prototype.toString = function() - { - return ""; - } - - //***** LABEL *****// - function Label(x, y, value, textAlign) - { - //Call super class - this.Component(x, y); - this.boundingBox = [-10, -10, 10, 10]; - this.value = value; - this.textAlign = textAlign; - } - - copyPrototype(Label, Component); - Label.prototype.paint = function() - { - this.ctx.save(); - this.ctx.textAlign = "left"; - this.ctx.translate(this.x, this.y); - this.ctx.rotate(this.rotation); - this.ctx.strokeStyle = this.color; - this.ctx.fillStyle = this.color; - this.diagram.drawSubSuperScript(this.ctx, this.value, 0, 0, this.textAlign); - this.ctx.restore(); - } - - Label.prototype.toString = function() - { - return "