From dbaea134b1cee2a9cdc4968dac50e50e44c162bb Mon Sep 17 00:00:00 2001 From: zsloan Date: Sun, 15 Mar 2020 15:03:15 -0500 Subject: Fixed digits issue, though still need to figure out how to increase gap between y axis and title --- wqflask/wqflask/static/new/javascript/show_trait.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/wqflask/wqflask/static/new/javascript/show_trait.js b/wqflask/wqflask/static/new/javascript/show_trait.js index 819c4d12..0162f858 100644 --- a/wqflask/wqflask/static/new/javascript/show_trait.js +++ b/wqflask/wqflask/static/new/javascript/show_trait.js @@ -1017,10 +1017,12 @@ get_bar_range = function(sample_vals, sample_errors = null){ root.chart_range = get_bar_range(get_sample_vals(sample_lists[0]), get_sample_errors(sample_lists[0])[0]) val_range = root.chart_range[1] - root.chart_range[0] -if (val_range < 5){ - tick_digits = '.1f' +if (val_range < 0.05){ + tick_digits = '.3f' } else if (val_range < 0.5) { tick_digits = '.2f' +} else if (val_range < 5){ + tick_digits = '.1f' } else { tick_digits = 'f' } @@ -1045,7 +1047,7 @@ if (js_data.num_values < 256) { }, }, yaxis: { - title: js_data.unit_type, + title: "" + js_data.unit_type + "", range: root.chart_range, titlefont: { size: 16 -- cgit v1.2.3 From 49d1af523c520a80f48f2e8d021877f6fb0f12a3 Mon Sep 17 00:00:00 2001 From: zsloan Date: Thu, 19 Mar 2020 11:46:49 -0500 Subject: Made a minor change just to help qtlreaper deal with genotype files with positions in bases instead of megabases --- wqflask/wqflask/marker_regression/qtlreaper_mapping.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/wqflask/wqflask/marker_regression/qtlreaper_mapping.py b/wqflask/wqflask/marker_regression/qtlreaper_mapping.py index 41764c1c..0c560582 100644 --- a/wqflask/wqflask/marker_regression/qtlreaper_mapping.py +++ b/wqflask/wqflask/marker_regression/qtlreaper_mapping.py @@ -97,7 +97,10 @@ def parse_reaper_output(gwa_filename, permu_filename, bootstrap_filename): except: marker['chr'] = line.split("\t")[2] marker['cM'] = float(line.split("\t")[3]) - marker['Mb'] = float(line.split("\t")[4]) + if float(line.split("\t")[4]) > 1000: + marker['Mb'] = float(line.split("\t")[4])/1000000 + else: + marker['Mb'] = float(line.split("\t")[4]) if float(line.split("\t")[7]) != 1: marker['p_value'] = float(line.split("\t")[7]) marker['lrs_value'] = float(line.split("\t")[5]) -- cgit v1.2.3 From 3dc5931e6729631affe23cfa736c98836e5e171b Mon Sep 17 00:00:00 2001 From: zsloan Date: Wed, 25 Mar 2020 13:53:20 -0500 Subject: Added fix for issue when creating collections (related to hmac import change) --- wqflask/wqflask/collect.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wqflask/wqflask/collect.py b/wqflask/wqflask/collect.py index 2a94e63d..74eb869f 100644 --- a/wqflask/wqflask/collect.py +++ b/wqflask/wqflask/collect.py @@ -43,10 +43,10 @@ def process_traits(unprocessed_traits): unprocessed_traits = unprocessed_traits.split(",") traits = set() for trait in unprocessed_traits: - data, _separator, hmac = trait.rpartition(':') + data, _separator, the_hmac = trait.rpartition(':') data = data.strip() if g.user_session.logged_in: - assert hmac == hmac.data_hmac(data), "Data tampering?" + assert the_hmac == hmac.hmac_creation(data), "Data tampering?" traits.add(str(data)) return traits -- cgit v1.2.3 From 05c172a8330f28c9c81300448d9f2630a6d7ff0f Mon Sep 17 00:00:00 2001 From: zsloan Date: Sat, 4 Apr 2020 20:23:34 -0500 Subject: Changed Y axis on permutation histogram from Frequency to Count --- wqflask/wqflask/templates/mapping_results.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wqflask/wqflask/templates/mapping_results.html b/wqflask/wqflask/templates/mapping_results.html index 86816d17..c046f7fb 100644 --- a/wqflask/wqflask/templates/mapping_results.html +++ b/wqflask/wqflask/templates/mapping_results.html @@ -490,7 +490,7 @@ }, yaxis: { autorange: true, - title: "Frequency", + title: "Count", titlefont: { family: "arial", size: 20 -- cgit v1.2.3 From dce4f524ae9640553eb8aa319ed593de9d603231 Mon Sep 17 00:00:00 2001 From: zsloan Date: Mon, 6 Apr 2020 17:41:19 -0500 Subject: Added Mean column for phenotype traits in search and global search result tables --- wqflask/base/data_set.py | 2 ++ wqflask/base/trait.py | 5 +++-- wqflask/wqflask/do_search.py | 1 + wqflask/wqflask/gsearch.py | 7 ++++++- wqflask/wqflask/search_results.py | 3 +++ wqflask/wqflask/templates/gsearch_pheno.html | 6 ++++++ wqflask/wqflask/templates/search_result_page.html | 7 +++++++ 7 files changed, 28 insertions(+), 3 deletions(-) diff --git a/wqflask/base/data_set.py b/wqflask/base/data_set.py index 5b32c5ae..ebf3f021 100644 --- a/wqflask/base/data_set.py +++ b/wqflask/base/data_set.py @@ -710,6 +710,7 @@ class PhenotypeDataSet(DataSet): 'Phenotype.Pre_publication_description', 'Phenotype.Pre_publication_abbreviation', 'Phenotype.Post_publication_abbreviation', + 'PublishXRef.mean', 'Phenotype.Lab_code', 'Publication.PubMed_ID', 'Publication.Abstract', @@ -725,6 +726,7 @@ class PhenotypeDataSet(DataSet): 'original_description', 'pre_publication_abbreviation', 'post_publication_abbreviation', + 'mean', 'lab_code', 'submitter', 'owner', 'authorized_users', diff --git a/wqflask/base/trait.py b/wqflask/base/trait.py index 8e779a11..5525472e 100644 --- a/wqflask/base/trait.py +++ b/wqflask/base/trait.py @@ -354,7 +354,7 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): SELECT PublishXRef.Id, InbredSet.InbredSetCode, Publication.PubMed_ID, Phenotype.Pre_publication_description, Phenotype.Post_publication_description, Phenotype.Original_description, - Phenotype.Pre_publication_abbreviation, Phenotype.Post_publication_abbreviation, + Phenotype.Pre_publication_abbreviation, Phenotype.Post_publication_abbreviation, PublishXRef.mean, Phenotype.Lab_code, Phenotype.Submitter, Phenotype.Owner, Phenotype.Authorized_Users, Publication.Authors, Publication.Title, Publication.Abstract, Publication.Journal, Publication.Volume, Publication.Pages, @@ -503,8 +503,9 @@ def retrieve_trait_info(trait, dataset, get_qtl_info=False): #LRS and its location trait.LRS_score_repr = "N/A" trait.LRS_location_repr = "N/A" - trait.locus = trait.locus_chr = trait.locus_mb = trait.lrs = trait.pvalue = trait.mean = trait.additive = "" + trait.locus = trait.locus_chr = trait.locus_mb = trait.lrs = trait.pvalue = trait.additive = "" if dataset.type == 'ProbeSet' and not trait.cellid: + trait.mean = "" query = """ SELECT ProbeSetXRef.Locus, ProbeSetXRef.LRS, ProbeSetXRef.pValue, ProbeSetXRef.mean, ProbeSetXRef.additive diff --git a/wqflask/wqflask/do_search.py b/wqflask/wqflask/do_search.py index b56a858a..05caa100 100644 --- a/wqflask/wqflask/do_search.py +++ b/wqflask/wqflask/do_search.py @@ -218,6 +218,7 @@ class PhenotypeSearch(DoSearch): header_fields = ['Index', 'Record', 'Description', + 'Mean', 'Authors', 'Year', 'Max LRS', diff --git a/wqflask/wqflask/gsearch.py b/wqflask/wqflask/gsearch.py index 12813e9a..3d9b508a 100644 --- a/wqflask/wqflask/gsearch.py +++ b/wqflask/wqflask/gsearch.py @@ -139,7 +139,8 @@ class GSearch(object): Publication.`PubMed_ID`, PublishXRef.`LRS`, PublishXRef.`additive`, - InbredSet.`InbredSetCode` + InbredSet.`InbredSetCode`, + PublishXRef.`mean` FROM Species,InbredSet,PublishFreeze,PublishXRef,Phenotype,Publication WHERE PublishXRef.`InbredSetId`=InbredSet.`Id` AND PublishFreeze.`InbredSetId`=InbredSet.`Id` @@ -183,6 +184,10 @@ class GSearch(object): this_trait['description'] = line[5].decode('utf-8', 'replace') else: this_trait['description'] = "N/A" + if line[13] != None and line[13] != "": + this_trait['mean'] = line[13] + else: + this_trait['mean'] = "N/A" this_trait['authors'] = line[7] this_trait['year'] = line[8] if this_trait['year'].isdigit(): diff --git a/wqflask/wqflask/search_results.py b/wqflask/wqflask/search_results.py index 34c647f4..698389ab 100644 --- a/wqflask/wqflask/search_results.py +++ b/wqflask/wqflask/search_results.py @@ -143,6 +143,9 @@ views.py). trait_dict['pubmed_id'] = this_trait.pubmed_id trait_dict['pubmed_link'] = this_trait.pubmed_link trait_dict['pubmed_text'] = this_trait.pubmed_text + trait_dict['mean'] = "N/A" + if this_trait.mean != "" and this_trait.mean != None: + trait_dict['mean'] = '%.3f' % this_trait.mean trait_dict['lrs_score'] = this_trait.LRS_score_repr trait_dict['lrs_location'] = this_trait.LRS_location_repr trait_dict['additive'] = "N/A" diff --git a/wqflask/wqflask/templates/gsearch_pheno.html b/wqflask/wqflask/templates/gsearch_pheno.html index 4d5238ef..f6ffff47 100644 --- a/wqflask/wqflask/templates/gsearch_pheno.html +++ b/wqflask/wqflask/templates/gsearch_pheno.html @@ -189,6 +189,12 @@ } } }, + { + 'title': "Mean", + 'type': "natural", + 'width': "10%", + 'data': "mean" + }, { 'title': "Authors", 'type': "natural", diff --git a/wqflask/wqflask/templates/search_result_page.html b/wqflask/wqflask/templates/search_result_page.html index 13457030..162bde08 100644 --- a/wqflask/wqflask/templates/search_result_page.html +++ b/wqflask/wqflask/templates/search_result_page.html @@ -349,6 +349,13 @@ } } }, + { + 'title': "Mean", + 'type': "natural", + 'width': "110px", + 'data': "mean", + 'orderSequence': [ "desc", "asc"] + }, { 'title': "Authors", 'type': "natural", -- cgit v1.2.3 From 3fed8e9bdb24ee0b9276794494df689d7a877421 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Mon, 8 Jul 2019 17:14:18 +0000 Subject: Mariadb --- doc/README.org | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/doc/README.org b/doc/README.org index 620c946c..b15bc33f 100644 --- a/doc/README.org +++ b/doc/README.org @@ -7,8 +7,8 @@ - [[#docker][Docker]] - [[#with-source][With source]] - [[#running-gn2][Running GN2]] - - [[#run-mysql-server][Run MySQL server]] - - [[#install-mysql-with-gnu-guix][Install MySQL with GNU GUIx]] + - [[#run-mariadb-server][Run MariaDB server]] + - [[#install-mariadb-with-gnu-guix][Install MariaDB with GNU GUIx]] - [[#load-the-small-database-in-mysql][Load the small database in MySQL]] - [[#gn2-dependency-graph][GN2 Dependency Graph]] - [[#working-with-the-gn2-source-code][Working with the GN2 source code]] @@ -94,8 +94,10 @@ or you can set environment variables to override individual parameters, e.g. the debug and logging switches can be particularly useful when developing GN2. -* Run MySQL server -** Install MySQL with GNU GUIx +* Run MariaDB server +** Install MariaDB with GNU GUIx + +/Note we are moving to MariaDB/ These are the steps you can take to install a fresh installation of mysql (which comes as part of the GNU Guix genenetwork2 install). @@ -103,30 +105,30 @@ mysql (which comes as part of the GNU Guix genenetwork2 install). As root configure and run #+BEGIN_SRC bash -adduser mysql && addgroup mysql -mysqld --datadir=/var/mysql --initialize-insecure -mkdir -p /var/run/mysqld -chown mysql.mysql ~/mysql /var/run/mysqld -mysqld -u mysql --datadir=/var/mysql --explicit_defaults_for_timestamp -P 12048" +adduser mariadb && addgroup mariadb +mysqld --datadir=/home/mariadb/database --initialize-insecure +mkdir -p /var/run/mariadbd +chown mariadb.mariadb /var/run/mariadbd +mysqld -u mariadb --datadir=/home/mariadb/database/mariadb --explicit_defaults_for_timestamp -P 12048" #+END_SRC If you want to run as root you may have to set : /etc/my.cnf -: [mysqld] +: [mariadbd] : user=root To check error output in a file on start-up run with something like -: mysqld -u mysql --console --explicit_defaults_for_timestamp --datadir=/gnu/mysql --log-error=~/test.log +: mariadbd -u mariadb --console --explicit_defaults_for_timestamp --datadir=/gnu/mariadb --log-error=~/test.log -Other tips are that Guix installs mysqld in your profile, so this may work +Other tips are that Guix installs mariadbd in your profile, so this may work -: /home/user/.guix-profile/bin/mysqld -u mysql --explicit_defaults_for_timestamp --datadir=/gnu/mysql +: /home/user/.guix-profile/bin/mariadbd -u mariadb --explicit_defaults_for_timestamp --datadir=/gnu/mariadb When you get errors like: -: qlalchemy.exc.IntegrityError: (_mysql_exceptions.IntegrityError) (1215, 'Cannot add foreign key constraint') +: qlalchemy.exc.IntegrityError: (_mariadb_exceptions.IntegrityError) (1215, 'Cannot add foreign key constraint') you may need to set -- cgit v1.2.3 From 6bdae3220dd52f136f9aeb7fe95bb40e5b0884bc Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Thu, 5 Sep 2019 05:22:52 +0000 Subject: Text search comment --- doc/README.org | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/README.org b/doc/README.org index b15bc33f..03cba62a 100644 --- a/doc/README.org +++ b/doc/README.org @@ -118,6 +118,13 @@ If you want to run as root you may have to set : [mariadbd] : user=root +You also need to set + +: ft_min_word_len = 3 + +To make sure word text searches (shh) work and rebuild the tables if +required. + To check error output in a file on start-up run with something like : mariadbd -u mariadb --console --explicit_defaults_for_timestamp --datadir=/gnu/mariadb --log-error=~/test.log -- cgit v1.2.3 From e72862bdb307b016cb1c2269818edae5c9ddc150 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Mon, 20 Apr 2020 09:46:16 -0500 Subject: Remove Elastic --- doc/README.org | 30 ------------------------------ 1 file changed, 30 deletions(-) diff --git a/doc/README.org b/doc/README.org index 03cba62a..4057cb79 100644 --- a/doc/README.org +++ b/doc/README.org @@ -12,8 +12,6 @@ - [[#load-the-small-database-in-mysql][Load the small database in MySQL]] - [[#gn2-dependency-graph][GN2 Dependency Graph]] - [[#working-with-the-gn2-source-code][Working with the GN2 source code]] - - [[#running-elasticsearch][Running ElasticSearch]] - - [[#systemd][SystemD]] - [[#read-more][Read more]] - [[#trouble-shooting][Trouble shooting]] - [[#importerror-no-module-named-jinja2][ImportError: No module named jinja2]] @@ -205,34 +203,6 @@ http://biogems.info/contrib/genenetwork/gn2.svg See [[development.org]]. -* Running ElasticSearch - -In order to start up elasticsearch: -Penguin - change user to "elasticsearch" and use the following command: "env JAVA_HOME=/opt/jdk-9.0.4 /opt/elasticsearch-6.2.1/bin/elasticsearch" - - -** SystemD - -New server - as root run "systemctl restart elasticsearch" - -#+BEGIN_SRC -tux01:/etc/systemd/system# cat elasticsearch.service -[Unit] -Description=Run Elasticsearch - -[Service] -ExecStart=/opt/elasticsearch-6.2.1/bin/elasticsearch -Environment=JAVA_HOME=/opt/jdk-9.0.4 -Environment="ES_JAVA_OPTS=-Xms1g -Xmx8g" -Environment="PATH=/usr/local/bin:/usr/bin:/bin:/usr/local/games:/usr/games:/opt/jdk-9.0.4/bin" -LimitNOFILE=65536 -StandardOutput=syslog -StandardError=syslog -User=elasticsearch - -[Install] -WantedBy=multi-user.target -#+END_SRC * Read more -- cgit v1.2.3 From 960f42184da639a4e352ba7517cf0eece06d58f9 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Thu, 23 Apr 2020 10:44:46 -0500 Subject: Docs --- doc/GUIX-Reproducible-from-source.org | 1 - doc/README.org | 80 ++++++++++++++++------------------- 2 files changed, 36 insertions(+), 45 deletions(-) diff --git a/doc/GUIX-Reproducible-from-source.org b/doc/GUIX-Reproducible-from-source.org index 83adce99..19e4d14f 100644 --- a/doc/GUIX-Reproducible-from-source.org +++ b/doc/GUIX-Reproducible-from-source.org @@ -33,7 +33,6 @@ GNU Guix has to be installed as root. I tested this recipe on a fresh install of Debian 8.3.0 (in KVM) though it should work on any modern Linux distribution (including CentOS). - Note that GN2 consists of an approx. 5 GB installation including database. If you use a virtual machine we recommend to use at least double. diff --git a/doc/README.org b/doc/README.org index 4057cb79..828ed2cd 100644 --- a/doc/README.org +++ b/doc/README.org @@ -3,9 +3,6 @@ * Table of Contents :TOC: - [[#introduction][Introduction]] - [[#install][Install]] - - [[#tarball][Tarball]] - - [[#docker][Docker]] - - [[#with-source][With source]] - [[#running-gn2][Running GN2]] - [[#run-mariadb-server][Run MariaDB server]] - [[#install-mariadb-with-gnu-guix][Install MariaDB with GNU GUIx]] @@ -20,6 +17,8 @@ - [[#rpy2-error-show-now-found][Rpy2 error 'show' now found]] - [[#mysql-cant-connect-server-through-socket-error][Mysql can't connect server through socket ERROR]] - [[#irc-session][IRC session]] + - [[#notes][NOTES]] + - [[#deploying-gn2-official][Deploying GN2 official]] * Introduction @@ -38,44 +37,10 @@ an example of the [[#gn2-dependency-graph][GN2 Dependency Graph]]. * Install -The quickest way to install GN2 is by using a binary installation -(tarball or Docker image). These installations are bundled by GNU -Guix and include all dependencies. You can install GeneNetwork on most -Linux distributions, including Debian, Ubuntu, Fedora and CentOS, -provided you have administrator privileges (root). The alternative is -a Docker installation. - -** Tarball - -Download the ~800Mb tarball from -[[http://files.genenetwork.org/software/binary_tarball/]]. Validate the checksum and -unpack to root, for example - -: tar xvzf genenetwork2-2.10rc3-1538ffd-tarball-pack.tar.gz -: mv /gnu / -: mv /opt/genenetwork2 /opt/ - -Now you shoud be able to start the server with - -: /opt/genenetwork2/bin/genenetwork2 - -When the server stops with a MySQL error [[#run-mysql-server][Run MySQL server]] -and set SQL_URI to point at it. For example: - -: export SQL_URI=mysql://gn2:mysql_password@127.0.0.1/db_webqtl_s - -See also [[#mysql-cant-connect-server-through-socket-error][Mysql can't connect server through socket ERROR]]. - -** Docker - -Docker images are also available through -[[http://files.genenetwork.org/software/]]. Validate the checksum and run -with [[https://docs.docker.com/engine/reference/commandline/load/][Docker load]]. - -** With source - -For more elaborate installation instructions on deploying GeneNetwork from -source see [[#source-deployment][Source deployment]]. +Make sure to install GNU Guix using the binary download instructions +on the main website. Follow the instructions on +[[GUIX-Reproducible-from-source.org]] to download pre-built binaries. Note +the download amounts to several GBs of data. * Running GN2 @@ -95,10 +60,10 @@ developing GN2. * Run MariaDB server ** Install MariaDB with GNU GUIx -/Note we are moving to MariaDB/ +/Note: we moved to MariaDB/ These are the steps you can take to install a fresh installation of -mysql (which comes as part of the GNU Guix genenetwork2 install). +mariadb (which comes as part of the GNU Guix genenetwork2 install). As root configure and run @@ -203,7 +168,6 @@ http://biogems.info/contrib/genenetwork/gn2.svg See [[development.org]]. - * Read more If you want to understand the architecture of GN2 read @@ -687,3 +651,31 @@ The following derivations would be built: I am building it on guix.genenetwork.org right now [10:09] nice [10:10] #+end_src + +* NOTES + +** Deploying GN2 official + +Let's see how fast we can deploy a second copy of GN2. + +- [-] Base install + + [X] First install a Debian server with GNU Guix on board + + [X] Get Guix build going + - [X] Build the correct version of Guix + - [X] Check out the correct gn-stable version of guix-bioinformatics http://git.genenetwork.org/pjotrp/guix-bioinformatics + - [X] guix package -i genenetwork2 -p /usr/local/guix-profiles/gn2-stable + + [X] Create a gn2 user and home with space + + [X] Install redis (currently debian) + - [X] add to systemd + - [X] update redis.cnf + - [X] update database + + [X] Install mariadb (currently debian mariadb-server) + - [X] add to systemd + - [X] system stop mysql + - [X] update mysql.cnf + - [X] update database (see gn-services/services/mariadb.md) + - [X] check tables + + [ ] run gn2 (rust-qtlreaper not working) + + [X] update nginx + + [ ] install genenetwork3 + - [ ] add to systemd -- cgit v1.2.3 From e5ccb1a586fcd83ec6feddad7e0b767b29a369b2 Mon Sep 17 00:00:00 2001 From: Pjotr Prins Date: Thu, 23 Apr 2020 18:09:52 +0200 Subject: Ignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 0006d9a8..70d0273d 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ *.bak *.cross *~ +*.swo +*.swp genotype_files/genotype/HSNIH_copy.geno web/new_genotypes/HSNIH.json wqflask/secure_server.py -- cgit v1.2.3 From 92f505e42c6ac6314b29ac152654b393dc581501 Mon Sep 17 00:00:00 2001 From: Arthur Centeno Date: Sun, 26 Apr 2020 09:59:25 -0500 Subject: URL for finding services --- etc/default_settings.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/etc/default_settings.py b/etc/default_settings.py index 75f84dc4..4a35f42d 100644 --- a/etc/default_settings.py +++ b/etc/default_settings.py @@ -28,6 +28,8 @@ GN_VERSION = open("../etc/VERSION","r").read() SQL_URI = "mysql://gn2:mysql_password@localhost/db_webqtl_s" SQL_ALCHEMY_POOL_RECYCLE = 3600 GN_SERVER_URL = "http://localhost:8880/" # REST API server +GN2_BASE_URL = "http://genenetwork.org/" +GN2_BRANCH_URL = GN2_BASE_URL # ---- Flask configuration (see website) TRAP_BAD_REQUEST_ERRORS = True -- cgit v1.2.3