Merge remote-tracking branch 'origin/develop' into fix/xss

# Conflicts:
#	templates/modals/collections/new_model_modal.html
This commit is contained in:
Liam Brydon
2025-11-07 08:34:33 +13:00
44 changed files with 2286 additions and 339 deletions

View File

@ -16,3 +16,5 @@ POSTGRES_PORT=
# MAIL
EMAIL_HOST_USER=
EMAIL_HOST_PASSWORD=
# MATOMO
MATOMO_SITE_ID

View File

@ -137,6 +137,7 @@ USE_TZ = True
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
EMAIL_SUBJECT_PREFIX = "[enviPath] "
if DEBUG:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
else:
@ -146,6 +147,8 @@ else:
EMAIL_HOST_USER = os.environ["EMAIL_HOST_USER"]
EMAIL_HOST_PASSWORD = os.environ["EMAIL_HOST_PASSWORD"]
EMAIL_PORT = 587
DEFAULT_FROM_EMAIL = os.environ["DEFAULT_FROM_EMAIL"]
SERVER_EMAIL = os.environ["SERVER_EMAIL"]
AUTH_USER_MODEL = "epdb.User"
ADMIN_APPROVAL_REQUIRED = os.environ.get("ADMIN_APPROVAL_REQUIRED", "False") == "True"
@ -356,3 +359,6 @@ if MS_ENTRA_ENABLED:
MS_ENTRA_AUTHORITY = f"https://login.microsoftonline.com/{MS_ENTRA_TENANT_ID}"
MS_ENTRA_REDIRECT_URI = os.environ["MS_REDIRECT_URI"]
MS_ENTRA_SCOPES = os.environ.get("MS_SCOPES", "").split(",")
# Site ID 10 -> beta.envipath.org
MATOMO_SITE_ID = os.environ.get("MATOMO_SITE_ID", "10")

View File

@ -7,6 +7,7 @@ from .models import (
GroupPackagePermission,
Package,
MLRelativeReasoning,
EnviFormer,
Compound,
CompoundStructure,
SimpleAmbitRule,
@ -19,11 +20,12 @@ from .models import (
Setting,
ExternalDatabase,
ExternalIdentifier,
JobLog,
)
class UserAdmin(admin.ModelAdmin):
pass
list_display = ["username", "email", "is_active"]
class UserPackagePermissionAdmin(admin.ModelAdmin):
@ -38,8 +40,14 @@ class GroupPackagePermissionAdmin(admin.ModelAdmin):
pass
class JobLogAdmin(admin.ModelAdmin):
pass
class EPAdmin(admin.ModelAdmin):
search_fields = ["name", "description"]
list_display = ["name", "url", "created"]
ordering = ["-created"]
class PackageAdmin(EPAdmin):
@ -50,6 +58,10 @@ class MLRelativeReasoningAdmin(EPAdmin):
pass
class EnviFormerAdmin(EPAdmin):
pass
class CompoundAdmin(EPAdmin):
pass
@ -102,8 +114,10 @@ admin.site.register(User, UserAdmin)
admin.site.register(UserPackagePermission, UserPackagePermissionAdmin)
admin.site.register(Group, GroupAdmin)
admin.site.register(GroupPackagePermission, GroupPackagePermissionAdmin)
admin.site.register(JobLog, JobLogAdmin)
admin.site.register(Package, PackageAdmin)
admin.site.register(MLRelativeReasoning, MLRelativeReasoningAdmin)
admin.site.register(EnviFormer, EnviFormerAdmin)
admin.site.register(Compound, CompoundAdmin)
admin.site.register(CompoundStructure, CompoundStructureAdmin)
admin.site.register(SimpleAmbitRule, SimpleAmbitRuleAdmin)

View File

@ -27,6 +27,7 @@ from epdb.models import (
Compound,
Reaction,
CompoundStructure,
EnzymeLink,
)
from utilities.chem import FormatConverter
from utilities.misc import PackageImporter, PackageExporter
@ -626,6 +627,8 @@ class PackageManager(object):
parent_mapping = {}
# Mapping old scen_id to old_obj_id
scen_mapping = defaultdict(list)
# Enzymelink Mapping rule_id to enzymelink objects
enzyme_mapping = defaultdict(list)
# Store Scenarios
for scenario in data["scenarios"]:
@ -657,9 +660,7 @@ class PackageManager(object):
# Broken eP Data
if name == "initialmasssediment" and addinf_data == "missing data":
continue
# TODO Enzymes arent ready yet
if name == "enzyme":
if name == "columnheight" and addinf_data == "(2)-(2.5);(6)-(8)":
continue
try:
@ -749,6 +750,9 @@ class PackageManager(object):
for scen in rule["scenarios"]:
scen_mapping[scen["id"]].append(r)
for enzyme_link in rule.get("enzymeLinks", []):
enzyme_mapping[r.uuid].append(enzyme_link)
print("Par: ", len(par_rules))
print("Seq: ", len(seq_rules))
@ -766,6 +770,9 @@ class PackageManager(object):
for scen in par_rule["scenarios"]:
scen_mapping[scen["id"]].append(r)
for enzyme_link in par_rule.get("enzymeLinks", []):
enzyme_mapping[r.uuid].append(enzyme_link)
for simple_rule in par_rule["simpleRules"]:
if simple_rule["id"] in mapping:
r.simple_rules.add(SimpleRule.objects.get(uuid=mapping[simple_rule["id"]]))
@ -786,6 +793,9 @@ class PackageManager(object):
for scen in seq_rule["scenarios"]:
scen_mapping[scen["id"]].append(r)
for enzyme_link in seq_rule.get("enzymeLinks", []):
enzyme_mapping[r.uuid].append(enzyme_link)
for i, simple_rule in enumerate(seq_rule["simpleRules"]):
sro = SequentialRuleOrdering()
sro.simple_rule = simple_rule
@ -919,6 +929,39 @@ class PackageManager(object):
print("Scenarios linked...")
# Import Enzyme Links
for rule_uuid, enzyme_links in enzyme_mapping.items():
r = Rule.objects.get(uuid=rule_uuid)
for enzyme in enzyme_links:
e = EnzymeLink()
e.uuid = UUID(enzyme["id"].split("/")[-1]) if keep_ids else uuid4()
e.rule = r
e.name = enzyme["name"]
e.ec_number = enzyme["ecNumber"]
e.classification_level = enzyme["classificationLevel"]
e.linking_method = enzyme["linkingMethod"]
e.save()
for reaction in enzyme["reactionLinkEvidence"]:
reaction = Reaction.objects.get(uuid=mapping[reaction["id"]])
e.reaction_evidence.add(reaction)
for edge in enzyme["edgeLinkEvidence"]:
edge = Edge.objects.get(uuid=mapping[edge["id"]])
e.reaction_evidence.add(edge)
for evidence in enzyme["linkEvidence"]:
matches = re.findall(r">(R[0-9]+)<", evidence["evidence"])
if not matches or len(matches) != 1:
logger.warning(f"Could not find reaction id in {evidence['evidence']}")
continue
e.add_kegg_reaction_id(matches[0])
e.save()
print("Enzyme links imported...")
print("Import statistics:")
print("Package {} stored".format(pack.url))
print("Imported {} compounds".format(Compound.objects.filter(package=pack).count()))

View File

@ -7,10 +7,11 @@ from epdb.models import MLRelativeReasoning, EnviFormer, Package
class Command(BaseCommand):
"""This command can be run with
`python manage.py create_ml_models [model_names] -d [data_packages] OPTIONAL: -e [eval_packages]`
For example, to train both EnviFormer and MLRelativeReasoning on BBD and SOIL and evaluate them on SLUDGE
the below command would be used:
`python manage.py create_ml_models enviformer mlrr -d bbd soil -e sludge
`python manage.py create_ml_models [model_names] -d [data_packages] FOR MLRR ONLY: -r [rule_packages]
OPTIONAL: -e [eval_packages] -t threshold`
For example, to train both EnviFormer and MLRelativeReasoning on BBD and SOIL and evaluate them on SLUDGE with a
threshold of 0.6, the below command would be used:
`python manage.py create_ml_models enviformer mlrr -d bbd soil -e sludge -t 0.6
"""
def add_arguments(self, parser):
@ -34,6 +35,13 @@ class Command(BaseCommand):
help="Rule Packages mandatory for MLRR",
default=[],
)
parser.add_argument(
"-t",
"--threshold",
type=float,
help="Model prediction threshold",
default=0.5,
)
@transaction.atomic
def handle(self, *args, **options):
@ -67,7 +75,11 @@ class Command(BaseCommand):
return packages
# Iteratively create models in options["model_names"]
print(f"Creating models: {options['model_names']}")
print(f"Creating models: {options['model_names']}\n"
f"Data packages: {options['data_packages']}\n"
f"Rule Packages (only for MLRR): {options['rule_packages']}\n"
f"Eval Packages: {options['eval_packages']}\n"
f"Threshold: {options['threshold']:.2f}")
data_packages = decode_packages(options["data_packages"])
eval_packages = decode_packages(options["eval_packages"])
rule_packages = decode_packages(options["rule_packages"])
@ -78,9 +90,10 @@ class Command(BaseCommand):
pack,
data_packages=data_packages,
eval_packages=eval_packages,
threshold=0.5,
name="EnviFormer - T0.5",
description="EnviFormer transformer",
threshold=options['threshold'],
name=f"EnviFormer - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
description=f"EnviFormer transformer trained on {options['data_packages']} "
f"evaluated on {options['eval_packages']}.",
)
elif model_name == "mlrr":
model = MLRelativeReasoning.create(
@ -88,9 +101,10 @@ class Command(BaseCommand):
rule_packages=rule_packages,
data_packages=data_packages,
eval_packages=eval_packages,
threshold=0.5,
name="ECC - BBD - T0.5",
description="ML Relative Reasoning",
threshold=options['threshold'],
name=f"ECC - {', '.join(options['data_packages'])} - T{options['threshold']:.2f}",
description=f"ML Relative Reasoning trained on {options['data_packages']} with rules from "
f"{options['rule_packages']} and evaluated on {options['eval_packages']}.",
)
else:
raise ValueError(f"Cannot create model of type {model_name}, unknown model type")
@ -100,6 +114,6 @@ class Command(BaseCommand):
print(f"Training {model_name}")
model.build_model()
print(f"Evaluating {model_name}")
model.evaluate_model()
model.evaluate_model(False, eval_packages=eval_packages)
print(f"Saving {model_name}")
model.save()

View File

@ -0,0 +1,59 @@
import json
import os
import tarfile
from tempfile import TemporaryDirectory
from django.conf import settings as s
from django.core.management.base import BaseCommand
from django.db import transaction
from epdb.models import EnviFormer
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"model",
type=str,
help="Model UUID of the Model to Dump",
)
parser.add_argument("--output", type=str)
def package_dict_and_folder(self, dict_data, folder_path, output_path):
with TemporaryDirectory() as tmpdir:
dict_filename = os.path.join(tmpdir, "data.json")
with open(dict_filename, "w", encoding="utf-8") as f:
json.dump(dict_data, f, indent=2)
with tarfile.open(output_path, "w:gz") as tar:
tar.add(dict_filename, arcname="data.json")
tar.add(folder_path, arcname=os.path.basename(folder_path))
os.remove(dict_filename)
@transaction.atomic
def handle(self, *args, **options):
output = options["output"]
if os.path.exists(output):
raise ValueError(f"Output file {output} already exists")
model = EnviFormer.objects.get(uuid=options["model"])
data = {
"uuid": str(model.uuid),
"name": model.name,
"description": model.description,
"kv": model.kv,
"data_packages_uuids": [str(p.uuid) for p in model.data_packages.all()],
"eval_packages_uuids": [str(p.uuid) for p in model.data_packages.all()],
"threshold": model.threshold,
"eval_results": model.eval_results,
"multigen_eval": model.multigen_eval,
"model_status": model.model_status,
}
model_folder = os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid))
self.package_dict_and_folder(data, model_folder, output)

View File

@ -0,0 +1,81 @@
import json
import os
import shutil
import tarfile
from tempfile import TemporaryDirectory
from django.conf import settings as s
from django.core.management.base import BaseCommand
from django.db import transaction
from epdb.models import EnviFormer, Package
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"input",
type=str,
help=".tar.gz file containing the Model dump.",
)
parser.add_argument(
"package",
type=str,
help="Package UUID where the Model should be loaded to.",
)
def read_dict_and_folder_from_archive(self, archive_path, extract_to="extracted_folder"):
with tarfile.open(archive_path, "r:gz") as tar:
tar.extractall(extract_to)
dict_path = os.path.join(extract_to, "data.json")
if not os.path.exists(dict_path):
raise FileNotFoundError("data.json not found in the archive.")
with open(dict_path, "r", encoding="utf-8") as f:
data_dict = json.load(f)
extracted_items = os.listdir(extract_to)
folders = [item for item in extracted_items if item != "data.json"]
folder_path = os.path.join(extract_to, folders[0]) if folders else None
return data_dict, folder_path
@transaction.atomic
def handle(self, *args, **options):
if not os.path.exists(options["input"]):
raise ValueError(f"Input file {options['input']} does not exist.")
target_package = Package.objects.get(uuid=options["package"])
with TemporaryDirectory() as tmpdir:
data, folder = self.read_dict_and_folder_from_archive(options["input"], tmpdir)
model = EnviFormer()
model.package = target_package
# model.uuid = data["uuid"]
model.name = data["name"]
model.description = data["description"]
model.kv = data["kv"]
model.threshold = float(data["threshold"])
model.eval_results = data["eval_results"]
model.multigen_eval = data["multigen_eval"]
model.model_status = data["model_status"]
model.save()
for p_uuid in data["data_packages_uuids"]:
p = Package.objects.get(uuid=p_uuid)
model.data_packages.add(p)
for p_uuid in data["eval_packages_uuids"]:
p = Package.objects.get(uuid=p_uuid)
model.eval_packages.add(p)
target_folder = os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid))
shutil.copytree(folder, target_folder)
os.rename(
os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid), f"{data['uuid']}.ckpt"),
os.path.join(s.MODEL_DIR, "enviformer", str(model.uuid), f"{model.uuid}.ckpt"),
)

View File

@ -1,8 +1,10 @@
from django.apps import apps
from django.core.management.base import BaseCommand
from django.db.models import F, Value
from django.db.models.functions import Replace
from django.db.models import F, Value, TextField, JSONField
from django.db.models.functions import Replace, Cast
from epdb.models import EnviPathModel
class Command(BaseCommand):
@ -41,6 +43,7 @@ class Command(BaseCommand):
"RuleBasedRelativeReasoning",
"EnviFormer",
"ApplicabilityDomain",
"EnzymeLink",
]
for model in MODELS:
obj_cls = apps.get_model("epdb", model)
@ -48,3 +51,14 @@ class Command(BaseCommand):
obj_cls.objects.update(
url=Replace(F("url"), Value(options["old"]), Value(options["new"]))
)
if issubclass(obj_cls, EnviPathModel):
obj_cls.objects.update(
kv=Cast(
Replace(
Cast(F("kv"), output_field=TextField()),
Value(options["old"]),
Value(options["new"]),
),
output_field=JSONField(),
)
)

View File

@ -0,0 +1,38 @@
from datetime import date, timedelta
from django.core.management.base import BaseCommand
from django.db import transaction
from epdb.models import JobLog
class Command(BaseCommand):
def add_arguments(self, parser):
parser.add_argument(
"--cleanup",
type=int,
default=None,
help="Remove all logs older than this number of days. Default is None, which does not remove any logs.",
)
@transaction.atomic
def handle(self, *args, **options):
if options["cleanup"] is not None:
cleanup_dt = date.today() - timedelta(days=options["cleanup"])
print(JobLog.objects.filter(created__lt=cleanup_dt).delete())
logs = JobLog.objects.filter(status="INITIAL")
print(f"Found {logs.count()} logs to update")
updated = 0
for log in logs:
res = log.check_for_update()
if res:
updated += 1
print(f"Updated {updated} logs")
from django.db.models import Count
qs = JobLog.objects.values("status").annotate(total=Count("status"))
for r in qs:
print(r["status"], r["total"])

View File

@ -0,0 +1,64 @@
# Generated by Django 5.2.7 on 2025-10-10 06:58
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields
import uuid
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("epdb", "0007_alter_enviformer_options_enviformer_app_domain_and_more"),
]
operations = [
migrations.CreateModel(
name="EnzymeLink",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, editable=False, verbose_name="created"
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, editable=False, verbose_name="modified"
),
),
(
"uuid",
models.UUIDField(
default=uuid.uuid4, unique=True, verbose_name="UUID of this object"
),
),
("name", models.TextField(default="no name", verbose_name="Name")),
(
"description",
models.TextField(default="no description", verbose_name="Descriptions"),
),
("url", models.TextField(null=True, unique=True, verbose_name="URL")),
("kv", models.JSONField(blank=True, default=dict, null=True)),
("ec_number", models.TextField(verbose_name="EC Number")),
("classification_level", models.IntegerField(verbose_name="Classification Level")),
("linking_method", models.TextField(verbose_name="Linking Method")),
("edge_evidence", models.ManyToManyField(to="epdb.edge")),
("reaction_evidence", models.ManyToManyField(to="epdb.reaction")),
(
"rule",
models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to="epdb.rule"),
),
],
options={
"abstract": False,
},
),
]

View File

@ -0,0 +1,66 @@
# Generated by Django 5.2.7 on 2025-10-27 09:39
import django.db.models.deletion
import django.utils.timezone
import model_utils.fields
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("epdb", "0008_enzymelink"),
]
operations = [
migrations.CreateModel(
name="JobLog",
fields=[
(
"id",
models.BigAutoField(
auto_created=True, primary_key=True, serialize=False, verbose_name="ID"
),
),
(
"created",
model_utils.fields.AutoCreatedField(
default=django.utils.timezone.now, editable=False, verbose_name="created"
),
),
(
"modified",
model_utils.fields.AutoLastModifiedField(
default=django.utils.timezone.now, editable=False, verbose_name="modified"
),
),
("task_id", models.UUIDField(unique=True)),
("job_name", models.TextField()),
(
"status",
models.CharField(
choices=[
("INITIAL", "Initial"),
("SUCCESS", "Success"),
("FAILURE", "Failure"),
("REVOKED", "Revoked"),
("IGNORED", "Ignored"),
],
default="INITIAL",
max_length=20,
),
),
("done_at", models.DateTimeField(blank=True, default=None, null=True)),
("task_result", models.TextField(blank=True, default=None, null=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL
),
),
],
options={
"abstract": False,
},
),
]

View File

@ -311,7 +311,7 @@ class ExternalDatabase(TimeStampedModel):
},
{
"database": ExternalDatabase.objects.get(name="ChEBI"),
"placeholder": "ChEBI ID without prefix e.g. 12345",
"placeholder": "ChEBI ID without prefix e.g. 10576",
},
],
"structure": [
@ -329,7 +329,7 @@ class ExternalDatabase(TimeStampedModel):
},
{
"database": ExternalDatabase.objects.get(name="ChEBI"),
"placeholder": "ChEBI ID without prefix e.g. 12345",
"placeholder": "ChEBI ID without prefix e.g. 10576",
},
],
"reaction": [
@ -343,7 +343,7 @@ class ExternalDatabase(TimeStampedModel):
},
{
"database": ExternalDatabase.objects.get(name="UniProt"),
"placeholder": "Query ID for UniPro e.g. rhea:12345",
"placeholder": "Query ID for UniProt e.g. rhea:12345",
},
],
}
@ -478,7 +478,7 @@ class ChemicalIdentifierMixin(ExternalIdentifierMixin):
return self.add_external_identifier("CAS", cas_number)
def get_pubchem_identifiers(self):
return self.get_external_identifier("PubChem Compound") or self.get_external_identifier(
return self.get_external_identifier("PubChem Compound") | self.get_external_identifier(
"PubChem Substance"
)
@ -495,6 +495,20 @@ class ChemicalIdentifierMixin(ExternalIdentifierMixin):
return self.get_external_identifier("CAS")
class KEGGIdentifierMixin(ExternalIdentifierMixin):
@property
def kegg_reaction_links(self):
return self.get_external_identifier("KEGG Reaction")
def add_kegg_reaction_id(self, kegg_id):
return self.add_external_identifier(
"KEGG Reaction", kegg_id, f"https://www.genome.jp/entry/{kegg_id}"
)
class Meta:
abstract = True
class ReactionIdentifierMixin(ExternalIdentifierMixin):
class Meta:
abstract = True
@ -1014,6 +1028,26 @@ class CompoundStructure(EnviPathModel, AliasMixin, ScenarioMixin, ChemicalIdenti
return self.compound.default_structure == self
class EnzymeLink(EnviPathModel, KEGGIdentifierMixin):
rule = models.ForeignKey("Rule", on_delete=models.CASCADE, db_index=True)
ec_number = models.TextField(blank=False, null=False, verbose_name="EC Number")
classification_level = models.IntegerField(
blank=False, null=False, verbose_name="Classification Level"
)
linking_method = models.TextField(blank=False, null=False, verbose_name="Linking Method")
reaction_evidence = models.ManyToManyField("epdb.Reaction")
edge_evidence = models.ManyToManyField("epdb.Edge")
external_identifiers = GenericRelation("ExternalIdentifier")
def _url(self):
return "{}/enzymelink/{}".format(self.rule.url, self.uuid)
def get_group(self) -> str:
return ".".join(self.ec_number.split(".")[:3]) + ".-"
class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey(
"epdb.Package", verbose_name="Package", on_delete=models.CASCADE, db_index=True
@ -1095,6 +1129,18 @@ class Rule(PolymorphicModel, EnviPathModel, AliasMixin, ScenarioMixin):
return new_rule
def enzymelinks(self):
return self.enzymelink_set.all()
def get_grouped_enzymelinks(self):
res = defaultdict(list)
for el in self.enzymelinks():
key = ".".join(el.ec_number.split(".")[:3]) + ".-"
res[key].append(el)
return dict(res)
class SimpleRule(Rule):
pass
@ -1436,6 +1482,16 @@ class Reaction(EnviPathModel, AliasMixin, ScenarioMixin, ReactionIdentifierMixin
id__in=Edge.objects.filter(edge_label=self).values("pathway_id")
).order_by("name")
def get_related_enzymes(self):
res = []
edges = Edge.objects.filter(edge_label=self)
for e in edges:
for scen in e.scenarios.all():
for ai in scen.additional_information.keys():
if ai == "Enzyme":
res.extend(scen.additional_information[ai])
return res
class Pathway(EnviPathModel, AliasMixin, ScenarioMixin):
package = models.ForeignKey(
@ -2172,10 +2228,18 @@ class PackageBasedModel(EPModel):
self.model_status = self.BUILT_NOT_EVALUATED
self.save()
def evaluate_model(self):
def evaluate_model(self, multigen: bool, eval_packages: List["Package"] = None):
if self.model_status != self.BUILT_NOT_EVALUATED:
raise ValueError(f"Can't evaluate a model in state {self.model_status}!")
if multigen:
self.multigen_eval = multigen
self.save()
if eval_packages is not None:
for p in eval_packages:
self.eval_packages.add(p)
self.model_status = self.EVALUATING
self.save()
@ -2472,7 +2536,6 @@ class RuleBasedRelativeReasoning(PackageBasedModel):
package: "Package",
rule_packages: List["Package"],
data_packages: List["Package"],
eval_packages: List["Package"],
threshold: float = 0.5,
min_count: int = 10,
max_count: int = 0,
@ -2521,10 +2584,6 @@ class RuleBasedRelativeReasoning(PackageBasedModel):
for p in rule_packages:
rbrr.data_packages.add(p)
if eval_packages:
for p in eval_packages:
rbrr.eval_packages.add(p)
rbrr.save()
return rbrr
@ -2579,7 +2638,6 @@ class MLRelativeReasoning(PackageBasedModel):
package: "Package",
rule_packages: List["Package"],
data_packages: List["Package"],
eval_packages: List["Package"],
threshold: float = 0.5,
name: "str" = None,
description: str = None,
@ -2619,10 +2677,6 @@ class MLRelativeReasoning(PackageBasedModel):
for p in rule_packages:
mlrr.data_packages.add(p)
if eval_packages:
for p in eval_packages:
mlrr.eval_packages.add(p)
if build_app_domain:
ad = ApplicabilityDomain.create(
mlrr,
@ -2942,7 +2996,6 @@ class EnviFormer(PackageBasedModel):
def create(
package: "Package",
data_packages: List["Package"],
eval_packages: List["Package"],
threshold: float = 0.5,
name: "str" = None,
description: str = None,
@ -2975,10 +3028,6 @@ class EnviFormer(PackageBasedModel):
for p in data_packages:
mod.data_packages.add(p)
if eval_packages:
for p in eval_packages:
mod.eval_packages.add(p)
# if build_app_domain:
# ad = ApplicabilityDomain.create(mod, app_domain_num_neighbours, app_domain_reliability_threshold,
# app_domain_local_compatibility_threshold)
@ -2992,7 +3041,8 @@ class EnviFormer(PackageBasedModel):
from enviformer import load
ckpt = os.path.join(s.MODEL_DIR, "enviformer", str(self.uuid), f"{self.uuid}.ckpt")
return load(device=s.ENVIFORMER_DEVICE, ckpt_path=ckpt)
mod = load(device=s.ENVIFORMER_DEVICE, ckpt_path=ckpt)
return mod
def predict(self, smiles) -> List["PredictionResult"]:
return self.predict_batch([smiles])[0]
@ -3006,8 +3056,12 @@ class EnviFormer(PackageBasedModel):
for smiles in smiles_list
]
logger.info(f"Submitting {canon_smiles} to {self.name}")
start = datetime.now()
products_list = self.model.predict_batch(canon_smiles)
logger.info(f"Got results {products_list}")
end = datetime.now()
logger.info(
f"Prediction took {(end - start).total_seconds():.2f} seconds. Got results {products_list}"
)
results = []
for products in products_list:
@ -3034,6 +3088,7 @@ class EnviFormer(PackageBasedModel):
start = datetime.now()
# Standardise reactions for the training data, EnviFormer ignores stereochemistry currently
co2 = {"C(=O)=O", "O=C=O"}
ds = []
for reaction in self._get_reactions():
educts = ".".join(
@ -3048,6 +3103,7 @@ class EnviFormer(PackageBasedModel):
for smile in reaction.products.all()
]
)
if products not in co2:
ds.append(f"{educts}>>{products}")
end = datetime.now()
@ -3084,10 +3140,18 @@ class EnviFormer(PackageBasedModel):
args = {"clz": "EnviFormer"}
return args
def evaluate_model(self):
def evaluate_model(self, multigen: bool, eval_packages: List["Package"] = None):
if self.model_status != self.BUILT_NOT_EVALUATED:
raise ValueError(f"Can't evaluate a model in state {self.model_status}!")
if multigen:
self.multigen_eval = multigen
self.save()
if eval_packages is not None:
for p in eval_packages:
self.eval_packages.add(p)
self.model_status = self.EVALUATING
self.save()
@ -3244,7 +3308,7 @@ class EnviFormer(PackageBasedModel):
ds = self.load_dataset()
n_splits = 20
shuff = ShuffleSplit(n_splits=n_splits, test_size=0.25, random_state=42)
shuff = ShuffleSplit(n_splits=n_splits, test_size=0.1, random_state=42)
# Single gen eval is done in one loop of train then evaluate rather than storing all n_splits trained models
# this helps reduce the memory footprint.
@ -3312,7 +3376,7 @@ class EnviFormer(PackageBasedModel):
# Compute splits of the collected pathway and evaluate. Like single gen we train and evaluate in each
# iteration instead of storing all trained models.
for split_id, (train, test) in enumerate(
ShuffleSplit(n_splits=n_splits, test_size=0.25, random_state=42).split(pathways)
ShuffleSplit(n_splits=n_splits, test_size=0.1, random_state=42).split(pathways)
):
train_pathways = [pathways[i] for i in train]
test_pathways = [pathways[i] for i in test]
@ -3614,3 +3678,53 @@ class Setting(EnviPathModel):
self.public = True
self.global_default = True
self.save()
class JobLogStatus(models.TextChoices):
INITIAL = "INITIAL", "Initial"
SUCCESS = "SUCCESS", "Success"
FAILURE = "FAILURE", "Failure"
REVOKED = "REVOKED", "Revoked"
IGNORED = "IGNORED", "Ignored"
class JobLog(TimeStampedModel):
user = models.ForeignKey("epdb.User", models.CASCADE)
task_id = models.UUIDField(unique=True)
job_name = models.TextField(null=False, blank=False)
status = models.CharField(
max_length=20,
choices=JobLogStatus.choices,
default=JobLogStatus.INITIAL,
)
done_at = models.DateTimeField(null=True, blank=True, default=None)
task_result = models.TextField(null=True, blank=True, default=None)
def check_for_update(self):
async_res = self.get_result()
new_status = async_res.state
TERMINAL_STATES = [
"SUCCESS",
"FAILURE",
"REVOKED",
"IGNORED",
]
if new_status != self.status and new_status in TERMINAL_STATES:
self.status = new_status
self.done_at = async_res.date_done
if new_status == "SUCCESS":
self.task_result = async_res.result
self.save()
return True
return False
def get_result(self):
from celery.result import AsyncResult
return AsyncResult(str(self.task_id))

View File

@ -1,12 +1,58 @@
import csv
import io
import logging
from typing import Optional
from datetime import datetime
from typing import Any, Callable, List, Optional
from uuid import uuid4
from celery import shared_task
from epdb.models import Pathway, Node, EPModel, Setting
from epdb.logic import SPathway
from celery.utils.functional import LRUCache
from epdb.logic import SPathway
from epdb.models import EPModel, JobLog, Node, Package, Pathway, Rule, Setting, User, Edge
logger = logging.getLogger(__name__)
ML_CACHE = LRUCache(3) # Cache the three most recent ML models to reduce load times.
def get_ml_model(model_pk: int):
if model_pk not in ML_CACHE:
ML_CACHE[model_pk] = EPModel.objects.get(id=model_pk)
return ML_CACHE[model_pk]
def dispatch_eager(user: "User", job: Callable, *args, **kwargs):
try:
x = job(*args, **kwargs)
log = JobLog()
log.user = user
log.task_id = uuid4()
log.job_name = job.__name__
log.status = "SUCCESS"
log.done_at = datetime.now()
log.task_result = str(x) if x else None
log.save()
return x
except Exception as e:
logger.exception(e)
raise e
def dispatch(user: "User", job: Callable, *args, **kwargs):
try:
x = job.delay(*args, **kwargs)
log = JobLog()
log.user = user
log.task_id = x.task_id
log.job_name = job.__name__
log.status = "INITIAL"
log.save()
return x.result
except Exception as e:
logger.exception(e)
raise e
@shared_task(queue="background")
@ -16,7 +62,7 @@ def mul(a, b):
@shared_task(queue="predict")
def predict_simple(model_pk: int, smiles: str):
mod = EPModel.objects.get(id=model_pk)
mod = get_ml_model(model_pk)
res = mod.predict(smiles)
return res
@ -26,17 +72,55 @@ def send_registration_mail(user_pk: int):
pass
@shared_task(queue="model")
def build_model(model_pk: int):
@shared_task(bind=True, queue="model")
def build_model(self, model_pk: int):
mod = EPModel.objects.get(id=model_pk)
if JobLog.objects.filter(task_id=self.request.id).exists():
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=mod.url)
try:
mod.build_dataset()
mod.build_model()
except Exception as e:
if JobLog.objects.filter(task_id=self.request.id).exists():
JobLog.objects.filter(task_id=self.request.id).update(
status="FAILED", task_result=mod.url
)
raise e
if JobLog.objects.filter(task_id=self.request.id).exists():
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=mod.url)
return mod.url
@shared_task(queue="model")
def evaluate_model(model_pk: int):
@shared_task(bind=True, queue="model")
def evaluate_model(self, model_pk: int, multigen: bool, package_pks: Optional[list] = None):
packages = None
if package_pks:
packages = Package.objects.filter(pk__in=package_pks)
mod = EPModel.objects.get(id=model_pk)
mod.evaluate_model()
if JobLog.objects.filter(task_id=self.request.id).exists():
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=mod.url)
try:
mod.evaluate_model(multigen, eval_packages=packages)
except Exception as e:
if JobLog.objects.filter(task_id=self.request.id).exists():
JobLog.objects.filter(task_id=self.request.id).update(
status="FAILED", task_result=mod.url
)
raise e
if JobLog.objects.filter(task_id=self.request.id).exists():
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=mod.url)
return mod.url
@shared_task(queue="model")
@ -45,16 +129,26 @@ def retrain(model_pk: int):
mod.retrain()
@shared_task(queue="predict")
@shared_task(bind=True, queue="predict")
def predict(
pw_pk: int, pred_setting_pk: int, limit: Optional[int] = None, node_pk: Optional[int] = None
self,
pw_pk: int,
pred_setting_pk: int,
limit: Optional[int] = None,
node_pk: Optional[int] = None,
) -> Pathway:
pw = Pathway.objects.get(id=pw_pk)
setting = Setting.objects.get(id=pred_setting_pk)
# If the setting has a model add/restore it from the cache
if setting.model is not None:
setting.model = get_ml_model(setting.model.pk)
pw.kv.update(**{"status": "running"})
pw.save()
if JobLog.objects.filter(task_id=self.request.id).exists():
JobLog.objects.filter(task_id=self.request.id).update(status="RUNNING", task_result=pw.url)
try:
# regular prediction
if limit is not None:
@ -79,7 +173,111 @@ def predict(
except Exception as e:
pw.kv.update({"status": "failed"})
pw.save()
if JobLog.objects.filter(task_id=self.request.id).exists():
JobLog.objects.filter(task_id=self.request.id).update(
status="FAILED", task_result=pw.url
)
raise e
pw.kv.update(**{"status": "completed"})
pw.save()
if JobLog.objects.filter(task_id=self.request.id).exists():
JobLog.objects.filter(task_id=self.request.id).update(status="SUCCESS", task_result=pw.url)
return pw.url
@shared_task(bind=True, queue="background")
def identify_missing_rules(
self,
pw_pks: List[int],
rule_package_pk: int,
):
from utilities.misc import PathwayUtils
rules = Package.objects.get(pk=rule_package_pk).get_applicable_rules()
rows: List[Any] = []
header = [
"Package Name",
"Pathway Name",
"Educt Name",
"Educt SMILES",
"Reaction Name",
"Reaction SMIRKS",
"Triggered Rules",
"Reactant SMARTS",
"Product SMARTS",
"Product Names",
"Product SMILES",
]
rows.append(header)
for pw in Pathway.objects.filter(pk__in=pw_pks):
pu = PathwayUtils(pw)
missing_rules = pu.find_missing_rules(rules)
package_name = pw.package.name
pathway_name = pw.name
for edge_url, rule_chain in missing_rules.items():
row: List[Any] = [package_name, pathway_name]
edge = Edge.objects.get(url=edge_url)
educts = edge.start_nodes.all()
for educt in educts:
row.append(educt.default_node_label.name)
row.append(educt.default_node_label.smiles)
row.append(edge.edge_label.name)
row.append(edge.edge_label.smirks())
rule_names = []
reactant_smarts = []
product_smarts = []
for r in rule_chain:
r = Rule.objects.get(url=r[0])
rule_names.append(r.name)
rs = r.reactants_smarts
if isinstance(rs, set):
rs = list(rs)
ps = r.products_smarts
if isinstance(ps, set):
ps = list(ps)
reactant_smarts.append(rs)
product_smarts.append(ps)
row.append(rule_names)
row.append(reactant_smarts)
row.append(product_smarts)
products = edge.end_nodes.all()
product_names = []
product_smiles = []
for product in products:
product_names.append(product.default_node_label.name)
product_smiles.append(product.default_node_label.smiles)
row.append(product_names)
row.append(product_smiles)
rows.append(row)
buffer = io.StringIO()
writer = csv.writer(buffer)
writer.writerows(rows)
buffer.seek(0)
return buffer.getvalue()

View File

@ -1,8 +1,21 @@
from django import template
from pydantic import AnyHttpUrl, ValidationError
from pydantic.type_adapter import TypeAdapter
register = template.Library()
url_adapter = TypeAdapter(AnyHttpUrl)
@register.filter
def classname(obj):
return obj.__class__.__name__
@register.filter
def is_url(value):
try:
url_adapter.validate_python(value)
return True
except ValidationError:
return False

View File

@ -1,5 +1,5 @@
from django.urls import path, re_path
from django.contrib.auth import views as auth_views
from django.urls import path, re_path
from . import views as v
@ -88,20 +88,36 @@ urlpatterns = [
v.package_rule,
name="package rule detail",
),
re_path(
rf"^package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$",
v.package_rule,
name="package rule detail",
),
# re_path(
# rf"^package/(?P<package_uuid>{UUID})/simple-rdkit-rule/(?P<rule_uuid>{UUID})$",
# v.package_rule,
# name="package rule detail",
# ),
re_path(
rf"^package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})$",
v.package_rule,
name="package rule detail",
),
# re_path(
# rf"^package/(?P<package_uuid>{UUID})/sequential-rule/(?P<rule_uuid>{UUID})$",
# v.package_rule,
# name="package rule detail",
# ),
# EnzymeLinks
re_path(
rf"^package/(?P<package_uuid>{UUID})/sequential-rule/(?P<rule_uuid>{UUID})$",
v.package_rule,
name="package rule detail",
rf"^package/(?P<package_uuid>{UUID})/rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
v.package_rule_enzymelink,
name="package rule enzymelink detail",
),
re_path(
rf"^package/(?P<package_uuid>{UUID})/simple-ambit-rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
v.package_rule_enzymelink,
name="package rule enzymelink detail",
),
re_path(
rf"^package/(?P<package_uuid>{UUID})/parallel-rule/(?P<rule_uuid>{UUID})/enzymelink/(?P<enzymelink_uuid>{UUID})$",
v.package_rule_enzymelink,
name="package rule enzymelink detail",
),
# Reaction
re_path(
@ -174,6 +190,7 @@ urlpatterns = [
re_path(r"^indigo/dearomatize$", v.dearomatize, name="indigo_dearomatize"),
re_path(r"^indigo/layout$", v.layout, name="indigo_layout"),
re_path(r"^depict$", v.depict, name="depict"),
re_path(r"^jobs", v.jobs, name="jobs"),
# OAuth Stuff
path("o/userinfo/", v.userinfo, name="oauth_userinfo"),
]

View File

@ -47,6 +47,8 @@ from .models import (
Edge,
ExternalDatabase,
ExternalIdentifier,
EnzymeLink,
JobLog,
)
logger = logging.getLogger(__name__)
@ -239,6 +241,7 @@ def get_base_context(request, for_user=None) -> Dict[str, Any]:
"enabled_features": s.FLAGS,
"debug": s.DEBUG,
"external_databases": ExternalDatabase.get_databases(),
"site_id": s.MATOMO_SITE_ID,
},
}
@ -756,8 +759,8 @@ def package_models(request, package_uuid):
context["unreviewed_objects"] = unreviewed_model_qs
context["model_types"] = {
"ML Relative Reasoning": "ml-relative-reasoning",
"Rule Based Relative Reasoning": "rule-based-relative-reasoning",
"ML Relative Reasoning": "mlrr",
"Rule Based Relative Reasoning": "rbrr",
}
if s.FLAGS.get("ENVIFORMER", False):
@ -776,48 +779,40 @@ def package_models(request, package_uuid):
model_type = request.POST.get("model-type")
if model_type == "enviformer":
threshold = float(request.POST.get(f"{model_type}-threshold", 0.5))
mod = EnviFormer.create(current_package, name, description, threshold)
elif model_type == "ml-relative-reasoning" or model_type == "rule-based-relative-reasoning":
# Generic fields for ML and Rule Based
rule_packages = request.POST.getlist("package-based-relative-reasoning-rule-packages")
data_packages = request.POST.getlist("package-based-relative-reasoning-data-packages")
eval_packages = request.POST.getlist(
"package-based-relative-reasoning-evaluation-packages", []
)
rule_packages = request.POST.getlist("model-rule-packages")
data_packages = request.POST.getlist("model-data-packages")
# Generic params
params = {
"package": current_package,
"name": name,
"description": description,
"rule_packages": [
PackageManager.get_package_by_url(current_user, p) for p in rule_packages
],
"data_packages": [
PackageManager.get_package_by_url(current_user, p) for p in data_packages
],
"eval_packages": [
PackageManager.get_package_by_url(current_user, p) for p in eval_packages
],
}
if model_type == "ml-relative-reasoning":
if model_type == "enviformer":
threshold = float(request.POST.get("model-threshold", 0.5))
params["threshold"] = threshold
mod = EnviFormer.create(**params)
elif model_type == "mlrr":
# ML Specific
threshold = float(request.POST.get(f"{model_type}-threshold", 0.5))
threshold = float(request.POST.get("model-threshold", 0.5))
# TODO handle additional fingerprinter
# fingerprinter = request.POST.get(f"{model_type}-fingerprinter")
# fingerprinter = request.POST.get("model-fingerprinter")
params["rule_packages"] = [
PackageManager.get_package_by_url(current_user, p) for p in rule_packages
]
# App Domain related parameters
build_ad = request.POST.get("build-app-domain", False) == "on"
num_neighbors = request.POST.get("num-neighbors", 5)
reliability_threshold = request.POST.get("reliability-threshold", 0.5)
local_compatibility_threshold = request.POST.get(
"local-compatibility-threshold", 0.5
)
local_compatibility_threshold = request.POST.get("local-compatibility-threshold", 0.5)
params["threshold"] = threshold
# params['fingerprinter'] = fingerprinter
@ -827,18 +822,24 @@ def package_models(request, package_uuid):
params["app_domain_local_compatibility_threshold"] = local_compatibility_threshold
mod = MLRelativeReasoning.create(**params)
else:
elif model_type == "rbrr":
params["rule_packages"] = [
PackageManager.get_package_by_url(current_user, p) for p in rule_packages
]
mod = RuleBasedRelativeReasoning.create(**params)
from .tasks import build_model
build_model.delay(mod.pk)
elif s.FLAGS.get("PLUGINS", False) and model_type in s.CLASSIFIER_PLUGINS.values():
pass
else:
return error(
request, "Invalid model type.", f'Model type "{model_type}" is not supported."'
)
return redirect(mod.url)
from .tasks import dispatch, build_model
dispatch(current_user, build_model, mod.pk)
return redirect(mod.url)
else:
return HttpResponseNotAllowed(["GET", "POST"])
@ -866,6 +867,10 @@ def package_model(request, package_uuid, model_uuid):
return JsonResponse({"error": f'"{smiles}" is not a valid SMILES'}, status=400)
if classify:
from epdb.tasks import dispatch_eager, predict_simple
res = dispatch_eager(current_user, predict_simple, current_model.pk, stand_smiles)
pred_res = current_model.predict(stand_smiles)
res = []
@ -910,9 +915,25 @@ def package_model(request, package_uuid, model_uuid):
current_model.delete()
return redirect(current_package.url + "/model")
elif hidden == "evaluate":
from .tasks import evaluate_model
from .tasks import dispatch, evaluate_model
eval_type = request.POST.get("model-evaluation-type")
if eval_type not in ["sg", "mg"]:
return error(
request,
"Invalid evaluation type",
f'Evaluation type "{eval_type}" is not supported. Only "sg" and "mg" are supported.',
)
multigen = eval_type == "mg"
eval_packages = request.POST.getlist("model-evaluation-packages")
eval_package_ids = [
PackageManager.get_package_by_url(current_user, p).id for p in eval_packages
]
dispatch(current_user, evaluate_model, current_model.pk, multigen, eval_package_ids)
evaluate_model.delay(current_model.pk)
return redirect(current_model.url)
else:
return HttpResponseBadRequest()
@ -1255,7 +1276,16 @@ def package_compound_structures(request, package_uuid, compound_uuid):
structure_smiles = request.POST.get("structure-smiles").strip()
structure_description = request.POST.get("structure-description")
cs = current_compound.add_structure(structure_smiles, structure_name, structure_description)
try:
cs = current_compound.add_structure(
structure_smiles, structure_name, structure_description
)
except ValueError:
return error(
request,
"Adding structure failed!",
"The structure could not be added as normalized structures don't match!",
)
return redirect(cs.url)
@ -1460,12 +1490,20 @@ def package_rule(request, package_uuid, rule_uuid):
logger.info(
f"Rule {current_rule.uuid} returned multiple product sets on {smiles}, picking the first one."
)
smirks = f"{stand_smiles}>>{'.'.join(sorted(res[0]))}"
# Some Rules are touching unrelated areas which might result in ~ indicating
# any bond (-, =, #). For drawing we need a concrete bond. -> use single bond
product_smiles = [x.replace("~", "-") for x in res[0]]
smirks = f"{stand_smiles}>>{'.'.join(sorted(product_smiles))}"
# Usually the functional groups are a mapping of fg -> count
# As we are doing it on the fly here fake a high count to ensure that its properly highlighted
if isinstance(current_rule, SimpleAmbitRule):
educt_functional_groups = {current_rule.reactants_smarts: 1000}
product_functional_groups = {current_rule.products_smarts: 1000}
else:
educt_functional_groups = {x: 1000 for x in current_rule.reactants_smarts}
product_functional_groups = {x: 1000 for x in current_rule.products_smarts}
return HttpResponse(
IndigoUtils.smirks_to_svg(
smirks,
@ -1536,6 +1574,32 @@ def package_rule(request, package_uuid, rule_uuid):
return HttpResponseNotAllowed(["GET", "POST"])
@package_permission_required()
def package_rule_enzymelink(request, package_uuid, rule_uuid, enzymelink_uuid):
current_user = _anonymous_or_real(request)
current_package = PackageManager.get_package_by_id(current_user, package_uuid)
current_rule = Rule.objects.get(package=current_package, uuid=rule_uuid)
current_enzymelink = EnzymeLink.objects.get(rule=current_rule, uuid=enzymelink_uuid)
if request.method == "GET":
context = get_base_context(request)
context["title"] = f"enviPath - {current_package.name} - {current_rule.name}"
context["meta"]["current_package"] = current_package
context["object_type"] = "enzyme"
context["breadcrumbs"] = breadcrumbs(
current_package, "rule", current_rule, "enzymelink", current_enzymelink
)
context["enzymelink"] = current_enzymelink
context["current_object"] = current_enzymelink
return render(request, "objects/enzymelink.html", context)
return HttpResponseNotAllowed(["GET"])
@package_permission_required()
def package_reactions(request, package_uuid):
current_user = _anonymous_or_real(request)
@ -1774,9 +1838,9 @@ def package_pathways(request, package_uuid):
pw.setting = prediction_setting
pw.save()
from .tasks import predict
from .tasks import dispatch, predict
predict.delay(pw.pk, prediction_setting.pk, limit=limit)
dispatch(current_user, predict, pw.pk, prediction_setting.pk, limit=limit)
return redirect(pw.url)
@ -1812,6 +1876,25 @@ def package_pathway(request, package_uuid, pathway_uuid):
return response
if (
request.GET.get("identify-missing-rules", False) == "true"
and request.GET.get("rule-package") is not None
):
from .tasks import dispatch_eager, identify_missing_rules
rule_package = PackageManager.get_package_by_url(
current_user, request.GET.get("rule-package")
)
res = dispatch_eager(
current_user, identify_missing_rules, [current_pathway.pk], rule_package.pk
)
filename = f"{current_pathway.name.replace(' ', '_')}_{current_pathway.uuid}.csv"
response = HttpResponse(res, content_type="text/csv")
response["Content-Disposition"] = f'attachment; filename="{filename}"'
return response
# Pathway d3_json() relies on a lot of related objects (Nodes, Structures, Edges, Reaction, Rules, ...)
# we will again fetch the current pathway identified by this url, but this time together with nearly all
# related objects
@ -1896,10 +1979,16 @@ def package_pathway(request, package_uuid, pathway_uuid):
if node_url:
n = current_pathway.get_node(node_url)
from .tasks import predict
from .tasks import dispatch, predict
dispatch(
current_user,
predict,
current_pathway.pk,
current_pathway.setting.pk,
node_pk=n.pk,
)
# Dont delay?
predict(current_pathway.pk, current_pathway.setting.pk, node_pk=n.pk)
return JsonResponse({"success": current_pathway.url})
return HttpResponseBadRequest()
@ -1976,9 +2065,42 @@ def package_pathway_node(request, package_uuid, pathway_uuid, node_uuid):
if request.method == "GET":
is_image_request = request.GET.get("image")
is_highlight_request = request.GET.get("highlight", False)
is_highlight_reactivity = request.GET.get("highlightReactivity", False)
if is_image_request:
if is_image_request == "svg":
# TODO optimize this chain
if is_highlight_request:
# User functional groups covered by the model training data
fgs = {}
if current_pathway.setting:
if current_pathway.setting.model:
if current_pathway.setting.model.app_domain:
fgs = current_pathway.setting.model.app_domain.functional_groups
svg_data = IndigoUtils.mol_to_svg(
current_node.default_node_label.smiles, functional_groups=fgs
)
elif is_highlight_reactivity:
# Use reactant smarts to show all reaction sites
# set a high count to obtain a strong color
ad_data = current_node.get_app_domain_assessment_data()
fgs = {}
for t in ad_data.get("assessment", {}).get("transformations", []):
r = Rule.objects.get(url=t["rule"]["url"])
if isinstance(r, SimpleAmbitRule):
fgs[r.reactants_smarts] = 1000
else:
for sr in r.srs:
fgs[sr.reactants_smarts] = 1000
svg_data = IndigoUtils.mol_to_svg(
current_node.default_node_label.smiles, functional_groups=fgs
)
else:
svg_data = current_node.as_svg
return HttpResponse(svg_data, content_type="image/svg+xml")
context = get_base_context(request)
@ -2640,6 +2762,24 @@ def setting(request, setting_uuid):
pass
def jobs(request):
current_user = _anonymous_or_real(request)
context = get_base_context(request)
if request.method == "GET":
context["object_type"] = "joblog"
context["breadcrumbs"] = [
{"Home": s.SERVER_URL},
{"Jobs": s.SERVER_URL + "/jobs"},
]
if current_user.is_superuser:
context["jobs"] = JobLog.objects.all().order_by("-created")
else:
context["jobs"] = JobLog.objects.filter(user=current_user).order_by("-created")
return render(request, "collections/joblog.html", context)
###########
# KETCHER #
###########

View File

@ -12,7 +12,6 @@ dependencies = [
"django-ninja>=1.4.1",
"django-oauth-toolkit>=3.0.1",
"django-polymorphic>=4.1.0",
"django-stubs>=5.2.4",
"enviformer",
"envipy-additional-information",
"envipy-ambit>=0.1.0",
@ -34,12 +33,14 @@ dependencies = [
[tool.uv.sources]
enviformer = { git = "ssh://git@git.envipath.com/enviPath/enviformer.git", rev = "v0.1.2" }
envipy-plugins = { git = "ssh://git@git.envipath.com/enviPath/enviPy-plugins.git", rev = "v0.1.0" }
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.4"}
envipy-additional-information = { git = "ssh://git@git.envipath.com/enviPath/enviPy-additional-information.git", rev = "v0.1.7"}
envipy-ambit = { git = "ssh://git@git.envipath.com/enviPath/enviPy-ambit.git" }
[project.optional-dependencies]
ms-login = ["msal>=1.33.0"]
dev = [
"celery-stubs==0.1.3",
"django-stubs>=5.2.4",
"poethepoet>=0.37.0",
"pre-commit>=4.3.0",
"ruff>=0.13.3",

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

488
static/images/uzh-logo.svg Normal file
View File

@ -0,0 +1,488 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1 Tiny//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11-tiny.dtd">
<svg version="1.1" baseProfile="tiny" id="Universität_Zürich"
xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="-0.499 -0.501 142.73 49.19" xml:space="preserve">
<path d="M6.818,32.721C6.296,32.944,6.2,32.986,6.093,33.04c-0.113,0.058-0.156,0.108-0.133,0.231
c0.005,0.029,0.026,0.091,0.047,0.139c0.017,0.038,0.019,0.062-0.006,0.071c-0.023,0.011-0.041-0.01-0.062-0.062
c-0.078-0.182-0.161-0.401-0.205-0.506c-0.035-0.082-0.138-0.295-0.191-0.418c-0.021-0.052-0.024-0.079-0.001-0.089
c0.024-0.011,0.04,0.007,0.056,0.042c0.016,0.037,0.027,0.057,0.053,0.095c0.057,0.086,0.127,0.088,0.25,0.043
c0.113-0.04,0.209-0.081,0.731-0.305l0.481-0.206c0.499-0.215,0.666-0.404,0.729-0.631c0.062-0.21,0.008-0.373-0.043-0.49
c-0.063-0.151-0.191-0.315-0.389-0.395c-0.27-0.108-0.584,0.014-0.938,0.166l-0.431,0.184c-0.521,0.225-0.619,0.266-0.726,0.319
c-0.114,0.058-0.157,0.107-0.132,0.23c0.005,0.031,0.025,0.091,0.042,0.129c0.016,0.037,0.018,0.062-0.006,0.071
c-0.024,0.011-0.041-0.011-0.062-0.06C5.083,31.425,5.001,31.204,5,31.201c-0.018-0.042-0.121-0.254-0.178-0.387
c-0.021-0.048-0.024-0.075,0-0.085c0.024-0.012,0.04,0.007,0.057,0.048c0.017,0.038,0.029,0.058,0.054,0.096
c0.057,0.085,0.127,0.088,0.25,0.043c0.112-0.04,0.209-0.082,0.73-0.306l0.368-0.157c0.382-0.164,0.803-0.3,1.175-0.117
c0.314,0.154,0.458,0.387,0.554,0.608c0.078,0.183,0.21,0.517,0.091,0.865c-0.083,0.242-0.281,0.481-0.78,0.695L6.818,32.721z
M6.676,28.899c0.283-0.069,0.368-0.139,0.378-0.217c0.008-0.066-0.004-0.138-0.017-0.195c-0.009-0.04-0.007-0.063,0.017-0.068
c0.028-0.006,0.043,0.025,0.055,0.076c0.052,0.237,0.073,0.386,0.088,0.455c0.007,0.033,0.056,0.202,0.098,0.393
c0.011,0.048,0.015,0.081-0.019,0.089c-0.022,0.005-0.034-0.017-0.042-0.053c-0.01-0.048-0.028-0.111-0.054-0.16
c-0.051-0.089-0.159-0.079-0.479-0.015l-2.169,0.442c-0.073,0.016-0.127,0.016-0.135-0.021c-0.009-0.04,0.031-0.083,0.083-0.16
c0.038-0.054,0.511-0.739,0.924-1.39c0.194-0.303,0.612-0.911,0.657-0.986l-0.004-0.018l-1.633,0.396
c-0.222,0.053-0.283,0.102-0.301,0.196c-0.01,0.061,0.009,0.146,0.021,0.198c0.01,0.044,0.002,0.061-0.021,0.064
c-0.028,0.008-0.042-0.032-0.054-0.088C4.028,27.651,4,27.474,3.984,27.397c-0.009-0.041-0.05-0.178-0.09-0.356
c-0.011-0.048-0.016-0.085,0.015-0.091c0.022-0.005,0.038,0.015,0.047,0.059c0.008,0.036,0.015,0.065,0.035,0.106
c0.051,0.097,0.135,0.108,0.34,0.067l2.313-0.467c0.08-0.018,0.115-0.01,0.124,0.022c0.009,0.041-0.021,0.094-0.054,0.143
c-0.168,0.277-0.544,0.852-0.837,1.312c-0.308,0.484-0.675,0.99-0.729,1.071l0.002,0.011L6.676,28.899z M4.871,25.556
c-0.566,0.035-0.672,0.042-0.791,0.058c-0.126,0.016-0.188,0.057-0.201,0.14C3.87,25.796,3.87,25.844,3.873,25.89
c0.002,0.036-0.004,0.06-0.034,0.062c-0.021,0.002-0.031-0.027-0.035-0.087c-0.01-0.142-0.014-0.378-0.021-0.486
c-0.006-0.093-0.031-0.312-0.04-0.455c-0.003-0.048,0.003-0.079,0.025-0.081c0.029-0.002,0.039,0.021,0.041,0.059
c0.002,0.037,0.008,0.066,0.018,0.111c0.025,0.1,0.09,0.126,0.222,0.123c0.12,0,0.225-0.006,0.792-0.042l0.657-0.042
c0.362-0.021,0.657-0.041,0.816-0.065c0.1-0.019,0.166-0.048,0.173-0.161c0.004-0.053,0.006-0.135,0.003-0.191
c-0.002-0.042,0.007-0.061,0.026-0.062c0.025-0.001,0.039,0.028,0.042,0.069c0.016,0.246,0.02,0.48,0.025,0.583
c0.005,0.086,0.031,0.319,0.041,0.47c0.003,0.048-0.006,0.075-0.033,0.076c-0.019,0.002-0.03-0.014-0.033-0.059
c-0.003-0.056-0.015-0.1-0.023-0.133c-0.02-0.074-0.084-0.093-0.193-0.097c-0.157-0.009-0.452,0.01-0.814,0.032L4.871,25.556z
M4.071,23.877c-0.21,0.073-0.256,0.134-0.302,0.267c-0.02,0.055-0.022,0.114-0.024,0.145c-0.002,0.034-0.014,0.044-0.037,0.043
c-0.029-0.002-0.031-0.043-0.026-0.099c0.013-0.198,0.038-0.41,0.045-0.541c0.006-0.093,0.006-0.273,0.019-0.46
c0.003-0.045,0.013-0.085,0.039-0.084c0.026,0.002,0.032,0.024,0.03,0.059c-0.003,0.061-0.003,0.116,0.017,0.147
c0.018,0.026,0.042,0.041,0.076,0.042c0.049,0.003,0.155-0.024,0.293-0.067l1.736-0.534l0.001-0.015
c-0.4-0.188-1.571-0.749-1.812-0.854c-0.047-0.021-0.102-0.041-0.136-0.042c-0.03-0.002-0.061,0.012-0.074,0.044
c-0.018,0.045-0.024,0.101-0.027,0.148c-0.003,0.034-0.009,0.063-0.034,0.062c-0.03-0.002-0.035-0.036-0.031-0.106
c0.012-0.188,0.033-0.343,0.036-0.391c0.004-0.063,0.004-0.24,0.011-0.353c0.003-0.048,0.013-0.078,0.039-0.076
c0.026,0.001,0.032,0.024,0.029,0.062C3.935,21.311,3.93,21.389,3.97,21.46C4,21.51,4.06,21.571,4.293,21.687
c0.342,0.168,0.537,0.281,0.983,0.513c0.53,0.274,0.926,0.476,1.106,0.569c0.21,0.11,0.27,0.137,0.266,0.188
c-0.003,0.048-0.058,0.067-0.236,0.127L4.071,23.877z M5.128,20.567c-0.556-0.12-0.657-0.143-0.776-0.16
c-0.126-0.019-0.191-0.002-0.242,0.113c-0.014,0.026-0.031,0.089-0.042,0.14c-0.009,0.041-0.021,0.062-0.046,0.055
c-0.026-0.006-0.027-0.032-0.017-0.087c0.021-0.099,0.047-0.204,0.067-0.295c0.024-0.095,0.045-0.178,0.057-0.229
c0.025-0.117,0.183-0.846,0.193-0.916c0.007-0.071,0.013-0.131,0.012-0.162c0-0.02-0.006-0.043-0.002-0.062
c0.004-0.018,0.02-0.019,0.038-0.015c0.025,0.005,0.065,0.033,0.231,0.08c0.036,0.012,0.194,0.054,0.236,0.07
c0.019,0.008,0.038,0.02,0.032,0.045c-0.005,0.024-0.024,0.028-0.058,0.021C4.787,19.16,4.724,19.15,4.676,19.16
c-0.07,0.011-0.122,0.039-0.185,0.216c-0.021,0.062-0.11,0.443-0.126,0.518c-0.004,0.019,0.005,0.027,0.031,0.032l0.925,0.199
c0.025,0.005,0.041,0.005,0.045-0.017c0.018-0.081,0.108-0.501,0.119-0.587c0.011-0.089,0.012-0.146-0.018-0.188
c-0.023-0.032-0.038-0.051-0.034-0.068c0.003-0.016,0.013-0.024,0.034-0.021c0.022,0.005,0.078,0.032,0.262,0.087
c0.072,0.02,0.216,0.062,0.242,0.067C6,19.405,6.04,19.414,6.033,19.447c-0.005,0.026-0.021,0.03-0.04,0.026
c-0.037-0.005-0.084-0.015-0.135-0.014c-0.077,0.002-0.144,0.042-0.188,0.174c-0.021,0.068-0.104,0.43-0.123,0.518
c-0.004,0.018,0.011,0.024,0.032,0.03l0.289,0.062c0.125,0.027,0.46,0.104,0.567,0.122c0.254,0.047,0.32,0,0.401-0.373
c0.021-0.095,0.054-0.249,0.03-0.353c-0.022-0.104-0.091-0.165-0.235-0.224C6.594,19.4,6.581,19.39,6.587,19.364
c0.008-0.029,0.036-0.023,0.072-0.015c0.084,0.018,0.327,0.101,0.396,0.135c0.089,0.046,0.083,0.079,0.052,0.218
c-0.06,0.274-0.109,0.474-0.146,0.63c-0.041,0.156-0.068,0.27-0.093,0.378c-0.009,0.04-0.026,0.121-0.041,0.209
C6.808,21.003,6.796,21.1,6.78,21.173c-0.01,0.049-0.026,0.071-0.052,0.065c-0.019-0.004-0.026-0.021-0.018-0.065
c0.013-0.055,0.015-0.1,0.015-0.135c0.001-0.076-0.075-0.112-0.179-0.148c-0.149-0.052-0.438-0.113-0.774-0.187L5.128,20.567z
M5.822,17.875c-0.53-0.205-0.628-0.243-0.743-0.278c-0.121-0.039-0.188-0.032-0.257,0.072c-0.018,0.025-0.044,0.083-0.063,0.132
c-0.015,0.038-0.029,0.058-0.054,0.048c-0.023-0.01-0.021-0.037-0.002-0.09c0.072-0.185,0.167-0.399,0.195-0.474
c0.046-0.118,0.138-0.388,0.18-0.496c0.085-0.22,0.196-0.446,0.398-0.584c0.104-0.072,0.336-0.143,0.569-0.052
c0.259,0.1,0.454,0.299,0.604,0.763c0.511-0.16,0.915-0.28,1.21-0.401c0.278-0.117,0.357-0.251,0.389-0.3
c0.021-0.035,0.038-0.065,0.048-0.094c0.011-0.028,0.026-0.038,0.044-0.031c0.028,0.012,0.025,0.038,0.01,0.08l-0.128,0.332
c-0.075,0.195-0.126,0.276-0.21,0.349c-0.139,0.118-0.354,0.188-0.698,0.279c-0.246,0.065-0.545,0.135-0.615,0.159
c-0.027,0.009-0.039,0.029-0.048,0.053l-0.125,0.302c-0.007,0.018-0.004,0.03,0.017,0.039l0.049,0.019
c0.325,0.125,0.601,0.232,0.754,0.271C7.45,18,7.535,18.009,7.59,17.91c0.027-0.05,0.064-0.124,0.08-0.166
c0.012-0.028,0.027-0.038,0.044-0.031c0.024,0.011,0.025,0.038,0.009,0.083c-0.079,0.202-0.188,0.457-0.209,0.51
c-0.025,0.065-0.101,0.289-0.154,0.43C7.341,18.779,7.321,18.8,7.297,18.79c-0.018-0.007-0.021-0.023-0.006-0.065
c0.021-0.054,0.029-0.098,0.035-0.132c0.013-0.074-0.057-0.123-0.153-0.176c-0.14-0.074-0.415-0.181-0.735-0.305L5.822,17.875z
M6.257,17.57c0.038,0.015,0.056,0.013,0.075-0.007c0.053-0.063,0.104-0.164,0.138-0.251c0.054-0.141,0.058-0.19,0.036-0.271
c-0.035-0.134-0.157-0.298-0.443-0.408c-0.495-0.191-0.766,0.081-0.846,0.287c-0.033,0.087-0.055,0.151-0.058,0.19
c-0.002,0.027,0.009,0.04,0.037,0.05L6.257,17.57z M8.459,16.014c-0.053,0.038-0.074,0.04-0.146-0.001
c-0.18-0.103-0.367-0.227-0.417-0.259c-0.047-0.03-0.077-0.061-0.062-0.087c0.018-0.028,0.048-0.016,0.074-0.001
c0.042,0.024,0.118,0.05,0.183,0.064c0.281,0.064,0.479-0.077,0.594-0.278c0.166-0.293,0.049-0.549-0.123-0.646
C8.4,14.716,8.223,14.68,7.858,14.83l-0.202,0.083c-0.483,0.199-0.781,0.193-1.044,0.044c-0.357-0.204-0.445-0.647-0.188-1.101
c0.12-0.212,0.23-0.33,0.302-0.401c0.022-0.024,0.042-0.036,0.064-0.022c0.042,0.023,0.129,0.091,0.383,0.233
c0.072,0.041,0.093,0.065,0.078,0.091c-0.013,0.023-0.038,0.021-0.077,0c-0.028-0.017-0.139-0.058-0.263-0.038
c-0.089,0.015-0.241,0.054-0.361,0.265C6.413,14.224,6.47,14.45,6.665,14.56c0.15,0.085,0.307,0.074,0.664-0.079l0.12-0.052
c0.521-0.227,0.823-0.238,1.133-0.062c0.188,0.107,0.372,0.306,0.393,0.627c0.012,0.222-0.062,0.421-0.16,0.593
C8.706,15.776,8.6,15.909,8.459,16.014z M8.373,12.925c-0.459-0.335-0.544-0.396-0.645-0.462c-0.107-0.067-0.182-0.075-0.244-0.021
c-0.034,0.026-0.065,0.063-0.091,0.1c-0.022,0.03-0.042,0.044-0.066,0.026c-0.018-0.013-0.006-0.042,0.029-0.09
c0.084-0.115,0.231-0.299,0.296-0.387c0.055-0.075,0.176-0.26,0.26-0.375c0.029-0.039,0.052-0.059,0.07-0.046
c0.023,0.019,0.017,0.041-0.005,0.071c-0.022,0.03-0.037,0.057-0.058,0.097c-0.044,0.094-0.011,0.154,0.092,0.237
c0.092,0.077,0.177,0.139,0.636,0.474l0.532,0.389c0.293,0.214,0.531,0.388,0.671,0.471c0.088,0.05,0.157,0.069,0.235-0.013
c0.037-0.038,0.093-0.101,0.125-0.146c0.024-0.033,0.044-0.042,0.061-0.031c0.021,0.017,0.012,0.047-0.013,0.08
c-0.146,0.199-0.294,0.384-0.354,0.466c-0.051,0.068-0.18,0.267-0.269,0.387c-0.029,0.04-0.054,0.054-0.074,0.039
c-0.016-0.011-0.017-0.03,0.011-0.066c0.033-0.045,0.053-0.086,0.067-0.118c0.031-0.068-0.007-0.125-0.087-0.197
c-0.116-0.108-0.354-0.282-0.648-0.496L8.373,12.925z M9.295,10.406l-0.372,0.415c-0.144,0.163-0.19,0.241-0.17,0.338
c0.016,0.065,0.035,0.11,0.052,0.136c0.017,0.026,0.02,0.044,0.004,0.062c-0.019,0.019-0.037,0.012-0.066-0.018
c-0.044-0.041-0.242-0.326-0.259-0.353c-0.028-0.042-0.036-0.065-0.021-0.081c0.021-0.022,0.073-0.023,0.144-0.086
c0.083-0.071,0.186-0.169,0.271-0.259l1.018-1.076c0.083-0.087,0.131-0.154,0.164-0.2c0.029-0.049,0.045-0.076,0.056-0.086
c0.018-0.019,0.037-0.006,0.078,0.032c0.057,0.054,0.237,0.238,0.308,0.306c0.024,0.028,0.036,0.049,0.021,0.065
c-0.021,0.021-0.039,0.014-0.083-0.021l-0.032-0.024c-0.076-0.062-0.221-0.061-0.456,0.177l-0.332,0.335l1.115,1.053
c0.25,0.236,0.465,0.439,0.595,0.536c0.083,0.062,0.159,0.104,0.247,0.032c0.042-0.033,0.104-0.087,0.142-0.128
c0.028-0.03,0.05-0.036,0.062-0.023c0.02,0.019,0.007,0.047-0.021,0.077c-0.169,0.18-0.339,0.343-0.41,0.419
c-0.06,0.062-0.214,0.242-0.315,0.351c-0.033,0.035-0.06,0.047-0.079,0.028c-0.014-0.013-0.012-0.031,0.02-0.063
c0.038-0.041,0.063-0.078,0.081-0.108c0.04-0.065-0.004-0.138-0.074-0.22c-0.103-0.122-0.316-0.324-0.566-0.562L9.295,10.406z
M12.134,9.947c-0.015,0.012-0.016,0.021-0.008,0.044l0.176,0.544c0.029,0.096,0.07,0.178,0.099,0.213
c0.042,0.053,0.099,0.069,0.188-0.003l0.044-0.035c0.035-0.027,0.049-0.028,0.062-0.012c0.019,0.023,0.006,0.043-0.026,0.069
c-0.094,0.074-0.227,0.166-0.316,0.237c-0.032,0.026-0.187,0.163-0.338,0.284c-0.038,0.03-0.061,0.039-0.079,0.016
c-0.015-0.019-0.007-0.033,0.017-0.052c0.026-0.021,0.065-0.057,0.086-0.078c0.121-0.125,0.097-0.269,0.039-0.461l-0.73-2.421
c-0.032-0.112-0.041-0.159-0.012-0.182c0.026-0.021,0.065-0.009,0.147,0.035c0.199,0.104,1.618,0.924,2.159,1.22
c0.32,0.174,0.438,0.148,0.512,0.113c0.051-0.026,0.097-0.059,0.132-0.086c0.023-0.02,0.041-0.027,0.057-0.008
c0.02,0.023-0.003,0.051-0.11,0.138c-0.105,0.084-0.319,0.254-0.558,0.435c-0.055,0.039-0.09,0.067-0.105,0.047
c-0.014-0.018-0.007-0.032,0.02-0.059c0.017-0.022,0.016-0.065-0.026-0.088l-0.729-0.434c-0.017-0.01-0.031-0.009-0.045,0.003
L12.134,9.947z M12.614,9.325c0.014-0.012,0.01-0.022,0-0.029l-0.839-0.513c-0.012-0.009-0.027-0.021-0.036-0.015
c-0.009,0.007-0.003,0.025,0.003,0.041l0.305,0.934c0.008,0.014,0.018,0.021,0.028,0.011L12.614,9.325z M14.557,9.301
c-0.064,0.009-0.084-0.002-0.126-0.072c-0.104-0.178-0.206-0.378-0.233-0.432c-0.025-0.05-0.037-0.091-0.011-0.105
c0.028-0.018,0.049,0.01,0.063,0.036c0.024,0.042,0.078,0.102,0.126,0.146c0.212,0.196,0.453,0.17,0.652,0.052
c0.29-0.172,0.315-0.451,0.214-0.623c-0.093-0.156-0.229-0.276-0.62-0.327l-0.217-0.028c-0.518-0.067-0.773-0.22-0.928-0.48
c-0.21-0.354-0.064-0.782,0.383-1.048c0.209-0.124,0.363-0.172,0.46-0.199c0.033-0.011,0.055-0.011,0.068,0.013
c0.024,0.042,0.066,0.143,0.216,0.394c0.042,0.071,0.047,0.104,0.021,0.118c-0.021,0.013-0.043,0-0.065-0.039
c-0.018-0.029-0.093-0.119-0.209-0.163c-0.084-0.032-0.235-0.074-0.444,0.05c-0.238,0.142-0.301,0.365-0.187,0.559
c0.088,0.147,0.229,0.217,0.614,0.261l0.13,0.014c0.562,0.062,0.833,0.202,1.015,0.508c0.109,0.187,0.171,0.45,0.028,0.738
c-0.1,0.198-0.263,0.333-0.433,0.436C14.89,9.217,14.731,9.28,14.557,9.301z M34.089,6.584L33.6,6.314
c-0.191-0.104-0.278-0.133-0.368-0.09c-0.062,0.028-0.101,0.059-0.121,0.081c-0.022,0.021-0.039,0.028-0.06,0.018
c-0.021-0.013-0.02-0.033,0.002-0.069c0.029-0.051,0.264-0.309,0.285-0.332c0.034-0.036,0.056-0.05,0.075-0.038
c0.026,0.016,0.039,0.066,0.116,0.12c0.088,0.064,0.207,0.144,0.312,0.205l1.278,0.75c0.104,0.062,0.18,0.093,0.232,0.115
c0.054,0.019,0.084,0.027,0.097,0.035c0.022,0.014,0.015,0.035-0.014,0.083c-0.04,0.067-0.181,0.285-0.229,0.369
c-0.021,0.03-0.04,0.046-0.06,0.035c-0.025-0.016-0.022-0.035,0.003-0.086l0.018-0.036c0.042-0.089,0.008-0.229-0.274-0.404
l-0.401-0.25l-0.776,1.323c-0.174,0.297-0.323,0.552-0.39,0.699c-0.043,0.097-0.064,0.179,0.024,0.248
c0.042,0.033,0.108,0.081,0.156,0.109c0.036,0.021,0.047,0.04,0.037,0.056c-0.013,0.022-0.044,0.018-0.08-0.004
C33.25,9.129,33.053,9,32.962,8.947c-0.073-0.043-0.282-0.153-0.412-0.229c-0.042-0.023-0.059-0.048-0.046-0.069
c0.01-0.017,0.028-0.019,0.067,0.004c0.048,0.029,0.091,0.045,0.124,0.055c0.072,0.025,0.133-0.034,0.197-0.122
c0.096-0.126,0.245-0.381,0.42-0.678L34.089,6.584z M35.774,8.711c0.371-0.431,0.438-0.511,0.511-0.605
c0.077-0.102,0.093-0.166,0.017-0.267c-0.018-0.024-0.062-0.069-0.104-0.104c-0.031-0.027-0.043-0.047-0.026-0.067
c0.018-0.02,0.042-0.009,0.084,0.028c0.15,0.129,0.322,0.291,0.407,0.364c0.068,0.058,0.254,0.204,0.356,0.291
c0.042,0.037,0.058,0.059,0.04,0.079c-0.018,0.02-0.039,0.011-0.067-0.014c-0.031-0.026-0.051-0.039-0.09-0.062
c-0.089-0.053-0.153-0.022-0.244,0.071c-0.084,0.087-0.152,0.166-0.521,0.598l-0.34,0.396c-0.353,0.412-0.419,0.655-0.378,0.889
c0.038,0.214,0.158,0.338,0.255,0.421c0.125,0.106,0.312,0.198,0.523,0.183c0.29-0.021,0.518-0.271,0.769-0.562l0.306-0.354
c0.37-0.432,0.438-0.511,0.511-0.605c0.077-0.103,0.093-0.167,0.017-0.268c-0.018-0.024-0.062-0.068-0.095-0.097
C37.673,9,37.661,8.979,37.678,8.96c0.017-0.02,0.042-0.009,0.08,0.025c0.146,0.124,0.316,0.286,0.319,0.289
c0.034,0.028,0.22,0.174,0.331,0.269c0.04,0.034,0.055,0.058,0.038,0.077c-0.017,0.021-0.039,0.011-0.073-0.018
c-0.031-0.027-0.05-0.039-0.089-0.062c-0.089-0.052-0.153-0.022-0.244,0.071c-0.083,0.086-0.151,0.166-0.521,0.597l-0.262,0.303
c-0.27,0.315-0.589,0.623-1.002,0.623c-0.35,0-0.581-0.144-0.766-0.302c-0.15-0.129-0.416-0.372-0.462-0.737
c-0.033-0.254,0.04-0.557,0.393-0.969L35.774,8.711z M38.61,11.381c0.443-0.355,0.523-0.422,0.613-0.503
c0.094-0.085,0.122-0.146,0.064-0.259c-0.013-0.028-0.05-0.08-0.083-0.121c-0.025-0.032-0.034-0.054-0.014-0.071
c0.021-0.016,0.043,0,0.078,0.043c0.125,0.154,0.263,0.346,0.312,0.406c0.079,0.1,0.268,0.313,0.339,0.404
c0.148,0.184,0.288,0.393,0.308,0.637c0.01,0.126-0.045,0.362-0.24,0.521c-0.216,0.173-0.486,0.242-0.962,0.141
c-0.117,0.522-0.215,0.932-0.257,1.249c-0.038,0.299,0.038,0.436,0.063,0.485c0.021,0.037,0.038,0.064,0.058,0.089
c0.019,0.022,0.02,0.042,0.005,0.054c-0.023,0.02-0.045,0.003-0.074-0.032l-0.223-0.275c-0.132-0.163-0.176-0.248-0.195-0.356
c-0.034-0.179,0.014-0.399,0.106-0.744c0.066-0.246,0.156-0.539,0.171-0.611c0.006-0.029-0.007-0.05-0.022-0.069l-0.198-0.258
c-0.012-0.016-0.023-0.02-0.042-0.005l-0.041,0.032c-0.271,0.219-0.501,0.403-0.611,0.517c-0.077,0.077-0.127,0.146-0.068,0.243
c0.029,0.048,0.075,0.117,0.103,0.151c0.02,0.023,0.021,0.042,0.006,0.055c-0.021,0.016-0.046,0.003-0.076-0.035
c-0.137-0.169-0.301-0.393-0.336-0.437c-0.044-0.055-0.201-0.23-0.294-0.348c-0.031-0.038-0.038-0.065-0.019-0.082
c0.016-0.012,0.033-0.007,0.062,0.028c0.034,0.043,0.068,0.073,0.096,0.096c0.059,0.049,0.135,0.012,0.229-0.044
c0.135-0.084,0.364-0.271,0.633-0.485L38.61,11.381z M38.656,11.91c-0.032,0.026-0.039,0.041-0.031,0.069
c0.029,0.078,0.09,0.172,0.149,0.245c0.094,0.116,0.136,0.145,0.216,0.167c0.134,0.035,0.336,0.013,0.575-0.181
c0.414-0.333,0.312-0.703,0.174-0.875c-0.059-0.072-0.104-0.123-0.136-0.145c-0.022-0.017-0.038-0.012-0.062,0.006L38.656,11.91z
M40.591,14.113c0.49-0.289,0.58-0.342,0.68-0.409c0.104-0.071,0.142-0.136,0.116-0.216c-0.012-0.041-0.033-0.084-0.056-0.123
c-0.02-0.032-0.023-0.056,0.001-0.071c0.021-0.011,0.041,0.012,0.072,0.062c0.072,0.123,0.182,0.331,0.237,0.424
c0.047,0.081,0.169,0.265,0.241,0.387c0.024,0.042,0.033,0.072,0.015,0.083c-0.025,0.016-0.043,0-0.062-0.033
c-0.019-0.032-0.038-0.056-0.065-0.091c-0.067-0.078-0.139-0.07-0.254-0.011c-0.107,0.054-0.197,0.108-0.688,0.396l-0.566,0.334
c-0.312,0.185-0.567,0.335-0.698,0.43c-0.081,0.061-0.126,0.118-0.082,0.222c0.021,0.049,0.056,0.123,0.084,0.172
c0.021,0.035,0.021,0.058,0.004,0.066c-0.022,0.014-0.047-0.007-0.068-0.042c-0.125-0.212-0.234-0.421-0.287-0.508
c-0.043-0.074-0.173-0.271-0.249-0.4c-0.024-0.042-0.027-0.07-0.006-0.083c0.017-0.009,0.033-0.002,0.057,0.036
c0.028,0.049,0.058,0.083,0.082,0.109c0.051,0.057,0.116,0.043,0.216-0.002c0.145-0.063,0.398-0.214,0.712-0.397L40.591,14.113z
M40.314,16.493c0.157-0.461,0.507-0.711,0.842-0.85c0.235-0.098,0.672-0.193,1.114,0.013c0.331,0.154,0.604,0.425,0.817,0.94
c0.089,0.214,0.128,0.348,0.174,0.515c0.036,0.139,0.052,0.262,0.084,0.37c0.012,0.039,0.001,0.06-0.021,0.068
c-0.028,0.012-0.073,0.019-0.197,0.062c-0.116,0.041-0.306,0.123-0.378,0.144c-0.053,0.019-0.084,0.022-0.097-0.008
c-0.011-0.028,0.014-0.046,0.062-0.065c0.105-0.048,0.208-0.144,0.263-0.267c0.073-0.164,0.056-0.472-0.076-0.79
c-0.125-0.3-0.282-0.466-0.483-0.561c-0.335-0.156-0.69-0.086-1.025,0.053c-0.822,0.34-0.972,1.134-0.78,1.597
c0.127,0.309,0.239,0.48,0.455,0.554c0.09,0.03,0.209,0.034,0.275,0.022c0.061-0.012,0.076-0.011,0.089,0.017
c0.01,0.023-0.012,0.042-0.039,0.053c-0.041,0.017-0.359,0.1-0.491,0.114c-0.066,0.007-0.089,0-0.137-0.05
c-0.113-0.114-0.246-0.367-0.338-0.589C40.232,17.361,40.171,16.92,40.314,16.493z M42.775,19.295
c0.555-0.125,0.657-0.146,0.771-0.181c0.123-0.035,0.175-0.077,0.174-0.204c0-0.031-0.01-0.094-0.021-0.145
c-0.009-0.04-0.007-0.064,0.019-0.069c0.026-0.006,0.039,0.018,0.052,0.073c0.021,0.099,0.042,0.205,0.062,0.296
c0.019,0.096,0.033,0.181,0.044,0.23c0.026,0.117,0.189,0.844,0.208,0.912c0.023,0.068,0.043,0.125,0.058,0.152
c0.008,0.018,0.023,0.037,0.027,0.056s-0.01,0.025-0.027,0.029c-0.025,0.005-0.073-0.003-0.244,0.023
c-0.038,0.005-0.2,0.033-0.245,0.036c-0.02,0-0.042-0.002-0.048-0.026c-0.006-0.025,0.011-0.037,0.043-0.044
c0.025-0.005,0.087-0.023,0.126-0.052c0.06-0.041,0.096-0.087,0.077-0.273c-0.006-0.063-0.085-0.448-0.103-0.521
c-0.004-0.018-0.017-0.022-0.042-0.017l-0.923,0.208c-0.026,0.006-0.04,0.013-0.035,0.034c0.019,0.081,0.112,0.5,0.14,0.583
c0.026,0.086,0.05,0.138,0.094,0.163c0.035,0.019,0.056,0.028,0.061,0.047c0.003,0.015-0.001,0.026-0.022,0.032
c-0.022,0.005-0.083,0.003-0.273,0.03c-0.074,0.014-0.223,0.035-0.248,0.041c-0.028,0.006-0.068,0.017-0.077-0.018
c-0.006-0.025,0.008-0.036,0.025-0.04c0.036-0.012,0.083-0.022,0.128-0.044c0.069-0.035,0.112-0.099,0.096-0.236
c-0.008-0.071-0.086-0.434-0.104-0.521c-0.004-0.019-0.021-0.02-0.042-0.015l-0.288,0.065c-0.124,0.028-0.461,0.1-0.566,0.127
c-0.25,0.062-0.292,0.134-0.208,0.507c0.021,0.095,0.057,0.248,0.121,0.333c0.065,0.085,0.152,0.112,0.308,0.104
c0.042-0.001,0.058,0.002,0.063,0.027c0.006,0.029-0.022,0.036-0.06,0.044c-0.084,0.02-0.338,0.045-0.416,0.043
c-0.102-0.004-0.108-0.037-0.14-0.176c-0.062-0.272-0.099-0.476-0.131-0.634c-0.027-0.158-0.05-0.272-0.074-0.382
c-0.009-0.04-0.026-0.121-0.05-0.207c-0.019-0.084-0.047-0.177-0.063-0.25c-0.012-0.047-0.006-0.075,0.02-0.081
c0.019-0.004,0.033,0.008,0.042,0.052c0.012,0.055,0.029,0.097,0.044,0.128c0.031,0.069,0.115,0.07,0.226,0.062
c0.156-0.017,0.445-0.081,0.78-0.156L42.775,19.295z M42.087,22.487c-0.292,0.018-0.387,0.07-0.41,0.146
c-0.021,0.064-0.021,0.137-0.02,0.195c0.001,0.041-0.005,0.064-0.027,0.064c-0.029,0.001-0.039-0.032-0.041-0.084
c-0.009-0.243-0.003-0.394-0.005-0.465c-0.001-0.033-0.019-0.209-0.024-0.402c-0.002-0.048,0.001-0.083,0.033-0.083
c0.022-0.001,0.031,0.021,0.032,0.059c0.002,0.049,0.008,0.116,0.024,0.167c0.034,0.097,0.143,0.107,0.468,0.101l2.214-0.044
c0.075-0.003,0.128,0.007,0.129,0.044c0.001,0.041-0.046,0.075-0.11,0.143c-0.048,0.046-0.637,0.636-1.159,1.201
c-0.245,0.263-0.767,0.787-0.823,0.852v0.02l1.677-0.098c0.229-0.012,0.298-0.048,0.332-0.139c0.021-0.057,0.018-0.146,0.016-0.199
c-0.001-0.045,0.009-0.061,0.031-0.061c0.03-0.001,0.035,0.04,0.037,0.095c0.007,0.194,0.002,0.375,0.005,0.453
c0.001,0.041,0.019,0.183,0.024,0.366c0.001,0.049,0,0.086-0.031,0.087c-0.021,0.001-0.034-0.021-0.036-0.066
c-0.001-0.038-0.002-0.066-0.015-0.111c-0.034-0.104-0.113-0.132-0.323-0.127l-2.359,0.042c-0.082,0.003-0.116-0.011-0.118-0.044
c-0.001-0.041,0.038-0.088,0.078-0.13c0.216-0.243,0.688-0.738,1.06-1.142c0.39-0.421,0.842-0.853,0.911-0.923v-0.011L42.087,22.487
z M41.585,25.938c0.035-0.057,0.056-0.064,0.137-0.054c0.203,0.03,0.424,0.076,0.481,0.09c0.055,0.012,0.094,0.027,0.09,0.058
c-0.004,0.034-0.038,0.032-0.067,0.028c-0.049-0.008-0.128-0.003-0.194,0.006c-0.286,0.042-0.417,0.246-0.45,0.476
c-0.048,0.333,0.154,0.528,0.352,0.558c0.182,0.025,0.359-0.006,0.645-0.277l0.158-0.151c0.377-0.36,0.656-0.465,0.956-0.422
c0.407,0.06,0.649,0.441,0.577,0.956c-0.035,0.24-0.095,0.392-0.135,0.483c-0.012,0.031-0.025,0.049-0.052,0.045
c-0.048-0.007-0.152-0.037-0.441-0.078c-0.082-0.012-0.109-0.027-0.105-0.057c0.004-0.026,0.027-0.034,0.072-0.027
c0.033,0.004,0.149,0.002,0.258-0.062c0.078-0.046,0.205-0.137,0.239-0.378c0.04-0.272-0.097-0.463-0.318-0.495
c-0.17-0.023-0.312,0.042-0.589,0.316l-0.093,0.093c-0.401,0.398-0.68,0.521-1.031,0.472c-0.215-0.03-0.458-0.148-0.594-0.44
c-0.092-0.202-0.097-0.414-0.067-0.61C41.442,26.249,41.492,26.087,41.585,25.938z M42.705,29.112
c0.547,0.154,0.648,0.183,0.766,0.208c0.125,0.027,0.195,0.009,0.235-0.065c0.022-0.036,0.039-0.082,0.051-0.125
c0.011-0.036,0.023-0.056,0.053-0.048c0.021,0.007,0.021,0.037,0.004,0.095c-0.039,0.138-0.113,0.36-0.144,0.466
c-0.024,0.09-0.073,0.305-0.112,0.44c-0.014,0.047-0.028,0.074-0.051,0.068c-0.028-0.009-0.029-0.032-0.02-0.068
s0.015-0.064,0.02-0.11c0.01-0.103-0.044-0.147-0.168-0.19c-0.112-0.04-0.213-0.068-0.76-0.223l-0.633-0.181
c-0.349-0.099-0.633-0.179-0.792-0.208c-0.1-0.017-0.172-0.01-0.217,0.095c-0.021,0.049-0.051,0.126-0.066,0.18
c-0.011,0.04-0.026,0.055-0.045,0.05c-0.025-0.007-0.028-0.039-0.017-0.079c0.066-0.236,0.142-0.461,0.169-0.559
c0.022-0.082,0.077-0.312,0.118-0.456c0.013-0.047,0.03-0.068,0.056-0.062c0.018,0.005,0.025,0.022,0.013,0.066
c-0.016,0.053-0.021,0.099-0.021,0.133c-0.006,0.076,0.049,0.115,0.149,0.155c0.146,0.061,0.431,0.141,0.779,0.239L42.705,29.112z
M40.502,30.431c0.048-0.046,0.069-0.048,0.146-0.016c0.188,0.081,0.389,0.186,0.441,0.211c0.051,0.026,0.083,0.054,0.072,0.08
c-0.013,0.031-0.046,0.021-0.073,0.009c-0.044-0.019-0.123-0.035-0.188-0.044c-0.287-0.033-0.467,0.128-0.56,0.342
c-0.134,0.309,0.011,0.55,0.192,0.629c0.168,0.071,0.348,0.09,0.694-0.101l0.191-0.104c0.458-0.25,0.755-0.277,1.033-0.156
c0.377,0.163,0.512,0.596,0.306,1.072c-0.097,0.224-0.192,0.353-0.256,0.432c-0.02,0.028-0.038,0.041-0.062,0.03
c-0.044-0.021-0.139-0.076-0.406-0.192c-0.075-0.032-0.099-0.055-0.086-0.082c0.01-0.022,0.035-0.024,0.077-0.007
c0.031,0.013,0.145,0.042,0.265,0.009c0.088-0.022,0.233-0.079,0.33-0.302c0.11-0.254,0.029-0.473-0.177-0.562
c-0.158-0.068-0.312-0.041-0.651,0.149l-0.113,0.064c-0.492,0.28-0.792,0.325-1.119,0.185c-0.199-0.086-0.403-0.264-0.458-0.58
c-0.036-0.22,0.017-0.425,0.095-0.606C40.282,30.694,40.373,30.55,40.502,30.431z M13.791,41.773c-0.024,0-0.056-0.002-0.114-0.02
c-0.053-0.015-0.07-0.058-0.09-0.186l-0.189-1.25c-0.009-0.048-0.034-0.077-0.064-0.079c-0.029,0.001-0.052,0.024-0.069,0.06
l-0.548,1.112l-0.546-1.098c-0.026-0.057-0.05-0.074-0.08-0.074c-0.029,0.001-0.052,0.029-0.057,0.066l-0.207,1.321
c-0.009,0.067-0.032,0.141-0.071,0.142c-0.032,0.004-0.046,0.004-0.062,0.004c-0.025,0-0.049,0.012-0.05,0.037
c0,0.032,0.033,0.04,0.055,0.04c0.068,0,0.168-0.008,0.208-0.008c0.037,0,0.132,0.008,0.221,0.008c0.029,0,0.065-0.005,0.067-0.04
c-0.003-0.027-0.026-0.036-0.05-0.037c-0.018,0-0.035-0.002-0.07-0.009c-0.035-0.009-0.05-0.017-0.051-0.044
c0-0.031,0.002-0.058,0.007-0.093l0.094-0.746c0.07,0.145,0.175,0.359,0.19,0.395c0.024,0.06,0.19,0.367,0.241,0.461
c0.034,0.061,0.054,0.105,0.099,0.111c0.04-0.004,0.051-0.031,0.099-0.124l0.432-0.866l0.109,0.832
c0.002,0.018,0.003,0.032,0.003,0.043c0,0.023-0.003,0.022-0.002,0.024c-0.015,0.006-0.033,0.015-0.035,0.037
c0.003,0.029,0.029,0.039,0.078,0.041c0.084,0.005,0.382,0.015,0.436,0.015c0.031-0.001,0.066-0.003,0.071-0.04
C13.839,41.781,13.812,41.773,13.791,41.773z M15.5,40.521c-0.234-0.237-0.593-0.235-0.854-0.235c-0.126,0-0.277,0.004-0.341,0.004
c-0.06,0-0.193-0.004-0.303-0.004c-0.028,0-0.062,0.002-0.064,0.035c0.001,0.029,0.029,0.039,0.053,0.039
c0.03,0,0.066,0.002,0.079,0.006c0.063,0.021,0.071,0.028,0.078,0.095c0.003,0.063,0.003,0.12,0.003,0.429v0.355
c0,0.188,0,0.346-0.01,0.43c-0.009,0.061-0.019,0.086-0.05,0.094c-0.018,0.004-0.04,0.006-0.069,0.006
c-0.031,0-0.05,0.02-0.05,0.039c0.001,0.028,0.029,0.038,0.058,0.038c0.043,0,0.096-0.002,0.146-0.005
c0.051,0,0.097-0.003,0.119-0.003c0.052,0,0.127,0.006,0.21,0.013c0.083,0.005,0.175,0.012,0.251,0.012
c0.391,0,0.616-0.145,0.715-0.243c0.121-0.119,0.233-0.315,0.233-0.576C15.703,40.802,15.607,40.629,15.5,40.521z M15.368,41.105
c0,0.207-0.042,0.382-0.172,0.488c-0.123,0.103-0.258,0.135-0.446,0.135c-0.162,0.001-0.24-0.044-0.26-0.073
c-0.011-0.012-0.02-0.086-0.021-0.134c-0.003-0.04-0.006-0.195-0.006-0.409v-0.255c0-0.159,0-0.326,0.003-0.396
c0.003-0.021,0-0.018,0.016-0.024c0.009-0.007,0.082-0.015,0.123-0.014c0.163,0,0.386,0.021,0.574,0.188
C15.268,40.69,15.368,40.853,15.368,41.105z M17.351,41.434c-0.028-0.001-0.041,0.025-0.042,0.055
c-0.006,0.033-0.034,0.09-0.068,0.127c-0.078,0.087-0.173,0.102-0.358,0.102c-0.272-0.001-0.635-0.222-0.636-0.691
c0-0.196,0.038-0.386,0.188-0.514c0.091-0.077,0.203-0.11,0.384-0.11c0.189,0,0.327,0.05,0.393,0.114
c0.049,0.049,0.074,0.116,0.076,0.177c0,0.023,0.004,0.058,0.039,0.06c0.036-0.003,0.043-0.038,0.045-0.065
c0.002-0.041,0.002-0.153,0.007-0.22c0.004-0.07,0.01-0.094,0.01-0.112c0.002-0.021-0.02-0.04-0.047-0.04
c-0.061-0.008-0.129-0.022-0.208-0.034c-0.096-0.012-0.176-0.021-0.308-0.021c-0.313,0-0.52,0.083-0.675,0.22
c-0.205,0.184-0.251,0.426-0.251,0.566c0,0.198,0.056,0.435,0.268,0.61c0.195,0.164,0.442,0.224,0.73,0.224
c0.137,0,0.296-0.012,0.385-0.047c0.037-0.013,0.057-0.033,0.065-0.069c0.021-0.072,0.045-0.245,0.045-0.273
C17.391,41.465,17.382,41.437,17.351,41.434z M19.029,41.434c-0.028-0.001-0.041,0.025-0.043,0.055
c-0.005,0.033-0.034,0.09-0.067,0.127c-0.079,0.087-0.173,0.102-0.358,0.102c-0.273-0.001-0.636-0.222-0.637-0.691
c0-0.196,0.038-0.386,0.188-0.514c0.092-0.077,0.203-0.11,0.384-0.11c0.19,0,0.327,0.05,0.394,0.114
c0.049,0.049,0.072,0.116,0.075,0.177c0,0.023,0.006,0.058,0.039,0.06c0.037-0.003,0.043-0.038,0.045-0.065
c0.002-0.041,0.002-0.153,0.008-0.22c0.004-0.07,0.009-0.094,0.01-0.112c0.001-0.021-0.021-0.04-0.047-0.04
c-0.062-0.008-0.129-0.022-0.208-0.034c-0.097-0.012-0.177-0.021-0.308-0.021c-0.314,0-0.521,0.083-0.676,0.22
c-0.205,0.184-0.251,0.426-0.251,0.566c0,0.198,0.056,0.435,0.269,0.61c0.194,0.164,0.441,0.224,0.729,0.224
c0.137,0,0.297-0.012,0.385-0.047c0.037-0.013,0.058-0.033,0.064-0.069c0.021-0.072,0.045-0.245,0.046-0.273
C19.069,41.465,19.06,41.437,19.029,41.434z M20.707,41.434c-0.027-0.001-0.041,0.025-0.043,0.055
c-0.006,0.033-0.034,0.09-0.066,0.127c-0.079,0.087-0.174,0.102-0.359,0.102c-0.272-0.001-0.635-0.222-0.635-0.691
c0-0.196,0.037-0.386,0.187-0.514c0.092-0.077,0.203-0.11,0.385-0.11c0.189,0,0.327,0.05,0.394,0.114
c0.049,0.049,0.074,0.116,0.076,0.177c0,0.023,0.005,0.058,0.039,0.06c0.037-0.003,0.043-0.038,0.045-0.065
c0.002-0.041,0.002-0.153,0.007-0.22c0.005-0.07,0.009-0.094,0.009-0.112c0.002-0.021-0.019-0.04-0.046-0.04
c-0.061-0.008-0.129-0.022-0.209-0.034c-0.095-0.012-0.175-0.021-0.307-0.021c-0.314,0-0.521,0.083-0.677,0.22
c-0.205,0.184-0.251,0.426-0.251,0.566c0,0.198,0.057,0.435,0.269,0.61c0.194,0.164,0.441,0.224,0.729,0.224
c0.138,0,0.297-0.012,0.386-0.047c0.036-0.013,0.057-0.033,0.064-0.069c0.022-0.072,0.045-0.245,0.046-0.273
C20.747,41.465,20.738,41.437,20.707,41.434z M28.803,41.773c-0.019,0-0.051-0.002-0.076-0.011
c-0.043-0.015-0.072-0.032-0.106-0.075c-0.049-0.061-0.379-0.581-0.459-0.703l0.34-0.465c0.064-0.089,0.104-0.141,0.142-0.148
c0.027-0.007,0.053-0.011,0.071-0.011c0.025,0,0.05-0.014,0.051-0.039c-0.002-0.031-0.032-0.036-0.056-0.036
c-0.08,0-0.166,0.005-0.205,0.005c-0.04,0-0.138-0.005-0.22-0.005c-0.029,0-0.06,0.005-0.062,0.036
c0.001,0.028,0.025,0.039,0.043,0.039c0.017,0,0.044,0,0.062,0.006c0.019,0.004,0.024,0.015,0.024,0.018
c0,0.013-0.006,0.039-0.022,0.064c-0.032,0.053-0.195,0.293-0.268,0.399c-0.079-0.132-0.161-0.266-0.248-0.418
c-0.011-0.02-0.021-0.047-0.021-0.052c0-0.002,0.002-0.009,0.017-0.013s0.036-0.006,0.046-0.006c0.021,0,0.049-0.01,0.05-0.039
c-0.002-0.031-0.033-0.036-0.06-0.036c-0.079,0-0.208,0.005-0.236,0.005c-0.101,0-0.269-0.005-0.319-0.005
c-0.024,0.001-0.055,0.001-0.06,0.034c0,0.021,0.016,0.041,0.038,0.041c0.019,0,0.051,0.004,0.083,0.014
c0.068,0.022,0.106,0.062,0.16,0.142l0.361,0.564l-0.401,0.544c-0.072,0.099-0.102,0.125-0.158,0.142
c-0.028,0.007-0.06,0.009-0.072,0.009c-0.024,0.001-0.045,0.018-0.045,0.041s0.024,0.036,0.048,0.036h0.036
c0.034,0,0.137-0.008,0.176-0.008c0.052,0,0.189,0.008,0.203,0.008h0.038c0.027,0,0.058-0.004,0.06-0.036
c-0.002-0.025-0.021-0.039-0.043-0.041c-0.015,0-0.03-0.002-0.045-0.002c-0.013,0-0.022-0.01-0.022-0.018c0-0.001,0-0.003,0-0.003
c0-0.015,0.01-0.042,0.028-0.07l0.293-0.453c0.093,0.149,0.201,0.332,0.316,0.52c0.004,0.007,0.005,0.012,0.005,0.014
c0,0.005-0.002,0.005-0.002,0.005c-0.021,0.005-0.041,0.021-0.041,0.043c0.006,0.036,0.038,0.034,0.09,0.038
c0.172,0.005,0.339,0.005,0.39,0.005h0.062c0.024,0,0.055-0.008,0.059-0.038C28.846,41.788,28.824,41.773,28.803,41.773z
M30.581,41.773c-0.018,0-0.05-0.002-0.076-0.011c-0.043-0.015-0.071-0.032-0.105-0.075c-0.049-0.061-0.379-0.581-0.46-0.703
l0.341-0.465c0.064-0.089,0.104-0.141,0.141-0.148c0.028-0.007,0.054-0.011,0.072-0.011c0.025,0,0.05-0.014,0.05-0.039
c-0.001-0.031-0.031-0.036-0.055-0.036c-0.079,0-0.165,0.005-0.205,0.005s-0.138-0.005-0.219-0.005
c-0.029,0-0.062,0.005-0.062,0.036c0.001,0.028,0.026,0.039,0.043,0.039c0.018,0,0.044,0,0.062,0.006
c0.019,0.004,0.024,0.015,0.023,0.018c0,0.013-0.006,0.039-0.021,0.064c-0.032,0.053-0.195,0.293-0.269,0.399
c-0.078-0.133-0.16-0.266-0.247-0.418c-0.012-0.02-0.021-0.047-0.02-0.052c0-0.002,0.001-0.008,0.016-0.013
c0.016-0.004,0.036-0.006,0.047-0.006c0.021,0,0.049-0.01,0.05-0.039c-0.002-0.031-0.033-0.036-0.061-0.036
c-0.079,0-0.208,0.005-0.234,0.005c-0.101,0-0.27-0.005-0.319-0.005c-0.025,0.001-0.055,0.001-0.06,0.034
c0,0.021,0.017,0.041,0.037,0.041s0.053,0.004,0.083,0.014c0.069,0.022,0.107,0.062,0.16,0.142l0.362,0.564l-0.402,0.544
c-0.071,0.099-0.101,0.125-0.157,0.142c-0.029,0.007-0.06,0.009-0.071,0.009c-0.025,0.001-0.045,0.018-0.046,0.041
c0,0.023,0.023,0.036,0.048,0.036h0.036c0.035,0,0.137-0.008,0.176-0.008c0.052,0,0.189,0.008,0.203,0.008h0.038
c0.026,0,0.058-0.004,0.06-0.036c-0.002-0.025-0.021-0.039-0.043-0.041c-0.015,0-0.031-0.002-0.045-0.002
c-0.013,0-0.022-0.008-0.023-0.018c0-0.001,0-0.003,0-0.003c0-0.015,0.01-0.042,0.028-0.07l0.292-0.453
c0.092,0.149,0.201,0.332,0.317,0.52c0.004,0.007,0.005,0.012,0.005,0.014c0,0.004-0.002,0.005-0.002,0.005
c-0.021,0.005-0.041,0.021-0.041,0.043c0.006,0.036,0.038,0.034,0.09,0.038c0.171,0.005,0.338,0.005,0.389,0.005h0.062
c0.024,0,0.055-0.008,0.058-0.038C30.624,41.788,30.603,41.773,30.581,41.773z M32.359,41.773c-0.019,0-0.05-0.002-0.076-0.011
c-0.043-0.015-0.072-0.032-0.106-0.075c-0.049-0.061-0.379-0.581-0.459-0.703l0.341-0.465c0.063-0.089,0.104-0.141,0.141-0.148
c0.028-0.007,0.053-0.011,0.072-0.011c0.024,0,0.049-0.014,0.05-0.039c-0.002-0.031-0.032-0.036-0.055-0.036
c-0.079,0-0.166,0.005-0.205,0.005c-0.04,0-0.139-0.005-0.22-0.005c-0.028,0-0.061,0.005-0.062,0.036
c0.001,0.028,0.025,0.039,0.043,0.039c0.016,0,0.044,0,0.061,0.006c0.02,0.004,0.025,0.015,0.024,0.018
c0,0.013-0.006,0.039-0.021,0.064c-0.032,0.053-0.195,0.293-0.269,0.399c-0.079-0.132-0.161-0.266-0.248-0.418
c-0.011-0.02-0.02-0.047-0.02-0.052c0-0.002,0.002-0.009,0.016-0.013c0.015-0.004,0.036-0.006,0.046-0.006
c0.021,0,0.049-0.01,0.05-0.039c-0.002-0.031-0.032-0.036-0.06-0.036c-0.079,0-0.208,0.005-0.236,0.005
c-0.099,0-0.268-0.005-0.317-0.005c-0.026,0.001-0.057,0.001-0.061,0.034c0,0.021,0.018,0.041,0.038,0.041
c0.02,0,0.052,0.004,0.083,0.014c0.069,0.022,0.107,0.062,0.159,0.142l0.363,0.564l-0.402,0.544
c-0.072,0.099-0.101,0.125-0.157,0.142c-0.029,0.007-0.059,0.009-0.072,0.009c-0.023,0.001-0.045,0.018-0.045,0.041
s0.024,0.036,0.047,0.036h0.036c0.035,0,0.138-0.008,0.177-0.008c0.052,0,0.188,0.008,0.201,0.008h0.038
c0.027,0,0.059-0.004,0.061-0.036c-0.002-0.025-0.021-0.039-0.043-0.041c-0.017,0-0.032-0.002-0.045-0.002
c-0.014,0-0.023-0.008-0.024-0.018v-0.003c0-0.015,0.01-0.042,0.028-0.07l0.293-0.453c0.092,0.149,0.201,0.332,0.315,0.52
c0.004,0.007,0.006,0.012,0.006,0.014c0,0.004-0.003,0.005-0.003,0.005c-0.021,0.005-0.041,0.021-0.041,0.043
c0.007,0.036,0.038,0.033,0.091,0.038c0.172,0.005,0.339,0.005,0.389,0.005h0.062c0.025,0,0.056-0.008,0.059-0.038
C32.401,41.788,32.38,41.773,32.359,41.773z M33.453,41.773c-0.03,0-0.078-0.002-0.104-0.007c-0.057-0.012-0.061-0.026-0.069-0.08
c-0.009-0.084-0.009-0.246-0.009-0.44v-0.356c0-0.309,0-0.364,0.004-0.429c0.007-0.069,0.015-0.083,0.063-0.094
c0.024-0.005,0.039-0.007,0.06-0.007s0.051-0.012,0.051-0.041c-0.005-0.033-0.035-0.033-0.061-0.034
c-0.081,0-0.218,0.005-0.27,0.005c-0.059,0-0.202-0.005-0.281-0.005c-0.031,0.001-0.062,0-0.066,0.034
c0,0.029,0.028,0.041,0.051,0.041c0.025,0,0.05,0.002,0.071,0.009c0.04,0.015,0.053,0.025,0.06,0.092
c0.002,0.063,0.002,0.12,0.002,0.429v0.356c0,0.194,0,0.356-0.01,0.438c-0.009,0.06-0.015,0.073-0.049,0.083
c-0.018,0.004-0.04,0.006-0.07,0.006c-0.031,0-0.051,0.02-0.051,0.039c0.001,0.029,0.031,0.038,0.058,0.038
c0.084,0,0.228-0.008,0.273-0.008c0.056,0,0.2,0.008,0.338,0.008c0.025,0,0.055-0.008,0.058-0.038
C33.504,41.791,33.482,41.773,33.453,41.773z M34.724,41.773c-0.03,0-0.078-0.002-0.104-0.007c-0.056-0.012-0.06-0.026-0.069-0.08
c-0.01-0.084-0.01-0.246-0.01-0.44v-0.356c0-0.309,0-0.364,0.005-0.429c0.008-0.069,0.015-0.083,0.064-0.094
c0.023-0.005,0.039-0.007,0.059-0.007c0.021,0,0.051-0.012,0.051-0.041c-0.006-0.033-0.034-0.033-0.061-0.034
c-0.082,0-0.218,0.005-0.27,0.005c-0.06,0-0.202-0.005-0.281-0.005c-0.032,0.001-0.062,0-0.067,0.034
c0,0.029,0.029,0.041,0.05,0.041c0.025,0,0.051,0.002,0.071,0.009c0.04,0.015,0.052,0.025,0.06,0.092
c0.003,0.063,0.003,0.12,0.003,0.429v0.356c0,0.194,0,0.356-0.011,0.438c-0.009,0.06-0.015,0.073-0.049,0.083
c-0.018,0.004-0.04,0.006-0.069,0.006c-0.031,0-0.051,0.02-0.051,0.039c0.001,0.029,0.03,0.038,0.058,0.038
c0.084,0,0.227-0.008,0.273-0.008c0.057,0,0.2,0.008,0.338,0.008c0.025,0,0.056-0.008,0.059-0.038
C34.774,41.791,34.753,41.773,34.724,41.773z M35.995,41.773c-0.03,0-0.077-0.002-0.104-0.007c-0.057-0.012-0.061-0.026-0.069-0.08
c-0.01-0.084-0.01-0.246-0.01-0.44v-0.356c0-0.309,0-0.364,0.005-0.429c0.007-0.069,0.014-0.083,0.063-0.094
c0.024-0.005,0.039-0.007,0.06-0.007s0.05-0.012,0.05-0.041c-0.005-0.033-0.035-0.033-0.06-0.034c-0.081,0-0.218,0.005-0.27,0.005
c-0.059,0-0.202-0.005-0.281-0.005c-0.031,0.001-0.062,0-0.066,0.034c0,0.029,0.028,0.041,0.05,0.041
c0.025,0,0.05,0.002,0.071,0.009c0.041,0.015,0.053,0.025,0.06,0.092c0.002,0.063,0.002,0.12,0.002,0.429v0.356
c0,0.194,0,0.356-0.009,0.438c-0.009,0.06-0.015,0.073-0.049,0.083c-0.019,0.004-0.04,0.006-0.07,0.006
c-0.031,0-0.05,0.02-0.05,0.039c0.001,0.029,0.031,0.038,0.058,0.038c0.084,0,0.228-0.008,0.274-0.008
c0.056,0,0.2,0.008,0.338,0.008c0.024,0,0.055-0.008,0.057-0.038C36.045,41.79,36.024,41.773,35.995,41.773z M23.406,31.974v-1.188
c0-0.222-0.18-0.402-0.402-0.402c-0.222,0-0.402,0.182-0.402,0.402v1.188H23.406z M25.583,31.974v-1.188
c0-0.222-0.18-0.402-0.401-0.402c-0.224,0-0.403,0.182-0.403,0.402v1.188H25.583z M24.092,8.076c-0.232,0-0.422,0.189-0.422,0.422
c0,0.233,0.189,0.422,0.422,0.422c0.233,0,0.423-0.188,0.423-0.422C24.514,8.265,24.325,8.076,24.092,8.076z M23.879,19.853h0.426
v-0.915h-0.426V19.853L23.879,19.853z M19.84,42.941c-0.232,0-0.422,0.188-0.422,0.422s0.189,0.422,0.422,0.422
c0.233,0,0.423-0.188,0.423-0.422S20.073,42.941,19.84,42.941z M19.584,19.869v-1.345c-0.003-0.315-0.158-0.533-0.39-0.635
c-0.231,0.102-0.386,0.319-0.389,0.636v1.344H19.584z M19.584,15.548V14.68c-0.003-0.315-0.158-0.534-0.39-0.637
c-0.231,0.104-0.386,0.321-0.389,0.637v0.867H19.584z M19.402,24.539v-1.127c0-0.116-0.093-0.209-0.209-0.209
s-0.209,0.093-0.209,0.209v1.127H19.402z M19.402,27.931v-1.126c0-0.116-0.093-0.211-0.209-0.211s-0.209,0.095-0.209,0.211v1.126
H19.402z M19.525,31.721v-1c0-0.188-0.151-0.339-0.338-0.339c-0.188,0-0.338,0.151-0.338,0.339v1H19.525z M19.194,3.409
c-0.233,0-0.422,0.189-0.422,0.422c0,0.233,0.188,0.422,0.422,0.422c0.232,0,0.422-0.188,0.422-0.422
C19.616,3.598,19.427,3.409,19.194,3.409z M29.375,19.869v-1.345c-0.002-0.315-0.158-0.533-0.389-0.635
c-0.231,0.102-0.387,0.319-0.389,0.636v1.344H29.375z M29.375,15.548V14.68c-0.002-0.315-0.158-0.534-0.389-0.637
c-0.231,0.104-0.387,0.321-0.389,0.637v0.867H29.375z M29.194,24.539v-1.127c0-0.116-0.094-0.209-0.209-0.209
s-0.209,0.093-0.209,0.209v1.127H29.194z M29.194,27.931v-1.126c0-0.116-0.094-0.211-0.209-0.211s-0.209,0.095-0.209,0.211v1.126
H29.194z M29.317,31.721v-1c0-0.188-0.151-0.339-0.338-0.339c-0.188,0-0.338,0.151-0.338,0.339v1H29.317z M28.985,3.409
c-0.233,0-0.422,0.189-0.422,0.422c0,0.233,0.188,0.422,0.422,0.422c0.232,0,0.422-0.188,0.422-0.422
C29.408,3.598,29.219,3.409,28.985,3.409z M60.109,21.36c-3.838,0-4.703-2.09-4.703-4.415V8.998h2.342v7.803
c0,1.532,0.505,2.613,2.523,2.613c1.802,0,2.559-0.757,2.559-2.829V8.998h2.325v7.442C65.155,19.774,63.316,21.36,60.109,21.36z
M73.155,21.162v-5.729c0-0.938-0.253-1.496-1.1-1.496c-1.172,0-2.091,1.334-2.091,2.901v4.325h-2.307v-8.956h2.181
c0,0.415-0.035,1.117-0.126,1.586l0.019,0.019c0.541-1.063,1.586-1.803,3.046-1.803c2.018,0,2.666,1.298,2.666,2.865v6.289
L73.155,21.162L73.155,21.162z M79.102,11.052c-0.793,0-1.424-0.631-1.424-1.405c0-0.757,0.631-1.389,1.424-1.389
s1.44,0.613,1.44,1.389C80.543,10.422,79.895,11.052,79.102,11.052z M77.948,21.162v-8.956h2.307v8.956H77.948z M87.211,21.162
h-2.343l-3.314-8.956h2.521l1.424,4.036c0.216,0.613,0.434,1.334,0.596,1.982h0.035c0.145-0.612,0.324-1.298,0.54-1.91l1.441-4.108
h2.451L87.211,21.162z M98.797,17.054h-5.551c-0.018,1.676,0.812,2.485,2.469,2.485c0.884,0,1.839-0.198,2.613-0.559l0.216,1.784
c-0.955,0.378-2.09,0.576-3.207,0.576c-2.848,0-4.433-1.423-4.433-4.576c0-2.739,1.514-4.758,4.198-4.758
c2.611,0,3.767,1.784,3.767,4C98.869,16.314,98.851,16.675,98.797,17.054z M95.03,13.702c-0.955,0-1.622,0.703-1.748,1.784h3.298
C96.616,14.368,96.004,13.702,95.03,13.702z M105.59,14.278c-1.657-0.343-2.485,0.739-2.485,3.226v3.658h-2.308v-8.956h2.181
c0,0.45-0.055,1.171-0.162,1.802h0.036c0.433-1.135,1.298-2.126,2.848-2L105.59,14.278z M108.743,21.342
c-0.647,0-1.298-0.071-1.874-0.161l0.055-1.893c0.559,0.145,1.242,0.271,1.929,0.271c0.883,0,1.459-0.361,1.459-0.956
c0-1.585-3.621-0.686-3.621-3.73c0-1.567,1.278-2.865,3.802-2.865c0.522,0,1.1,0.072,1.64,0.162l-0.07,1.82
c-0.505-0.146-1.101-0.234-1.658-0.234c-0.901,0-1.333,0.36-1.333,0.919c0,1.46,3.676,0.812,3.676,3.713
C112.744,20.153,111.194,21.342,108.743,21.342z M115.861,11.052c-0.793,0-1.424-0.631-1.424-1.405c0-0.757,0.631-1.389,1.424-1.389
s1.44,0.613,1.44,1.389C117.302,10.422,116.654,11.052,115.861,11.052z M114.708,21.162v-8.956h2.308v8.956H114.708z
M122.851,21.342c-1.982,0-2.613-0.721-2.613-2.811V13.99h-1.531v-1.784h1.531V9.449l2.307-0.613v3.37h2.182v1.784h-2.182v3.928
c0,1.153,0.271,1.479,1.063,1.479c0.378,0,0.793-0.055,1.117-0.145v1.856C124.147,21.252,123.481,21.342,122.851,21.342z
M131.731,21.162c0-0.521,0.019-1.045,0.091-1.514l-0.019-0.019c-0.434,1.01-1.531,1.712-2.865,1.712c-1.621,0-2.56-0.919-2.56-2.36
c0-2.145,2.126-3.279,5.172-3.279v-0.487c0-0.937-0.45-1.423-1.747-1.423c-0.812,0-1.894,0.271-2.649,0.703l-0.198-1.928
c0.901-0.324,2.056-0.56,3.208-0.56c2.884,0,3.694,1.172,3.694,3.118v3.73c0,0.721,0.018,1.567,0.054,2.307L131.731,21.162
L131.731,21.162z M128.561,10.729c-0.687,0-1.243-0.56-1.243-1.244c0-0.703,0.558-1.244,1.243-1.244
c0.685,0,1.243,0.541,1.243,1.244C129.804,10.169,129.245,10.729,128.561,10.729z M131.552,17.198c-2.433,0-2.974,0.703-2.974,1.423
c0,0.577,0.396,0.955,1.063,0.955c1.135,0,1.909-1.081,1.909-2.162L131.552,17.198L131.552,17.198z M132.2,10.729
c-0.685,0-1.243-0.56-1.243-1.244c0-0.703,0.56-1.244,1.243-1.244s1.244,0.541,1.244,1.244
C133.444,10.169,132.885,10.729,132.2,10.729z M139.55,21.342c-1.981,0-2.612-0.721-2.612-2.811V13.99h-1.531v-1.784h1.531V9.449
l2.307-0.613v3.37h2.181v1.784h-2.181v3.928c0,1.153,0.271,1.479,1.063,1.479c0.379,0,0.793-0.055,1.116-0.145v1.856
C140.847,21.252,140.181,21.342,139.55,21.342z M54.955,40.202V38.4l4.379-7.137c0.252-0.414,0.504-0.774,0.793-1.153
c-0.433,0.019-1.009,0.036-2.217,0.036h-2.811v-2.108h7.856v1.874l-4.631,7.316c-0.18,0.288-0.342,0.559-0.558,0.848
c0.306-0.036,1.135-0.036,2.631-0.036h2.631v2.162H54.955z M70.484,40.202c0-0.414,0.018-1.117,0.107-1.586l-0.018-0.018
c-0.541,1.062-1.567,1.802-3.045,1.802c-2.02,0-2.667-1.298-2.667-2.865v-6.289h2.289v5.73c0,0.937,0.252,1.496,1.117,1.496
c1.171,0,2.071-1.334,2.071-2.901v-4.325h2.308v8.956H70.484z M66.826,29.769c-0.685,0-1.243-0.56-1.243-1.244
c0-0.702,0.56-1.243,1.243-1.243c0.685,0,1.244,0.541,1.244,1.243C68.069,29.21,67.511,29.769,66.826,29.769z M70.466,29.769
c-0.685,0-1.243-0.56-1.243-1.244c0-0.702,0.559-1.243,1.243-1.243c0.686,0,1.243,0.541,1.243,1.243
C71.709,29.21,71.151,29.769,70.466,29.769z M79.926,33.318c-1.656-0.343-2.485,0.739-2.485,3.226v3.658h-2.308v-8.956h2.182
c0,0.45-0.055,1.171-0.162,1.802h0.036c0.432-1.135,1.297-2.126,2.847-2L79.926,33.318z M82.854,30.093
c-0.793,0-1.424-0.631-1.424-1.405c0-0.756,0.631-1.388,1.424-1.388s1.44,0.612,1.44,1.388
C84.295,29.462,83.646,30.093,82.854,30.093z M81.7,40.202v-8.956h2.307v8.956H81.7z M89.729,40.364
c-2.486,0-4.036-1.297-4.036-4.343c0-2.793,1.46-4.938,4.632-4.938c0.612,0,1.261,0.09,1.838,0.252l-0.233,2.001
c-0.486-0.181-1.046-0.325-1.622-0.325c-1.46,0-2.198,1.081-2.198,2.775c0,1.531,0.595,2.577,2.126,2.577
c0.612,0,1.279-0.127,1.767-0.379l0.181,1.965C91.567,40.185,90.684,40.364,89.729,40.364z M99.478,40.202v-5.729
c0-0.938-0.252-1.496-1.1-1.496c-1.172,0-2.091,1.334-2.091,2.9v4.325h-2.307V27.047h2.307v3.839c0,0.541-0.036,1.298-0.162,1.82
l0.036,0.019c0.522-1.01,1.55-1.677,2.938-1.677c2.018,0,2.667,1.299,2.667,2.865v6.289H99.478z M105.334,31.716
c-1.461,0-1.813-0.788-1.813-1.706v-2.972h1.061v2.91c0,0.537,0.17,0.877,0.822,0.877c0.599,0,0.83-0.252,0.83-0.938v-2.85h1.047
v2.788C107.279,31.118,106.552,31.716,105.334,31.716z M108.163,31.628v-0.83l1.477-2.468c0.073-0.122,0.155-0.238,0.243-0.354
c-0.12,0.007-0.277,0.014-0.721,0.014h-0.951v-0.952h2.972v0.863l-1.558,2.502c-0.055,0.089-0.107,0.164-0.177,0.259
c0.089-0.014,0.347-0.014,0.891-0.014h0.87v0.979L108.163,31.628L108.163,31.628z M114.922,31.628v-1.911h-1.68v1.911h-1.061v-4.59
h1.061V28.8h1.68v-1.762h1.062v4.59H114.922z M0,24.091C0,10.786,10.786,0,24.091,0l0,0c13.306,0,24.092,10.786,24.092,24.091l0,0
c0,13.306-10.786,24.092-24.092,24.092l0,0C10.786,48.182,0,37.396,0,24.091L0,24.091z M7.477,7.477
C3.225,11.73,0.595,17.602,0.595,24.091l0,0c0,6.489,2.629,12.361,6.882,16.614l0,0c4.253,4.252,10.125,6.881,16.613,6.881l0,0
c6.489,0,12.361-2.629,16.614-6.881l0,0c4.252-4.253,6.882-10.125,6.882-16.614l0,0c0-6.488-2.629-12.36-6.882-16.613l0,0
C36.452,3.224,30.58,0.594,24.091,0.594l0,0C17.602,0.594,11.73,3.224,7.477,7.477L7.477,7.477z M24.456,39.232h0.194v-0.34h-0.462
v0.753h0.269L24.456,39.232z M24.091,1.568c-12.436,0-22.516,10.081-22.516,22.516c0,12.437,10.081,22.518,22.516,22.518
c12.436,0,22.516-10.081,22.517-22.518C46.606,11.649,36.526,1.568,24.091,1.568z M21.598,43.19h-1.021v0.34h1.021v2.53
c-5.112-0.573-9.696-2.889-13.146-6.338c-0.495-0.495-0.967-1.015-1.414-1.555c0.091-0.549,0.565-0.967,1.141-0.967
c0.639,0,1.155,0.518,1.155,1.156v1.11h1.468v-0.007l0,0v-1.104c0-0.64,0.519-1.156,1.156-1.156c0.639,0,1.157,0.518,1.157,1.156
v1.11h1.468v-0.007l0,0v-1.104c0-0.64,0.519-1.156,1.156-1.156s1.157,0.518,1.157,1.156v1.11h1.468v-0.007l0,0v-1.104
c0-0.64,0.518-1.156,1.157-1.156c0.639,0,1.155,0.518,1.155,1.156v1.11h0.922V43.19z M25.794,46.135
c-0.562,0.044-1.13,0.065-1.703,0.065c-0.571,0-1.14-0.021-1.7-0.063l-0.418-2.606h0.481c-0.12-0.286-0.329-0.786-0.409-0.989
c-0.13-0.326-0.032-0.542,0.185-0.626c0.211-0.082,0.449,0.034,0.523,0.312l0.211,0.792h0.309v0.511l0,0v0.102h-0.729v0.341h1.066
l0.001-0.441h1.293l-0.001-0.511h0.31l0.213-0.792c0.075-0.278,0.315-0.396,0.528-0.312c0.218,0.084,0.315,0.3,0.186,0.626
c-0.035,0.089-0.093,0.235-0.154,0.396l-0.288,0.695H24.92v0.341h1.005l0.184-0.441h0.104L25.794,46.135z M23.391,38.684h1.4
c0.055,0.129,0.188,0.454,0.271,0.643c0.057,0.131,0.13,0.271,0.041,0.416c-0.106,0.171-0.245,0.146-0.342,0.139
c-0.006-0.001-0.012,0-0.018-0.001c-0.098,0.269-0.352,0.459-0.653,0.459c-0.301,0-0.555-0.19-0.652-0.459
c-0.005,0.001-0.013,0-0.018,0.001c-0.098,0.009-0.235,0.032-0.342-0.139c-0.089-0.145-0.016-0.285,0.041-0.416
C23.204,39.138,23.337,38.812,23.391,38.684z M23.18,37.23l0.447,0.641l0.458-0.869l0.458,0.869l0.448-0.641l-0.178,1.111h-1.456
L23.18,37.23z M23.715,40.376c0.105,0.293,0.385,0.504,0.715,0.504c0.356,0,0.654-0.247,0.736-0.58
c0.222,0.012,0.442,0.075,0.661,0.261c0.211,0.177,0.293,0.48,0.323,0.632h-4.116c0.029-0.15,0.112-0.455,0.323-0.632
C22.808,40.181,23.268,40.305,23.715,40.376z M25.414,41.532c-0.155,0.586-0.688,1.019-1.322,1.019
c-0.634,0-1.167-0.433-1.321-1.019H25.414z M39.729,39.723c-3.45,3.449-8.033,5.764-13.145,6.337v-2.53h0.935
c1.731,0,2.983-0.146,2.983-0.146s-1.289-0.194-2.983-0.194h-0.935v-3.721h1.117v-0.007l0,0v-1.104c0-0.64,0.519-1.156,1.157-1.156
s1.157,0.519,1.157,1.156v1.11h1.467v-0.007l0,0v-1.104c0-0.64,0.518-1.156,1.156-1.156c0.638,0,1.155,0.519,1.155,1.156v1.11h1.469
v-0.007l0,0v-1.104c0-0.64,0.518-1.156,1.156-1.156c0.64,0,1.156,0.519,1.156,1.156v1.11h1.468v-0.007l0,0v-1.104
c0-0.64,0.519-1.156,1.157-1.156c0.506,0,0.937,0.326,1.092,0.78C40.803,38.589,40.281,39.171,39.729,39.723z M41.541,37.673
c-0.257-0.541-0.808-0.916-1.445-0.916c-0.885,0-1.602,0.718-1.602,1.603v0.517h-0.579v-0.517c0-0.885-0.717-1.603-1.601-1.603
s-1.602,0.718-1.602,1.603v0.517h-0.58v-0.517c0-0.885-0.717-1.603-1.601-1.603s-1.601,0.718-1.601,1.603v0.517h-0.58v-0.517
c0-0.885-0.718-1.603-1.602-1.603s-1.601,0.718-1.601,1.603v0.517h-0.598c-0.182-1.198-1.213-2.118-2.462-2.118
s-2.279,0.92-2.462,2.118h-0.614v-0.517c0-0.885-0.718-1.603-1.602-1.603s-1.601,0.718-1.601,1.603v0.517h-0.58v-0.517
c0-0.885-0.716-1.603-1.602-1.603c-0.884,0-1.601,0.718-1.601,1.603v0.517h-0.58v-0.517c0-0.885-0.717-1.603-1.601-1.603
c-0.885,0-1.602,0.718-1.602,1.603v0.517H9.672v-0.517c0-0.885-0.717-1.603-1.601-1.603c-0.634,0-1.181,0.369-1.44,0.903
c-0.39-0.501-0.758-1.02-1.104-1.554h37.129C42.306,36.645,41.935,37.168,41.541,37.673z M43.027,35.515H5.155
c-0.11-0.183-0.217-0.366-0.322-0.553h38.517C43.243,35.148,43.137,35.333,43.027,35.515z M20.275,7.089v0.187
C20.27,7.313,20.26,7.368,20.241,7.42c-0.032,0.085-0.111,0.221-0.217,0.386c-0.04,0.064-0.11,0.174-0.188,0.33
C19.735,7.982,19.65,7.88,19.59,7.817c-0.066-0.07-0.154-0.16-0.228-0.246c-0.157-0.187-0.166-0.298-0.166-0.298l0,0
c0,0-0.005,0.11-0.163,0.297c-0.073,0.085-0.161,0.175-0.228,0.246c-0.062,0.063-0.147,0.167-0.25,0.325
c-0.08-0.161-0.151-0.272-0.192-0.341c-0.105-0.165-0.185-0.3-0.217-0.385c-0.03-0.081-0.041-0.174-0.042-0.184V7.089H20.275z
M18.109,6.749c0.017-0.277,0.092-0.805,0.362-1.164c0.204-0.278,0.407-0.443,0.557-0.54C19.096,5,19.151,4.973,19.19,4.954
c0.04,0.02,0.095,0.046,0.164,0.091c0.147,0.097,0.352,0.262,0.557,0.54c0.271,0.359,0.345,0.887,0.362,1.164H18.109z M20.354,7.988
c0.062,0.085,0.125,0.209,0.194,0.359c0.096,0.233,0.121,0.462,0.124,0.63v2.492h-0.642c0-0.548,0-1.756,0-2.491
c0.002-0.171,0.033-0.409,0.124-0.632C20.234,8.17,20.296,8.07,20.354,7.988z M19.682,8.994l-0.001,2.476h-0.974
c0-0.689-0.001-2.477-0.001-2.478c0.008-0.162,0.021-0.386,0.138-0.617c0.122-0.242,0.354-0.386,0.354-0.386h0.001
c0,0,0.219,0.144,0.342,0.386C19.664,8.618,19.675,8.828,19.682,8.994z M18.245,8.343c0.091,0.224,0.106,0.465,0.109,0.636
l0.003,2.491h-0.634V8.975c0.003-0.168,0.021-0.396,0.116-0.631c0.07-0.149,0.138-0.273,0.195-0.358
C18.106,8.091,18.175,8.193,18.245,8.343z M21.164,11.877v0.807h-3.941v-0.807H21.164z M21.164,13.022v3.141H20.21V14.75
c0.001-0.469-0.173-0.866-0.459-1.138c-0.153-0.147-0.338-0.258-0.542-0.328c-0.013-0.004-0.012-0.005-0.012-0.005
s-0.002,0-0.015,0.005c-0.204,0.07-0.39,0.181-0.543,0.328c-0.286,0.271-0.459,0.669-0.458,1.138v1.414h-0.958v-3.141L21.164,13.022
L21.164,13.022z M19.87,14.75v1.414h-1.35V14.75c0.005-0.546,0.272-0.925,0.675-1.103C19.596,13.824,19.865,14.203,19.87,14.75z
M21.164,16.503v3.995h-0.876v-1.995c0-0.468-0.176-0.875-0.47-1.155c-0.157-0.15-0.344-0.263-0.55-0.333
c-0.011-0.005-0.074-0.024-0.074-0.024s-0.062,0.02-0.067,0.022c-0.207,0.07-0.396,0.185-0.555,0.335
c-0.294,0.279-0.471,0.688-0.47,1.154v1.995h-0.88v-3.995L21.164,16.503L21.164,16.503z M19.948,18.506v1.992h-1.507v-1.995
c0-0.384,0.141-0.694,0.364-0.909c0.11-0.105,0.241-0.185,0.389-0.241h0.001c0.147,0.058,0.276,0.136,0.387,0.24
c0.226,0.215,0.364,0.525,0.365,0.91L19.948,18.506z M21.164,20.838v4.755h-3.941v-4.755H21.164z M21.164,25.933v3.103h-3.941
v-3.103H21.164z M21.164,29.375v5.247h-3.941v-5.247H21.164z M24.634,16.538v1.416l-0.542-0.542l-0.541,0.541v-1.415H24.634z
M23.566,16.13l0.396-1.694l0.163-2.835l0.098,2.833l0.396,1.696H23.566z M24.092,17.988l2.511,2.51h-5.021L24.092,17.988z
M26.603,20.838v1.921h-5.037v-1.921H26.603z M26.603,23.354v2.238h-1.575v-0.561c0-0.516-0.419-0.938-0.938-0.938
c-0.517,0-0.937,0.421-0.937,0.938v0.561h-1.589v-2.238H26.603z M23.155,25.933v1.998h1.873v-1.998h1.575v3.103h-5.037v-3.103
H23.155z M26.603,29.375v5.247h-5.037v-5.247H26.603z M30.067,7.089v0.187c-0.005,0.038-0.017,0.093-0.035,0.145
C30,7.505,29.921,7.64,29.815,7.805c-0.04,0.064-0.11,0.174-0.188,0.33c-0.101-0.153-0.186-0.256-0.246-0.318
c-0.065-0.07-0.154-0.16-0.228-0.246c-0.158-0.187-0.166-0.298-0.166-0.298l0,0c0,0-0.005,0.11-0.163,0.297
c-0.073,0.085-0.161,0.175-0.228,0.246c-0.062,0.063-0.147,0.167-0.25,0.325c-0.08-0.161-0.151-0.272-0.192-0.341
c-0.105-0.165-0.185-0.3-0.217-0.385c-0.031-0.082-0.041-0.177-0.042-0.185V7.089H30.067z M27.901,6.749
c0.017-0.277,0.092-0.805,0.362-1.164c0.204-0.278,0.407-0.443,0.557-0.54C28.888,5,28.942,4.973,28.982,4.954
c0.04,0.02,0.095,0.046,0.163,0.091c0.147,0.097,0.352,0.262,0.557,0.54c0.271,0.359,0.345,0.887,0.362,1.164H27.901z M30.146,7.988
c0.061,0.085,0.125,0.209,0.193,0.359c0.096,0.233,0.121,0.462,0.124,0.63v2.492h-0.642c0-0.548,0-1.756,0-2.491
c0.003-0.171,0.033-0.409,0.125-0.632C30.025,8.17,30.088,8.07,30.146,7.988z M29.473,8.994l-0.001,2.476H28.5
c0-0.689-0.001-2.477-0.001-2.478c0.008-0.162,0.021-0.386,0.138-0.617c0.122-0.242,0.354-0.386,0.354-0.386h0.001
c0,0,0.219,0.144,0.342,0.386C29.455,8.618,29.466,8.828,29.473,8.994z M28.036,8.343c0.091,0.224,0.106,0.465,0.109,0.636
l0.003,2.491h-0.635V8.975c0.004-0.168,0.021-0.396,0.116-0.631c0.07-0.149,0.138-0.273,0.194-0.358
C27.898,8.091,27.966,8.193,28.036,8.343z M30.956,11.877v0.807h-3.941v-0.807H30.956z M30.956,13.022v3.141h-0.954V14.75
c0-0.469-0.173-0.866-0.459-1.138c-0.153-0.147-0.339-0.258-0.543-0.328c-0.013-0.004-0.012-0.005-0.012-0.005s-0.002,0-0.015,0.005
c-0.204,0.07-0.39,0.181-0.543,0.328c-0.286,0.271-0.459,0.669-0.459,1.138v1.414h-0.958v-3.141L30.956,13.022L30.956,13.022z
M29.662,14.75v1.414h-1.351V14.75c0.005-0.546,0.272-0.925,0.675-1.103C29.388,13.824,29.657,14.203,29.662,14.75z M30.956,16.503
v3.995H30.08v-1.995c0-0.468-0.176-0.875-0.47-1.155c-0.157-0.15-0.344-0.263-0.55-0.333c-0.01-0.005-0.074-0.024-0.074-0.024
s-0.062,0.02-0.068,0.022c-0.207,0.07-0.396,0.185-0.555,0.335c-0.294,0.279-0.47,0.688-0.47,1.154v1.995h-0.88v-3.995
L30.956,16.503L30.956,16.503z M29.74,18.506v1.992h-1.506v-1.995c0-0.384,0.141-0.694,0.364-0.909
c0.11-0.105,0.241-0.185,0.388-0.241h0.001c0.147,0.058,0.277,0.136,0.388,0.24c0.225,0.215,0.364,0.525,0.365,0.91V18.506z
M30.956,20.838v4.755h-3.941v-4.755H30.956z M30.956,25.933v3.103h-3.941v-3.103H30.956z M30.956,29.375v5.247h-3.941v-5.247
H30.956z M31.562,34.622V11.469h-0.497V8.917c-0.005-0.317-0.051-0.519-0.191-0.844c-0.036-0.082-0.087-0.172-0.145-0.262V6.889
c0,0-0.001-0.725-0.3-1.27c-0.325-0.595-0.956-0.92-1.264-1.049c-0.07-0.033-0.117-0.048-0.126-0.05l-0.051-0.019l-0.063,0.019
c-0.03,0.009-0.529,0.167-1.004,0.812c-0.43,0.593-0.444,1.354-0.448,1.563c0,0.034,0.001,0.054,0.001,0.057v0.851
c-0.058,0.084-0.138,0.211-0.226,0.398c-0.132,0.324-0.141,0.624-0.146,0.804v2.465h-0.5v8.452l-1.368-1.367v-2.413l-0.464-1.558
l-0.68-5.341l0,0l-0.501,5.117l-0.466,1.782v2.234l-1.354,1.355v-8.265h-0.497V8.917c-0.005-0.317-0.051-0.519-0.192-0.844
c-0.035-0.082-0.087-0.172-0.144-0.262V6.889c0,0-0.001-0.725-0.3-1.27c-0.326-0.595-0.957-0.92-1.264-1.049
c-0.07-0.033-0.117-0.048-0.126-0.05l-0.051-0.019l-0.063,0.019c-0.031,0.009-0.529,0.167-1.005,0.812
c-0.43,0.593-0.444,1.354-0.448,1.563c0,0.034,0,0.054,0,0.057v0.851c-0.058,0.084-0.138,0.211-0.226,0.398
c-0.131,0.324-0.141,0.624-0.145,0.804v2.465h-0.5v23.151H4.646c-1.702-3.133-2.672-6.721-2.672-10.538
c0-6.106,2.475-11.635,6.477-15.639c4.003-4.002,9.531-6.479,15.639-6.479c6.108,0,11.637,2.476,15.64,6.479
c4.002,4.003,6.478,9.531,6.478,15.639c0,3.817-0.97,7.405-2.672,10.538L31.562,34.622L31.562,34.622z"/>
</svg>

After

Width:  |  Height:  |  Size: 53 KiB

View File

@ -646,8 +646,8 @@ function handleAssessmentResponse(depict_url, data) {
var reactivityCentersImgSrc = null;
if (data['assessment']['node'] !== undefined) {
functionalGroupsImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "'>";
reactivityCentersImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "'>"
functionalGroupsImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "&highlight=true'>";
reactivityCentersImgSrc = "<img width='400' src='" + data['assessment']['node']['image'] + "&highlightReactivity=true'>"
} else {
functionalGroupsImgSrc = "<img width='400' src=\"" + depict_url + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "\">";
reactivityCentersImgSrc = "<img width='400' src=\"" + depict_url + "?smiles=" + encodeURIComponent(data['assessment']['smiles']) + "\">"

View File

@ -444,6 +444,13 @@ function serializeSVG(svgElement) {
line.setAttribute("fill", style.fill);
});
svgElement.querySelectorAll("line.link_no_arrow").forEach(line => {
const style = getComputedStyle(line);
line.setAttribute("stroke", style.stroke);
line.setAttribute("stroke-width", style.strokeWidth);
line.setAttribute("fill", style.fill);
});
const serializer = new XMLSerializer();
let svgString = serializer.serializeToString(svgElement);
@ -455,7 +462,26 @@ function serializeSVG(svgElement) {
return svgString;
}
function shrinkSVG(svgSelector) {
const svg = d3.select(svgSelector);
const node = svg.node();
// Compute bounding box of everything inside the SVG
const bbox = node.getBBox();
const padding = 10;
svg.attr("viewBox",
`${bbox.x - padding} ${bbox.y - padding} ${bbox.width + 2 * padding} ${bbox.height + 2 * padding}`
)
.attr("width", bbox.width + 2 * padding)
.attr("height", bbox.height + 2 * padding);
return bbox;
}
function downloadSVG(svgElement, filename = 'chart.svg') {
shrinkSVG("#" + svgElement.id);
const svgString = serializeSVG(svgElement);
const blob = new Blob([svgString], {type: 'image/svg+xml;charset=utf-8'});
const url = URL.createObjectURL(blob);

View File

@ -22,6 +22,10 @@
<i class="glyphicon glyphicon-floppy-save"></i> Download Pathway as Image</a>
</li>
{% if meta.can_edit %}
<li>
<a class="button" data-toggle="modal" data-target="#identify_missing_rules_modal">
<i class="glyphicon glyphicon-question-sign"></i> Identify Missing Rules</a>
</li>
<li role="separator" class="divider"></li>
<li>
<a class="button" data-toggle="modal" data-target="#edit_pathway_modal">

View File

@ -0,0 +1,71 @@
{% extends "framework.html" %}
{% load static %}
{% load envipytags %}
{% block content %}
<div class="panel-group" id="reviewListAccordion">
<div class="panel panel-default">
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
Jobs
</div>
<div class="panel-body">
<p>
Job Logs Desc
</p>
</div>
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="job-accordion-link" data-toggle="collapse" data-parent="#job-accordion" href="#jobs">
Jobs
</a>
</h4>
</div>
<div id="jobs"
class="panel-collapse collapse in">
<div class="panel-body list-group-item" id="job-content">
<table class="table table-bordered table-hover">
<tr style="background-color: rgba(0, 0, 0, 0.08);">
<th scope="col">ID</th>
<th scope="col">Name</th>
<th scope="col">Status</th>
<th scope="col">Queued</th>
<th scope="col">Done</th>
<th scope="col">Result</th>
</tr>
<tbody>
{% for job in jobs %}
<tr>
<td>{{ job.task_id }}</td>
<td>{{ job.job_name }}</td>
<td>{{ job.status }}</td>
<td>{{ job.created }}</td>
<td>{{ job.done_at }}</td>
{% if job.task_result and job.task_result|is_url == True %}
<td><a href="{{ job.task_result }}">Result</a></td>
{% elif job.task_result %}
<td>{{ job.task_result|slice:"40" }}...</td>
{% else %}
<td>Empty</td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<!-- Unreviewable objects such as User / Group / Setting -->
<ul class='list-group'>
{% for obj in objects %}
{% if object_type == 'user' %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.username }}</a>
{% else %}
<a class="list-group-item" href="{{ obj.url }}">{{ obj.name }}</a>
{% endif %}
{% endfor %}
</ul>
</div>
</div>
{% endblock content %}

View File

@ -56,7 +56,7 @@
(function () {
var u = "//matomo.envipath.com/";
_paq.push(['setTrackerUrl', u + 'matomo.php']);
_paq.push(['setSiteId', '10']);
_paq.push(['setSiteId', '{{ meta.site_id }}']);
var d = document, g = d.createElement('script'), s = d.getElementsByTagName('script')[0];
g.async = true;
g.src = u + 'matomo.js';
@ -227,19 +227,21 @@
<div class="row">
<div class="col-lg-12">
<ul class="nav nav-pills nav-justified">
<li><a href="http://eawag.ch" target="_blank">
<img id="image-ealogo"
height="60"
src='{% static "/images/ealogo.gif" %}'
alt="Eawag"/>
<li>
<a href="http://ml.auckland.ac.nz" target="_blank">
<img id="image-uoalogo" height="60" src='{% static "/images/UoA-Logo-Primary-RGB-Small.png" %}'
alt="The Univserity of Auckland"/>
</a>
</li>
<li>
<a href="http://ml.auckland.ac.nz" target="_blank">
<img id="image-uoalogo"
height="60"
src='{% static "/images/uoa.png" %}'
alt="The Univserity of Auckland"/>
<a href="https://eawag.ch" target="_blank">
<img id="image-ealogo" height="60" src='{% static "/images/ealogo.gif" %}' alt="Eawag"/>
</a>
</li>
<li>
<a href="https://www.uzh.ch/" target="_blank">
<img id="image-ufzlogo" height="60" src='{% static "/images/uzh-logo.svg" %}'
alt="University of Zurich"/>
</a>
</li>
</ul>

View File

@ -19,13 +19,19 @@
prediction. You just need to set a name and the packages
you want the object to be based on. There are multiple types of models available.
For additional information have a look at our
<a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki &gt;&gt;</a>
<a target="_blank" href="https://wiki.envipath.org/index.php/relative-reasoning" role="button">wiki
&gt;&gt;</a>
</div>
<!-- Name -->
<label for="model-name">Name</label>
<input id="model-name" name="model-name" class="form-control" placeholder="Name"/>
<!-- Description -->
<label for="model-description">Description</label>
<input id="model-description" name="model-description" class="form-control"
placeholder="Description"/>
<!-- Model Type -->
<label for="model-type">Model Type</label>
<select id="model-type" name="model-type" class="form-control" data-width='100%'>
<option disabled selected>Select Model Type</option>
@ -33,12 +39,12 @@
<option value="{{ v }}">{{ k }}</option>
{% endfor %}
</select>
<!-- ML and Rule Based Based Form-->
<div id="package-based-relative-reasoning-specific-form">
<!-- Rule Packages -->
<label for="package-based-relative-reasoning-rule-packages">Rule Packages</label>
<select id="package-based-relative-reasoning-rule-packages" name="package-based-relative-reasoning-rule-packages"
data-actions-box='true' class="form-control" multiple data-width='100%'>
<div id="rule-packages" class="ep-model-param mlrr rbrr">
<label for="model-rule-packages">Rule Packages</label>
<select id="model-rule-packages" name="model-rule-packages" data-actions-box='true'
class="form-control" multiple data-width='100%'>
<option disabled>Reviewed Packages</option>
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
@ -53,10 +59,13 @@
{% endif %}
{% endfor %}
</select>
</div>
<!-- Data Packages -->
<label for="package-based-relative-reasoning-data-packages" >Data Packages</label>
<select id="package-based-relative-reasoning-data-packages" name="package-based-relative-reasoning-data-packages"
data-actions-box='true' class="form-control" multiple data-width='100%'>
<div id="data-packages" class="ep-model-param mlrr rbrr enviformer">
<label for="model-data-packages">Data Packages</label>
<select id="model-data-packages" name="model-data-packages" data-actions-box='true'
class="form-control" multiple data-width='100%'>
<option disabled>Reviewed Packages</option>
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
@ -71,32 +80,31 @@
{% endif %}
{% endfor %}
</select>
</div>
<div id="ml-relative-reasoning-specific-form">
<!-- Fingerprinter -->
<label for="ml-relative-reasoning-fingerprinter">Fingerprinter</label>
<select id="ml-relative-reasoning-fingerprinter" name="ml-relative-reasoning-fingerprinter"
class="form-control">
<div id="fingerprinter" class="ep-model-param mlrr">
<label for="model-fingerprinter">Fingerprinter</label>
<select id="model-fingerprinter" name="model-fingerprinter" data-actions-box='true'
class="form-control" multiple data-width='100%'>
<option value="MACCS" selected>MACCS Fingerprinter</option>
</select>
{% if meta.enabled_features.PLUGINS and additional_descriptors %}
<!-- Property Plugins go here -->
<label for="ml-relative-reasoning-additional-fingerprinter">Additional Fingerprinter /
Descriptors</label>
<select id="ml-relative-reasoning-additional-fingerprinter"
name="ml-relative-reasoning-additional-fingerprinter" class="form-control">
<option disabled selected>Select Additional Fingerprinter / Descriptor</option>
{% for k, v in additional_descriptors.items %}
<option value="{{ v }}">{{ k }}</option>
{% endfor %}
</select>
{% endif %}
<label for="ml-relative-reasoning-threshold">Threshold</label>
<input type="number" min="0" max="1" step="0.05" value="0.5"
id="ml-relative-reasoning-threshold"
name="ml-relative-reasoning-threshold" class="form-control">
</select>
</div>
<!-- Threshold -->
<div id="threshold" class="ep-model-param mlrr enviformer">
<label for="model-threshold">Threshold</label>
<input type="number" min="0" max="1" step="0.05" value="0.5" id="model-threshold"
name="model-threshold" class="form-control">
</div>
<div id="appdomain" class="ep-model-param mlrr">
{% if meta.enabled_features.APPLICABILITY_DOMAIN %}
<!-- Build AD? -->
<div class="checkbox">
@ -108,11 +116,13 @@
<div id="ad-params" style="display:none">
<!-- Num Neighbors -->
<label for="num-neighbors">Number of Neighbors</label>
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control" value="5"
<input id="num-neighbors" name="num-neighbors" type="number" class="form-control"
value="5"
step="1" min="0" max="10">
<!-- Local Compatibility -->
<label for="local-compatibility-threshold">Local Compatibility Threshold</label>
<input id="local-compatibility-threshold" name="local-compatibility-threshold" type="number"
<input id="local-compatibility-threshold" name="local-compatibility-threshold"
type="number"
class="form-control" value="0.5" step="0.01" min="0" max="1">
<!-- Reliability -->
<label for="reliability-threshold">Reliability Threshold</label>
@ -121,12 +131,6 @@
</div>
{% endif %}
</div>
<!-- EnviFormer-->
<div id="enviformer-specific-form">
<label for="enviformer-threshold">Threshold</label>
<input type="number" min="0" max="1" step="0.05" value="0.5" id="enviformer-threshold"
name="enviformer-threshold" class="form-control">
</div>
</form>
</div>
<div class="modal-footer">
@ -139,19 +143,22 @@
<script>
$(function () {
// Built in Model Types
var nativeModelTypes = [
"mlrr",
"rbrr",
"enviformer",
]
// Initially hide all "specific" forms
$("div[id$='-specific-form']").each( function() {
$(".ep-model-param").each(function () {
$(this).hide();
});
$('#model-type').selectpicker();
$("#ml-relative-reasoning-fingerprinter").selectpicker();
$("#package-based-relative-reasoning-rule-packages").selectpicker();
$("#package-based-relative-reasoning-data-packages").selectpicker();
$("#package-based-relative-reasoning-evaluation-packages").selectpicker();
if ($('#ml-relative-reasoning-additional-fingerprinter').length > 0) {
$("#ml-relative-reasoning-additional-fingerprinter").selectpicker();
}
$("#model-fingerprinter").selectpicker();
$("#model-rule-packages").selectpicker();
$("#model-data-packages").selectpicker();
$("#build-app-domain").change(function () {
if ($(this).is(":checked")) {
@ -163,18 +170,12 @@ $(function() {
// On change hide all and show only selected
$("#model-type").change(function () {
$("div[id$='-specific-form']").each( function() {
$(this).hide();
});
val = $('option:selected', this).val();
if (val === 'ml-relative-reasoning' || val === 'rule-based-relative-reasoning') {
$("#package-based-relative-reasoning-specific-form").show();
if (val === 'ml-relative-reasoning') {
$("#ml-relative-reasoning-specific-form").show();
}
$('.ep-model-param').hide();
var modelType = $('#model-type').val();
if (nativeModelTypes.indexOf(modelType) !== -1) {
$('.' + modelType).show();
} else {
$("#" + val + "-specific-form").show();
// do nothing
}
});
@ -184,7 +185,4 @@ $(function() {
});
});
</script>

View File

@ -18,10 +18,10 @@
For evaluation, you need to select the packages you want to use.
While the model is evaluating, you can use the model for predictions.
</div>
<!-- Evaluation -->
<label for="relative-reasoning-evaluation-packages">Evaluation Packages</label>
<select id="relative-reasoning-evaluation-packages" name=relative-reasoning-evaluation-packages"
data-actions-box='true' class="form-control" multiple data-width='100%'>
<!-- Evaluation Packages -->
<label for="model-evaluation-packages">Evaluation Packages</label>
<select id="model-evaluation-packages" name="model-evaluation-packages" data-actions-box='true'
class="form-control" multiple data-width='100%'>
<option disabled>Reviewed Packages</option>
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
@ -36,6 +36,15 @@
{% endif %}
{% endfor %}
</select>
<!-- Eval Type -->
<label for="model-evaluation-type">Evaluation Type</label>
<select id="model-evaluation-type" name="model-evaluation-type" class="form-control">
<option disabled selected>Select evaluation type</option>
<option value="sg">Single Generation</option>
<option value="mg">Multiple Generations</option>
</select>
<input type="hidden" name="hidden" value="evaluate">
</form>
</div>
@ -51,7 +60,7 @@
$(function () {
$("#relative-reasoning-evaluation-packages").selectpicker();
$("#model-evaluation-packages").selectpicker();
$('#evaluate_model_form_submit').on('click', function (e) {
e.preventDefault();

View File

@ -19,6 +19,7 @@
<select id="scenario-select" name="selected-scenarios" data-actions-box='true' class="form-control"
multiple data-width='100%'>
<option disabled>Select Scenarios</option>
<option value="" hidden></option>
</select>
</form>
</div>
@ -66,7 +67,7 @@
$('#set_scenario_modal_form_submit').on('click', function (e) {
e.preventDefault();
if ($('#scenario-select').val().length == 0) {
$('#scenario-select').val([''])
$('#scenario-select').val("")
}
$('#set_scenario_modal_form').submit();
});

View File

@ -0,0 +1,54 @@
{% load static %}
<!-- Identify Missing Rules -->
<div id="identify_missing_rules_modal" class="modal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h3 class="modal-title">Identify Missing Rules</h3>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
By clicking on Download we'll search the Pathway for Reactions that are not backed by
a Rule or which can be assembled by chaining two rules.
<form id="identify-missing-rules-modal-form" accept-charset="UTF-8" action="{{ pathway.url }}"
data-remote="true" method="GET">
<label for="rule-package">Select the Rule Package</label>
<select id="rule-package" name="rule-package" data-actions-box='true' class="form-control"
data-width='100%'>
<option disabled>Reviewed Packages</option>
{% for obj in meta.readable_packages %}
{% if obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name }}</option>
{% endif %}
{% endfor %}
<option disabled>Unreviewed Packages</option>
{% for obj in meta.readable_packages %}
{% if not obj.reviewed %}
<option value="{{ obj.url }}">{{ obj.name }}</option>
{% endif %}
{% endfor %}
</select>
<input type="hidden" name="identify-missing-rules" value="true"/>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary" id="identify-missing-rules-modal-submit">Download</button>
</div>
</div>
</div>
</div>
<script>
$(function () {
$('#identify-missing-rules-modal-submit').click(function (e) {
e.preventDefault();
$('#identify-missing-rules-modal-form').submit();
$('#identify_missing_rules_modal').modal('hide');
});
})
</script>

View File

@ -88,6 +88,7 @@
</div>
{% endif %}
{% if rule.enzymelinks %}
<!-- EC Numbers -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
@ -95,12 +96,33 @@
href="#rule-ec-numbers">EC Numbers</a>
</h4>
</div>
<div id="rule-ec-numbers" class="panel-collapse collapse">
<div id="rule-ec-numbers" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for k, v in rule.get_grouped_enzymelinks.items %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="{{ k|slugify }}_Link" data-toggle="collapse"
data-parent="#{{ k|slugify }}_Accordion"
href="#{{ k|slugify }}">
{{ k }}
</a>
</h4>
</div>
<div id="{{ k|slugify }}" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for enzyme in v %}
<a class="list-group-item" href="{{ enzyme.url }}">
{{ enzyme.ec_number }}
<div style="position:absolute;bottom:10px;left:100px;">{{ enzyme.name }}</div>
<div style="float:right;">{{ enzyme.linking_method }}</div>
</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@ -184,7 +184,7 @@
</div>
<div id="compound-external-identifier" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% if compound.get_pubchem_identifiers %}
{% if compound.get_pubchem_compound_identifiers %}
<div class="panel panel-default panel-heading list-group-item"
style="background-color:silver">
<h4 class="panel-title">
@ -194,12 +194,28 @@
</h4>
</div>
<div id="compound-pubchem-identifier" class="panel-collapse collapse in">
{% for eid in compound.get_pubchem_identifiers %}
{% for eid in compound.get_pubchem_compound_identifiers %}
<a class="list-group-item"
href="{{ eid.external_url }}">CID{{ eid.identifier_value }}</a>
{% endfor %}
</div>
{% endif %}
{% if compound.get_pubchem_substance_identifiers %}
<div class="panel panel-default panel-heading list-group-item"
style="background-color:silver">
<h4 class="panel-title">
<a id="compound-pubchem-identifier-link" data-toggle="collapse"
data-parent="#compound-external-identifier"
href="#compound-pubchem-identifier">PubChem Substance Identifier</a>
</h4>
</div>
<div id="compound-pubchem-identifier" class="panel-collapse collapse in">
{% for eid in compound.get_pubchem_substance_identifiers %}
<a class="list-group-item"
href="{{ eid.external_url }}">SID{{ eid.identifier_value }}</a>
{% endfor %}
</div>
{% endif %}
{% if compound.get_chebi_identifiers %}
<div class="panel panel-default panel-heading list-group-item"
style="background-color:silver">

View File

@ -0,0 +1,105 @@
{% extends "framework.html" %}
{% block content %}
<div class="panel-group" id="enzyme-detail">
<div class="panel panel-default">
<div class="panel-heading" id="headingPanel" style="font-size:2rem;height: 46px">
{{ enzymelink.ec_number }}
</div>
<!-- Name -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="enzyme-name-link" data-toggle="collapse" data-parent="#enzyme-detail"
href="#enzyme-name">Enzyme Name</a>
</h4>
</div>
<div id="enzyme-name" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ enzymelink.name }}
</div>
</div>
<!-- Linking Method -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="enzyme-linking-link" data-toggle="collapse" data-parent="#enzyme-detail"
href="#enzyme-linking">Linking Method</a>
</h4>
</div>
<div id="enzyme-linking" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{{ enzymelink.linking_method }}. &nbsp;<a
href="https://wiki.envipath.org/index.php/Rules#EnzymeLinks" target="#">Learn more &gt;&gt;</a>
</div>
</div>
{% if enzymelink.kegg_reaction_links %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="enzyme-evidence-link" data-toggle="collapse" data-parent="#enzyme-detail"
href="#enzyme-evidence">Linking Evidence</a>
</h4>
</div>
<div id="enzyme-evidence" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for kl in enzymelink.kegg_reaction_links %}
<a class="list-group-item"
href="{{ kl.external_url }}">{{ kl.identifier_value }}</a>
{% endfor %}
</div>
</div>
{% endif %}
{% if enzymelink.reaction_evidence.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="enzyme-reaction-evidence-link" data-toggle="collapse" data-parent="#enzyme-detail"
href="#enzyme-reaction-evidence">Linking Evidence - enviPath Reactions</a>
</h4>
</div>
<div id="enzyme-reaction-evidence" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for r in enzymelink.reaction_evidence.all %}
<a class="list-group-item" href="{{ r.url }}">{{ r.name }} <i>({{ r.package.name }})</i></a>
{% endfor %}
</div>
</div>
{% endif %}
{% if enzymelink.edge_evidence.all %}
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="enzyme-edge-evidence-link" data-toggle="collapse" data-parent="#enzyme-detail"
href="#enzyme-edge-evidence">Linking Evidence - enviPath Pathways</a>
</h4>
</div>
<div id="enzyme-edge-evidence" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for e in enzymelink.edge_evidence.all %}
<a class="list-group-item" href="{{ e.pathway.url }}">{{ e.pathway.name }}
<i>({{ r.package.name }})</i></a>
{% endfor %}
</div>
</div>
{% endif %}
<!-- External DB Reference -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="enzyme-external-identifier-link" data-toggle="collapse" data-parent="#enzyme-detail"
href="#enzyme-external-identifier">External DB References</a>
</h4>
</div>
<div id="enzyme-external-identifier" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
<a class="list-group-item"
href="http://www.brenda-enzymes.org/enzyme.php?ecno={{ enzymelink.ec_number }}"
target="_blank"> Brenda entry for {{ enzymelink.ec_number }}</a>
</div>
</div>
</div>
</div>
{% endblock content %}

View File

@ -117,7 +117,7 @@
<!-- End Predict Panel -->
{% endif %}
{% if model.app_domain %}
{% if model.ready_for_prediction and model.app_domain %}
<!-- App Domain -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">

View File

@ -84,6 +84,7 @@
{% include "modals/objects/add_pathway_edge_modal.html" %}
{% include "modals/objects/download_pathway_csv_modal.html" %}
{% include "modals/objects/download_pathway_image_modal.html" %}
{% include "modals/objects/identify_missing_rules_modal.html" %}
{% include "modals/objects/generic_copy_object_modal.html" %}
{% include "modals/objects/edit_pathway_modal.html" %}
{% include "modals/objects/generic_set_aliases_modal.html" %}
@ -178,9 +179,6 @@
</nav>
<div id="vizdiv" >
<svg id="pwsvg">
{% if debug %}
<rect width="100%" height="100%" fill="aliceblue"/>
{% endif %}
<defs>
<marker id="arrow" viewBox="0 0 10 10" refX="43" refY="5" markerWidth="6" markerHeight="6"
orient="auto-start-reverse" markerUnits="userSpaceOnUse">

View File

@ -125,6 +125,23 @@
</div>
{% endif %}
{% if reaction.get_related_enzymes %}
<!-- EC Numbers -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-ec-numbers-link" data-toggle="collapse" data-parent="#rule-detail"
href="#rule-ec-numbers">EC Numbers</a>
</h4>
</div>
<div id="rule-ec-numbers" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for e in reaction.get_related_enzymes %}
<a class="list-group-item" href="http://www.brenda-enzymes.org/enzyme.php?ecno={{ e.ec_number }}">{{ e.name }}</a>
{% endfor %}
</div>
</div>
{% endif %}
{% if reaction.related_pathways %}
<!-- Pathways -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">

View File

@ -202,6 +202,43 @@
</div>
</div>
{% endif %}
{% if rule.enzymelinks %}
<!-- EC Numbers -->
<div class="panel panel-default panel-heading list-group-item" style="background-color:silver">
<h4 class="panel-title">
<a id="rule-ec-numbers-link" data-toggle="collapse" data-parent="#rule-detail"
href="#rule-ec-numbers">EC Numbers</a>
</h4>
</div>
<div id="rule-ec-numbers" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for k, v in rule.get_grouped_enzymelinks.items %}
<div class="panel panel-default panel-heading list-group-item"
style="background-color:silver">
<h4 class="panel-title">
<a id="{{ k|slugify }}_Link" data-toggle="collapse"
data-parent="#{{ k|slugify }}_Accordion"
href="#{{ k|slugify }}">
{{ k }}
</a>
</h4>
</div>
<div id="{{ k|slugify }}" class="panel-collapse collapse in">
<div class="panel-body list-group-item">
{% for enzyme in v %}
<a class="list-group-item" href="{{ enzyme.url }}">
{{ enzyme.ec_number }}
<div style="position:absolute;bottom:10px;left:100px;">{{ enzyme.name }}</div>
<div style="float:right;">{{ enzyme.linking_method }}</div>
</a>
{% endfor %}
</div>
</div>
{% endfor %}
</div>
</div>
{% endif %}
</div>
</div>
{% endblock content %}

View File

@ -1,7 +1,27 @@
from collections import defaultdict
from datetime import datetime
from tempfile import TemporaryDirectory
from django.test import TestCase, tag
from epdb.logic import PackageManager
from epdb.models import User, EnviFormer, Package
from epdb.models import User, EnviFormer, Package, Setting
from epdb.tasks import predict_simple, predict
def measure_predict(mod, pathway_pk=None):
# Measure and return the prediction time
start = datetime.now()
if pathway_pk:
s = Setting()
s.model = mod
s.model_threshold = 0.2
s.max_depth = 4
s.max_nodes = 20
s.save()
pred_result = predict.delay(pathway_pk, s.pk, limit=s.max_depth)
else:
pred_result = predict_simple.delay(mod.pk, "C1=CC=C(CSCC2=CC=CC=C2)C=C1")
_ = pred_result.get()
return round((datetime.now() - start).total_seconds(), 2)
@tag("slow")
@ -28,8 +48,41 @@ class EnviFormerTest(TestCase):
mod.build_dataset()
mod.build_model()
mod.multigen_eval = True
mod.save()
mod.evaluate_model()
mod.evaluate_model(True, eval_packages_objs)
mod.predict("CCN(CC)C(=O)C1=CC(=CC=C1)C")
def test_predict_runtime(self):
with TemporaryDirectory() as tmpdir:
with self.settings(MODEL_DIR=tmpdir):
threshold = float(0.5)
data_package_objs = [self.BBD_SUBSET]
eval_packages_objs = [self.BBD_SUBSET]
mods = []
for _ in range(4):
mod = EnviFormer.create(
self.package, data_package_objs, eval_packages_objs, threshold=threshold
)
mod.build_dataset()
mod.build_model()
mods.append(mod)
# Test prediction time drops after first prediction
times = [measure_predict(mods[0]) for _ in range(5)]
print(f"First prediction took {times[0]} seconds, subsequent ones took {times[1:]}")
# Test pathway prediction
times = [measure_predict(mods[1], self.BBD_SUBSET.pathways[0].pk) for _ in range(5)]
print(
f"First pathway prediction took {times[0]} seconds, subsequent ones took {times[1:]}"
)
# Test eviction by performing three prediction with every model, twice.
times = defaultdict(list)
for _ in range(
2
): # Eviction should cause the second iteration here to have to reload the models
for mod in mods:
for _ in range(3):
times[mod.pk].append(measure_predict(mod))
print(times)

View File

@ -30,7 +30,6 @@ class ModelTest(TestCase):
self.package,
rule_package_objs,
data_package_objs,
eval_packages_objs,
threshold=threshold,
name="ECC - BBD - 0.5",
description="Created MLRelativeReasoning in Testcase",
@ -50,9 +49,7 @@ class ModelTest(TestCase):
mod.build_dataset()
mod.build_model()
mod.multigen_eval = True
mod.save()
mod.evaluate_model()
mod.evaluate_model(True, eval_packages_objs)
results = mod.predict("CCN(CC)C(=O)C1=CC(=CC=C1)C")

View File

@ -6,7 +6,7 @@ from epdb.logic import UserManager
from epdb.models import Package, User
@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models")
@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True)
class PathwayViewTest(TestCase):
fixtures = ["test_fixtures_incl_model.jsonl.gz"]

View File

@ -6,7 +6,7 @@ from epdb.logic import UserManager, PackageManager
from epdb.models import Pathway, Edge
@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models")
@override_settings(MODEL_DIR=s.FIXTURE_DIRS[0] / "models", CELERY_TASK_ALWAYS_EAGER=True)
class PathwayViewTest(TestCase):
fixtures = ["test_fixtures_incl_model.jsonl.gz"]

View File

@ -185,7 +185,7 @@ class FormatConverter(object):
return smiles
@staticmethod
def standardize(smiles, remove_stereo=False):
def standardize(smiles, remove_stereo=False, canonicalize_tautomers=False):
# Taken from https://bitsilla.com/blog/2021/06/standardizing-a-molecule-using-rdkit/
# follows the steps in
# https://github.com/greglandrum/RSC_OpenScience_Standardization_202104/blob/main/MolStandardize%20pieces.ipynb
@ -203,19 +203,21 @@ class FormatConverter(object):
uncharger = (
rdMolStandardize.Uncharger()
) # annoying, but necessary as no convenience method exists
uncharged_parent_clean_mol = uncharger.uncharge(parent_clean_mol)
res_mol = uncharger.uncharge(parent_clean_mol)
# note that no attempt is made at reionization at this step
# nor at ionization at some pH (rdkit has no pKa caculator)
# the main aim to to represent all molecules from different sources
# in a (single) standard way, for use in ML, catalogue, etc.
# te = rdMolStandardize.TautomerEnumerator() # idem
# taut_uncharged_parent_clean_mol = te.Canonicalize(uncharged_parent_clean_mol)
if remove_stereo:
Chem.RemoveStereochemistry(uncharged_parent_clean_mol)
Chem.RemoveStereochemistry(res_mol)
return Chem.MolToSmiles(uncharged_parent_clean_mol, kekuleSmiles=True)
if canonicalize_tautomers:
te = rdMolStandardize.TautomerEnumerator() # idem
res_mol = te.Canonicalize(res_mol)
return Chem.MolToSmiles(res_mol, kekuleSmiles=True)
@staticmethod
def neutralize_smiles(smiles):
@ -363,6 +365,76 @@ class FormatConverter(object):
return parsed_smiles, errors
@staticmethod
def smiles_covered_by(
l_smiles: List[str],
r_smiles: List[str],
standardize: bool = True,
canonicalize_tautomers: bool = True,
) -> bool:
"""
Check if all SMILES in the left list are covered by (contained in) the right list.
This function performs a subset check to determine if every chemical structure
represented in l_smiles has a corresponding representation in r_smiles.
Args:
l_smiles (List[str]): List of SMILES strings to check for coverage.
r_smiles (List[str]): List of SMILES strings that should contain all l_smiles.
standardize (bool, optional): Whether to standardize SMILES before comparison.
Defaults to True. When True, applies FormatConverter.standardize() to
normalize representations for accurate comparison.
canonicalize_tautomers (bool, optional): Whether to canonicalize tautomers
Defaults to False. When True, applies rdMolStandardize.TautomerEnumerator().Canonicalize(res_mol)
to the compounds before comparison.
Returns:
bool: True if all SMILES in l_smiles are found in r_smiles (i.e., l_smiles
is a subset of r_smiles), False otherwise.
Note:
- Comparison treats lists as sets, ignoring duplicates and order
- Failed standardization attempts are silently ignored (original SMILES used)
- This is a one-directional check: l_smiles ⊆ r_smiles
- For bidirectional equality, both directions must be checked separately
Example:
>>> FormatConverter.smiles_covered_by(["CCO", "CC"], ["CCO", "CC", "CCC"])
True
>>> FormatConverter.smiles_covered_by(["CCO", "CCCC"], ["CCO", "CC", "CCC"])
False
"""
standardized_l_smiles = []
if standardize:
for smi in l_smiles:
try:
smi = FormatConverter.standardize(
smi, canonicalize_tautomers=canonicalize_tautomers
)
except Exception:
# :shrug:
# logger.debug(f'Standardizing SMILES failed for {smi}')
pass
standardized_l_smiles.append(smi)
else:
standardized_l_smiles = l_smiles
standardized_r_smiles = []
if standardize:
for smi in r_smiles:
try:
smi = FormatConverter.standardize(smi)
except Exception:
# :shrug:
# logger.debug(f'Standardizing SMILES failed for {smi}')
pass
standardized_r_smiles.append(smi)
else:
standardized_r_smiles = r_smiles
return len(set(standardized_l_smiles).difference(set(standardized_r_smiles))) == 0
class Standardizer(ABC):
def __init__(self, name):
@ -729,6 +801,7 @@ class IndigoUtils(object):
height: int = 0,
educt_functional_groups: Dict[str, int] = None,
product_functional_groups: Dict[str, int] = None,
debug: bool = False,
):
if educt_functional_groups is None:
educt_functional_groups = {}
@ -739,6 +812,11 @@ class IndigoUtils(object):
i = Indigo()
renderer = IndigoRenderer(i)
if debug:
i.setOption("render-atom-ids-visible", True)
i.setOption("render-bond-ids-visible", False)
i.setOption("render-atom-bond-ids-from-one", True)
i.setOption("render-output-format", "svg")
i.setOption("render-coloring", True)
i.setOption("render-image-size", width, height)

View File

@ -9,36 +9,37 @@ from collections import defaultdict
from datetime import datetime
from enum import Enum
from types import NoneType
from typing import Dict, Any, List
from typing import Any, Dict, List
from django.db import transaction
from envipy_additional_information import Interval, EnviPyModel
from envipy_additional_information import NAME_MAPPING
from envipy_additional_information import NAME_MAPPING, EnviPyModel, Interval
from pydantic import BaseModel, HttpUrl
from epdb.models import (
Package,
Compound,
CompoundStructure,
SimpleRule,
Edge,
EnviFormer,
EPModel,
ExternalDatabase,
ExternalIdentifier,
License,
MLRelativeReasoning,
Node,
Package,
ParallelRule,
Pathway,
PluginModel,
Reaction,
Rule,
RuleBasedRelativeReasoning,
Scenario,
SequentialRule,
SimpleAmbitRule,
SimpleRDKitRule,
ParallelRule,
SequentialRule,
Reaction,
Pathway,
Node,
Edge,
Scenario,
EPModel,
MLRelativeReasoning,
RuleBasedRelativeReasoning,
EnviFormer,
PluginModel,
ExternalIdentifier,
ExternalDatabase,
License,
SimpleRule,
)
from utilities.chem import FormatConverter
logger = logging.getLogger(__name__)
@ -48,7 +49,7 @@ class HTMLGenerator:
@staticmethod
def generate_html(additional_information: "EnviPyModel", prefix="") -> str:
from typing import get_origin, get_args, Union
from typing import Union, get_args, get_origin
if isinstance(additional_information, type):
clz_name = additional_information.__name__
@ -1171,3 +1172,89 @@ class PackageImporter:
url=identifier_data.get("url", ""),
is_primary=identifier_data.get("is_primary", False),
)
class PathwayUtils:
def __init__(self, pathway: "Pathway"):
self.pathway = pathway
@staticmethod
def _get_products(smiles: str, rules: List["Rule"]):
educt_rule_products: Dict[str, Dict[str, List[str]]] = defaultdict(
lambda: defaultdict(list)
)
for r in rules:
product_sets = r.apply(smiles)
for product_set in product_sets:
for product in product_set:
educt_rule_products[smiles][r.url].append(product)
return educt_rule_products
def find_missing_rules(self, rules: List["Rule"]):
print(f"Processing {self.pathway.name}")
# compute products for each node / rule combination in the pathway
educt_rule_products = defaultdict(lambda: defaultdict(list))
for node in self.pathway.nodes:
educt_rule_products.update(**self._get_products(node.default_node_label.smiles, rules))
# loop through edges and determine reactions that can't be constructed by
# any of the rules or a combination of two rules in a chained fashion
res: Dict[str, List["Rule"]] = dict()
for edge in self.pathway.edges:
found = False
reaction = edge.edge_label
educts = [cs for cs in reaction.educts.all()]
products = [cs.smiles for cs in reaction.products.all()]
rule_chain = []
for educt in educts:
educt = educt.smiles
triggered_rules = list(educt_rule_products.get(educt, {}).keys())
for triggered_rule in triggered_rules:
if rule_products := educt_rule_products[educt][triggered_rule]:
# check if this rule covers the reaction
if FormatConverter.smiles_covered_by(
products, rule_products, standardize=True, canonicalize_tautomers=True
):
found = True
else:
# Check if another prediction step would cover the reaction
for product in rule_products:
prod_rule_products = self._get_products(product, rules)
prod_triggered_rules = list(
prod_rule_products.get(product, {}).keys()
)
for prod_triggered_rule in prod_triggered_rules:
if second_step_products := prod_rule_products[product][
prod_triggered_rule
]:
if FormatConverter.smiles_covered_by(
products,
second_step_products,
standardize=True,
canonicalize_tautomers=True,
):
rule_chain.append(
(
triggered_rule,
Rule.objects.get(url=triggered_rule).name,
)
)
rule_chain.append(
(
prod_triggered_rule,
Rule.objects.get(url=prod_triggered_rule).name,
)
)
res[edge.url] = rule_chain
if not found:
res[edge.url] = rule_chain
return res