# HG changeset patch # User Tuomo Valkonen # Date 1738628536 18000 # Node ID b3c35d16affeb46d27d4b70e76199db83cb96eee # Parent d14c877e14b70dd4c1ddc9436e4095e84964ef20# Parent 8513a10c5dd937c3fed7159cf9ca04ef46c2a981 merge dev to default diff -r d14c877e14b7 -r b3c35d16affe Cargo.lock --- a/Cargo.lock Tue Feb 20 12:33:16 2024 -0500 +++ b/Cargo.lock Mon Feb 03 19:22:16 2025 -0500 @@ -1,11 +1,12 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] name = "alg_tools" -version = "0.1.0" +version = "0.3.0" dependencies = [ + "anyhow", "colored", "cpu-time", "csv", @@ -17,10 +18,16 @@ "rayon", "serde", "serde_json", - "trait-set", + "simba", ] [[package]] +name = "anyhow" +version = "1.0.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04" + +[[package]] name = "approx" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -31,47 +38,22 @@ [[package]] name = "autocfg" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" - -[[package]] -name = "bitflags" -version = "2.4.0" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" - -[[package]] -name = "bstr" -version = "0.2.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" -dependencies = [ - "lazy_static", - "memchr", - "regex-automata", - "serde", -] +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "bytemuck" -version = "1.14.0" +version = "1.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" [[package]] name = "colored" -version = "2.0.4" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2674ec482fbc38012cf31e6c42ba0177b431a0cb6f15fe40efa5aab1bda516f6" +checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" dependencies = [ - "is-terminal", "lazy_static", "windows-sys", ] @@ -88,46 +70,37 @@ [[package]] name = "crossbeam-deque" -version = "0.8.3" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" dependencies = [ - "cfg-if", "crossbeam-epoch", "crossbeam-utils", ] [[package]] name = "crossbeam-epoch" -version = "0.9.15" +version = "0.9.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" dependencies = [ - "autocfg", - "cfg-if", "crossbeam-utils", - "memoffset", - "scopeguard", ] [[package]] name = "crossbeam-utils" -version = "0.8.16" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" -dependencies = [ - "cfg-if", -] +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "csv" -version = "1.1.6" +version = "1.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22813a6dc45b335f9bade10bf7271dc477e81113e89eb251a0bc2a8a81c536e1" +checksum = "acdc4883a9c96732e4733212c01447ebd805833b7275a73ca3ee080fd77afdaf" dependencies = [ - "bstr", "csv-core", - "itoa 0.4.8", + "itoa", "ryu", "serde", ] @@ -143,81 +116,42 @@ [[package]] name = "either" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" - -[[package]] -name = "errno" -version = "0.3.5" +version = "1.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860" -dependencies = [ - "libc", - "windows-sys", -] - -[[package]] -name = "hermit-abi" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d77f7ec81a6d05a3abb01ab6eb7590f6083d08449fe5a1c8b1e620283546ccb7" - -[[package]] -name = "is-terminal" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" -dependencies = [ - "hermit-abi", - "rustix", - "windows-sys", -] +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" [[package]] name = "itertools" -version = "0.10.5" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] [[package]] name = "itoa" -version = "0.4.8" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" - -[[package]] -name = "itoa" -version = "1.0.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +checksum = "d75a2a4b1b190afb6f5425f10f6a8f959d2ea0b9c2b1d79553551850539e4674" [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" -version = "0.2.149" +version = "0.2.168" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a08173bc88b7955d1b3145aa561539096c421ac8debde8cbc3612ec635fee29b" - -[[package]] -name = "linux-raw-sys" -version = "0.4.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da2479e8c062e40bf0066ffa0bc823de0a9368974af99c9f6df941d2c231e03f" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" [[package]] name = "matrixmultiply" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" dependencies = [ "autocfg", "rawpointer", @@ -225,24 +159,15 @@ [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" - -[[package]] -name = "memoffset" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" -dependencies = [ - "autocfg", -] +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "nalgebra" -version = "0.31.4" +version = "0.33.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20bd243ab3dbb395b39ee730402d2e5405e448c75133ec49cc977762c4cba3d1" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" dependencies = [ "approx", "matrixmultiply", @@ -256,20 +181,20 @@ [[package]] name = "nalgebra-macros" -version = "0.1.0" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fcc0b8149b4632adc89ac3b7b31a12fb6099a0317a4eb2ebff574ef7de7218" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn 2.0.90", ] [[package]] name = "num" -version = "0.4.1" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" dependencies = [ "num-bigint", "num-complex", @@ -281,39 +206,37 @@ [[package]] name = "num-bigint" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" dependencies = [ - "autocfg", "num-integer", "num-traits", ] [[package]] name = "num-complex" -version = "0.4.4" +version = "0.4.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ba157ca0885411de85d6ca030ba7e2a83a28636056c7c699b07c8b6f7383214" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" dependencies = [ "num-traits", ] [[package]] name = "num-integer" -version = "0.1.45" +version = "0.1.46" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" dependencies = [ - "autocfg", "num-traits", ] [[package]] name = "num-iter" -version = "0.1.43" +version = "0.1.45" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" +checksum = "1429034a0490724d0075ebb2bc9e875d6503c3cf69e235a8941aa757d83ef5bf" dependencies = [ "autocfg", "num-integer", @@ -322,11 +245,10 @@ [[package]] name = "num-rational" -version = "0.4.1" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" dependencies = [ - "autocfg", "num-bigint", "num-integer", "num-traits", @@ -334,9 +256,9 @@ [[package]] name = "num-traits" -version = "0.2.17" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -353,24 +275,24 @@ [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "proc-macro2" -version = "1.0.69" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "134c189feb4956b20f6f547d2cf727d4c0fe06722b20a0eec87ed445a97f92da" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.37" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" dependencies = [ "proc-macro2", ] @@ -383,9 +305,9 @@ [[package]] name = "rayon" -version = "1.8.0" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" dependencies = [ "either", "rayon-core", @@ -393,90 +315,66 @@ [[package]] name = "rayon-core" -version = "1.12.0" +version = "1.12.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" dependencies = [ "crossbeam-deque", "crossbeam-utils", ] [[package]] -name = "regex-automata" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c230d73fb8d8c1b9c0b3135c5142a8acee3a0558fb8db5cf1cb65f8d7862132" - -[[package]] -name = "rustix" -version = "0.38.19" +name = "ryu" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "745ecfa778e66b2b63c88a61cb36e0eea109e803b0b86bf9879fbc77c70e86ed" -dependencies = [ - "bitflags", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - -[[package]] -name = "ryu" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "safe_arch" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f398075ce1e6a179b46f51bd88d0598b92b00d3551f1a2d4ac49e771b56ac354" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" dependencies = [ "bytemuck", ] [[package]] -name = "scopeguard" -version = "1.2.0" +name = "serde" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.189" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e422a44e74ad4001bdc8eede9a4570ab52f71190e9c076d14369f38b9200537" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.189" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e48d1f918009ce3145511378cf68d613e3b3d9137d67272562080d68a2b32d5" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", - "syn 2.0.38", + "syn 2.0.90", ] [[package]] name = "serde_json" -version = "1.0.107" +version = "1.0.133" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b420ce6e3d8bd882e9b243c6eed35dbc9a6110c9769e74b584e0d68d1f20c65" +checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" dependencies = [ - "itoa 1.0.9", + "itoa", + "memchr", "ryu", "serde", ] [[package]] name = "simba" -version = "0.7.3" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f3fd720c48c53cace224ae62bef1bbff363a70c68c4802a78b5cc6159618176" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" dependencies = [ "approx", "num-complex", @@ -498,9 +396,9 @@ [[package]] name = "syn" -version = "2.0.38" +version = "2.0.90" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e96b79aaa137db8f61e26363a0c9b47d8b4ec75da28b7d1d614c2303e232408b" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" dependencies = [ "proc-macro2", "quote", @@ -508,17 +406,6 @@ ] [[package]] -name = "trait-set" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "875c4c873cc824e362fa9a9419ffa59807244824275a44ad06fec9684fff08f2" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] name = "typenum" version = "1.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -526,15 +413,15 @@ [[package]] name = "unicode-ident" -version = "1.0.12" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" [[package]] name = "wide" -version = "0.7.12" +version = "0.7.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebecebefc38ff1860b4bc47550bbfa63af5746061cf0d29fcd7fa63171602598" +checksum = "58e6db2670d2be78525979e9a5f9c69d296fd7d670549fe9ebf70f8708cb5019" dependencies = [ "bytemuck", "safe_arch", diff -r d14c877e14b7 -r b3c35d16affe Cargo.toml --- a/Cargo.toml Tue Feb 20 12:33:16 2024 -0500 +++ b/Cargo.toml Mon Feb 03 19:22:16 2025 -0500 @@ -1,8 +1,8 @@ [package] name = "alg_tools" -version = "0.1.0" +version = "0.3.0" edition = "2021" -rust-version = "1.67" +rust-version = "1.85" authors = ["Tuomo Valkonen "] description = "Utility routines for iterative numerical algorithms" homepage = "https://tuomov.iki.fi/software/alg_tools/" @@ -13,17 +13,18 @@ [dependencies] serde = { version = "1.0", features = ["derive"] } -csv = "~1.1.6" -nalgebra = "~0.31.0" +csv = "~1.3.1" +nalgebra = "~0.33.0" num-traits = { version = "~0.2.14", features = ["std"] } -colored = "~2.0.0" -trait-set = "~0.2.0" +colored = "~2.1.0" num = "~0.4.0" -itertools = "~0.10.3" +itertools = "~0.13.0" numeric_literals = "~0.2.0" cpu-time = "~1.0.0" -serde_json = "~1.0.85" +serde_json = { version = "~1.0.85", features = ["std"] } rayon = "1.5.3" +simba = "0.9.0" +anyhow = "1.0.95" [package.metadata.docs.rs] rustdoc-args = [ "--html-in-header", "katex-header.html" ] @@ -31,3 +32,9 @@ [profile.release] debug = true + +[features] +default = ["nightly"] +use_custom_thread_pool = [] +nightly = [] # enable for higher-performance implementations of some things + diff -r d14c877e14b7 -r b3c35d16affe README.md --- a/README.md Tue Feb 20 12:33:16 2024 -0500 +++ b/README.md Mon Feb 03 19:22:16 2025 -0500 @@ -8,6 +8,10 @@ * [Linear operator], [mapping], [Euclidean space], and [norm] abstractions. Matrices and vectors are supported via [nalgebra]. There is also abstraction for [`AXPY`][AXPY] and [`GEMV`][GEMV] operations. + * A facility to create [`Instance`][Instance]s of other types, for easy application + of functions to various concrete and reference types. + * Abstraction of [Fenchel conjugates] and [proximal operators] of convex + functions. * Small (on stack) [vectors] and [cubes] that implement the relevant abstractions and vector space operations. * Multi-dimensional [linear grids], including the familiar-from-Matlab @@ -56,4 +60,7 @@ [associated traits]: crate::types [vectors]: crate::loc::Loc [cubes]: crate::sets::Cube + [Instance]: crate::instance::Instance + [Fenchel conjugates]: crate::convex::Conjugable + [proximal operators]: crate::convex::Prox diff -r d14c877e14b7 -r b3c35d16affe rust-toolchain.toml --- a/rust-toolchain.toml Tue Feb 20 12:33:16 2024 -0500 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly" diff -r d14c877e14b7 -r b3c35d16affe src/bisection_tree/aggregator.rs --- a/src/bisection_tree/aggregator.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/bisection_tree/aggregator.rs Mon Feb 03 19:22:16 2025 -0500 @@ -4,6 +4,7 @@ use crate::types::*; use crate::sets::Set; +use crate::instance::Instance; /// Trait for aggregating information about a branch of a [bisection tree][super::BT]. /// @@ -136,10 +137,11 @@ } impl Set for Bounds { - fn contains(&self, item : &F) -> bool { + fn contains>(&self, item : I) -> bool { + let v = item.own(); let &Bounds(l, u) = self; debug_assert!(l <= u); - l <= *item && *item <= u + l <= v && v <= u } } diff -r d14c877e14b7 -r b3c35d16affe src/bisection_tree/btfn.rs --- a/src/bisection_tree/btfn.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/bisection_tree/btfn.rs Mon Feb 03 19:22:16 2025 -0500 @@ -4,7 +4,10 @@ use std::marker::PhantomData; use std::sync::Arc; use crate::types::Float; -use crate::mapping::{Apply, Mapping}; +use crate::mapping::{ + Instance, Mapping, DifferentiableImpl, DifferentiableMapping, Space, + BasicDecomposition, +}; //use crate::linops::{Apply, Linear}; use crate::sets::Set; use crate::sets::Cube; @@ -40,10 +43,22 @@ } impl +Space for BTFN +where + G : SupportGenerator, + G::SupportType : LocalAnalysis, + BT : BTImpl +{ + type Decomp = BasicDecomposition; +} + +impl BTFN -where G : SupportGenerator, - G::SupportType : LocalAnalysis, - BT : BTImpl { +where + G : SupportGenerator, + G::SupportType : LocalAnalysis, + BT : BTImpl +{ /// Create a new BTFN from a support generator and a pre-initialised bisection tree. /// @@ -386,42 +401,52 @@ make_btfn_unaryop!(Neg, neg); - - // -// Mapping +// Apply, Mapping, Differentiate // -impl<'a, F : Float, G, BT, V, const N : usize> Apply<&'a Loc> +impl Mapping> for BTFN -where BT : BTImpl, - G : SupportGenerator, - G::SupportType : LocalAnalysis + Apply<&'a Loc, Output = V>, - V : Sum { +where + BT : BTImpl, + G : SupportGenerator, + G::SupportType : LocalAnalysis + Mapping, Codomain = V>, + V : Sum + Space, +{ - type Output = V; + type Codomain = V; - fn apply(&self, x : &'a Loc) -> Self::Output { - self.bt.iter_at(x) - .map(|&d| self.generator.support_for(d).apply(x)).sum() + fn apply>>(&self, x : I) -> Self::Codomain { + let xc = x.cow(); + self.bt.iter_at(&*xc) + .map(|&d| self.generator.support_for(d).apply(&*xc)).sum() } } -impl Apply> +impl DifferentiableImpl> for BTFN -where BT : BTImpl, - G : SupportGenerator, - G::SupportType : LocalAnalysis + Apply, Output = V>, - V : Sum { +where + BT : BTImpl, + G : SupportGenerator, + G::SupportType : LocalAnalysis + + DifferentiableMapping, DerivativeDomain = V>, + V : Sum + Space, +{ - type Output = V; + type Derivative = V; - fn apply(&self, x : Loc) -> Self::Output { - self.bt.iter_at(&x) - .map(|&d| self.generator.support_for(d).apply(x)).sum() + fn differential_impl>>(&self, x :I) -> Self::Derivative { + let xc = x.cow(); + self.bt.iter_at(&*xc) + .map(|&d| self.generator.support_for(d).differential(&*xc)) + .sum() } } +// +// GlobalAnalysis +// + impl GlobalAnalysis for BTFN where BT : BTImpl, @@ -480,7 +505,7 @@ /// /// `U` is the domain, generally [`Loc`]``, and `F` the type of floating point numbers. /// `Self` is generally a set of `U`, for example, [`Cube`]``. -pub trait P2Minimise : Set { +pub trait P2Minimise : Set { /// Minimise `g` over the set presented by `Self`. /// /// The function returns `(x, v)` where `x` is the minimiser `v` an approximation of `g(x)`. @@ -806,7 +831,7 @@ impl BTFN where BT : BTSearch>, G : SupportGenerator, - G::SupportType : Mapping,Codomain=F> + G::SupportType : Mapping, Codomain=F> + LocalAnalysis, N>, Cube : P2Minimise, F> { diff -r d14c877e14b7 -r b3c35d16affe src/bisection_tree/either.rs --- a/src/bisection_tree/either.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/bisection_tree/either.rs Mon Feb 03 19:22:16 2025 -0500 @@ -3,8 +3,14 @@ use std::sync::Arc; use crate::types::*; -use crate::mapping::Apply; -use crate::iter::{Mappable,MapF,MapZ}; +use crate::mapping::{ + Instance, + Mapping, + DifferentiableImpl, + DifferentiableMapping, + Space, +}; +use crate::iter::{Mappable, MapF, MapZ}; use crate::sets::Cube; use crate::loc::Loc; @@ -177,12 +183,17 @@ } } -impl Apply for EitherSupport -where S1 : Apply, - S2 : Apply { - type Output = F; +impl Mapping for EitherSupport +where + F : Space, + X : Space, + S1 : Mapping, + S2 : Mapping, +{ + type Codomain = F; + #[inline] - fn apply(&self, x : X) -> F { + fn apply>(&self, x : I) -> F { match self { EitherSupport::Left(ref a) => a.apply(x), EitherSupport::Right(ref b) => b.apply(x), @@ -190,6 +201,24 @@ } } +impl DifferentiableImpl for EitherSupport +where + O : Space, + X : Space, + S1 : DifferentiableMapping, + S2 : DifferentiableMapping, +{ + type Derivative = O; + + #[inline] + fn differential_impl>(&self, x : I) -> O { + match self { + EitherSupport::Left(ref a) => a.differential(x), + EitherSupport::Right(ref b) => b.differential(x), + } + } +} + macro_rules! make_either_scalarop_rhs { ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { impl diff -r d14c877e14b7 -r b3c35d16affe src/bisection_tree/refine.rs --- a/src/bisection_tree/refine.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/bisection_tree/refine.rs Mon Feb 03 19:22:16 2025 -0500 @@ -364,8 +364,13 @@ let mut container = container_arc.lock().unwrap(); // Safe: we just created arg_b and have a mutable exclusive // reference to self containing it. + #[cfg(feature = "nightly")] unsafe { Arc::get_mut_unchecked(arc_b) } .stage_refine(domain, &mut *container); + #[cfg(not(feature = "nightly"))] + Arc::get_mut(arc_b).unwrap() + .stage_refine(domain, &mut *container); + return Err(container) }, _ => unreachable!("This cannot happen"), diff -r d14c877e14b7 -r b3c35d16affe src/bisection_tree/support.rs --- a/src/bisection_tree/support.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/bisection_tree/support.rs Mon Feb 03 19:22:16 2025 -0500 @@ -1,35 +1,23 @@ /*! -Traits for representing the support of a [`Apply`], and analysing the mapping on a [`Cube`]. +Traits for representing the support of a [`Mapping`], and analysing the mapping on a [`Cube`]. */ use serde::Serialize; use std::ops::{MulAssign,DivAssign,Neg}; use crate::types::{Float, Num}; use crate::maputil::map2; -use crate::mapping::Apply; +use crate::mapping::{ + Instance, Mapping, DifferentiableImpl, DifferentiableMapping, Space +}; use crate::sets::Cube; use crate::loc::Loc; use super::aggregator::Bounds; use crate::norms::{Norm, L1, L2, Linfinity}; - -/// A trait for encoding constant [`Float`] values -pub trait Constant : Copy + Sync + Send + 'static + std::fmt::Debug + Into { - /// The type of the value - type Type : Float; - /// Returns the value of the constant - fn value(&self) -> Self::Type; -} +pub use crate::operator_arithmetic::{Weighted, Constant}; -impl Constant for F { - type Type = F; - #[inline] - fn value(&self) -> F { *self } -} - - -/// A trait for working with the supports of [`Apply`]s. +/// A trait for working with the supports of [`Mapping`]s. /// -/// Apply is not a super-trait to allow more general use. +/// `Mapping` is not a super-trait to allow more general use. pub trait Support : Sized + Sync + Send + 'static { /// Return a cube containing the support of the function represented by `self`. /// @@ -62,15 +50,9 @@ fn shift(self, x : Loc) -> Shift { Shift { shift : x, base_fn : self } } - - /// Multiply `self` by the scalar `a`. - #[inline] - fn weigh>(self, a : C) -> Weighted { - Weighted { weight : a, base_fn : self } - } } -/// Trait for globally analysing a property `A` of a [`Apply`]. +/// Trait for globally analysing a property `A` of a [`Mapping`]. /// /// Typically `A` is an [`Aggregator`][super::aggregator::Aggregator] such as /// [`Bounds`][super::aggregator::Bounds]. @@ -91,7 +73,7 @@ // } // } -/// Trait for locally analysing a property `A` of a [`Apply`] (implementing [`Support`]) +/// Trait for locally analysing a property `A` of a [`Mapping`] (implementing [`Support`]) /// within a [`Cube`]. /// /// Typically `A` is an [`Aggregator`][super::aggregator::Aggregator] such as @@ -105,10 +87,10 @@ fn local_analysis(&self, cube : &Cube) -> A; } -/// Trait for determining the upper and lower bounds of an float-valued [`Apply`]. +/// Trait for determining the upper and lower bounds of an float-valued [`Mapping`]. /// /// This is a blanket-implemented alias for [`GlobalAnalysis`]`>` -/// [`Apply`] is not a supertrait to allow flexibility in the implementation of either +/// [`Mapping`] is not a supertrait to allow flexibility in the implementation of either /// reference or non-reference arguments. pub trait Bounded : GlobalAnalysis> { /// Return lower and upper bounds for the values of of `self`. @@ -120,28 +102,30 @@ impl>> Bounded for T { } -/// Shift of [`Support`] and [`Apply`]; output of [`Support::shift`]. +/// Shift of [`Support`] and [`Mapping`]; output of [`Support::shift`]. #[derive(Copy,Clone,Debug,Serialize)] // Serialize! but not implemented by Loc. pub struct Shift { shift : Loc, base_fn : T, } -impl<'a, T, V, F : Float, const N : usize> Apply<&'a Loc> for Shift -where T : Apply, Output=V> { - type Output = V; +impl<'a, T, V : Space, F : Float, const N : usize> Mapping> for Shift +where T : Mapping, Codomain=V> { + type Codomain = V; + #[inline] - fn apply(&self, x : &'a Loc) -> Self::Output { - self.base_fn.apply(x - &self.shift) + fn apply>>(&self, x : I) -> Self::Codomain { + self.base_fn.apply(x.own() - &self.shift) } } -impl<'a, T, V, F : Float, const N : usize> Apply> for Shift -where T : Apply, Output=V> { - type Output = V; +impl<'a, T, V : Space, F : Float, const N : usize> DifferentiableImpl> for Shift +where T : DifferentiableMapping, DerivativeDomain=V> { + type Derivative = V; + #[inline] - fn apply(&self, x : Loc) -> Self::Output { - self.base_fn.apply(x - &self.shift) + fn differential_impl>>(&self, x : I) -> Self::Derivative { + self.base_fn.differential(x.own() - &self.shift) } } @@ -200,38 +184,6 @@ impl_shift_norm!(L1 L2 Linfinity); -/// Weighting of a [`Support`] and [`Apply`] by scalar multiplication; -/// output of [`Support::weigh`]. -#[derive(Copy,Clone,Debug,Serialize)] -pub struct Weighted { - /// The weight - pub weight : C, - /// The base [`Support`] or [`Apply`] being weighted. - pub base_fn : T, -} - -impl<'a, T, V, F : Float, C, const N : usize> Apply<&'a Loc> for Weighted -where T : for<'b> Apply<&'b Loc, Output=V>, - V : std::ops::Mul, - C : Constant { - type Output = V; - #[inline] - fn apply(&self, x : &'a Loc) -> Self::Output { - self.base_fn.apply(x) * self.weight.value() - } -} - -impl<'a, T, V, F : Float, C, const N : usize> Apply> for Weighted -where T : Apply, Output=V>, - V : std::ops::Mul, - C : Constant { - type Output = V; - #[inline] - fn apply(&self, x : Loc) -> Self::Output { - self.base_fn.apply(x) * self.weight.value() - } -} - impl<'a, T, F : Float, C, const N : usize> Support for Weighted where T : Support, C : Constant { @@ -331,30 +283,21 @@ impl_weighted_norm!(L1 L2 Linfinity); -/// Normalisation of [`Support`] and [`Apply`] to L¹ norm 1. +/// Normalisation of [`Support`] and [`Mapping`] to L¹ norm 1. /// /// Currently only scalar-valued functions are supported. #[derive(Copy, Clone, Debug, Serialize, PartialEq)] pub struct Normalised( - /// The base [`Support`] or [`Apply`]. + /// The base [`Support`] or [`Mapping`]. pub T ); -impl<'a, T, F : Float, const N : usize> Apply<&'a Loc> for Normalised -where T : Norm + for<'b> Apply<&'b Loc, Output=F> { - type Output = F; +impl<'a, T, F : Float, const N : usize> Mapping> for Normalised +where T : Norm + Mapping, Codomain=F> { + type Codomain = F; + #[inline] - fn apply(&self, x : &'a Loc) -> Self::Output { - let w = self.0.norm(L1); - if w == F::ZERO { F::ZERO } else { self.0.apply(x) / w } - } -} - -impl<'a, T, F : Float, const N : usize> Apply> for Normalised -where T : Norm + Apply, Output=F> { - type Output = F; - #[inline] - fn apply(&self, x : Loc) -> Self::Output { + fn apply>>(&self, x : I) -> Self::Codomain { let w = self.0.norm(L1); if w == F::ZERO { F::ZERO } else { self.0.apply(x) / w } } @@ -449,7 +392,7 @@ : MulAssign + DivAssign + Neg + Clone + Sync + Send + 'static { /// The identification type type Id : 'static + Copy; - /// The type of the [`Support`] (often also a [`Apply`]). + /// The type of the [`Support`] (often also a [`Mapping`]). type SupportType : 'static + Support; /// An iterator over all the [`Support`]s of the generator. type AllDataIter<'a> : Iterator where Self : 'a; diff -r d14c877e14b7 -r b3c35d16affe src/collection.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/collection.rs Mon Feb 03 19:22:16 2025 -0500 @@ -0,0 +1,54 @@ +/*! +Abstract collections of objects. +*/ + +use crate::loc::Loc; + +/// An abstract collection of elements. +pub trait Collection : IntoIterator { + /// Type of elements of the collection + type Element; + /// Iterator over references to elements of the collection + type RefsIter<'a> : Iterator where Self : 'a; + + /// Returns an iterator over references to elements of the collection. + fn iter_refs(&self) -> Self::RefsIter<'_>; +} + +/// An abstract collection of mutable elements. +pub trait CollectionMut : Collection { + /// Iterator over references to elements of the collection + type RefsIterMut<'a> : Iterator where Self : 'a; + + /// Returns an iterator over references to elements of the collection. + fn iter_refs_mut(&mut self) -> Self::RefsIterMut<'_>; +} + +/// Helps implement Collection and CollectionMut for slice-like collections. +#[macro_export] +macro_rules! slice_like_collection { + ($type : ty where $($where:tt)*) => { + impl<$($where)*> Collection for $type { + type Element = E; + type RefsIter<'_a> = std::slice::Iter<'_a, E> where Self : '_a; + + #[inline] + fn iter_refs(&self) -> Self::RefsIter<'_> { + self.iter() + } + } + + impl<$($where)*> CollectionMut for $type { + type RefsIterMut<'_a> = std::slice::IterMut<'_a, E> where Self : '_a; + + #[inline] + fn iter_refs_mut(&mut self) -> Self::RefsIterMut<'_> { + self.iter_mut() + } + } + } +} + +slice_like_collection!(Vec where E); +slice_like_collection!([E; N] where E, const N : usize); +slice_like_collection!(Loc where E, const N : usize); diff -r d14c877e14b7 -r b3c35d16affe src/convex.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/convex.rs Mon Feb 03 19:22:16 2025 -0500 @@ -0,0 +1,290 @@ +/*! +Some convex analysis basics +*/ + +use std::marker::PhantomData; +use crate::types::*; +use crate::mapping::{Mapping, Space}; +use crate::linops::IdOp; +use crate::instance::{Instance, InstanceMut, DecompositionMut}; +use crate::operator_arithmetic::{Constant, Weighted}; +use crate::norms::*; + +/// Trait for convex mappings. Has no features, just serves as a constraint +/// +/// TODO: should constrain `Mapping::Codomain` to implement a partial order, +/// but this makes everything complicated with little benefit. +pub trait ConvexMapping : Mapping +{} + +/// Trait for mappings with a Fenchel conjugate +/// +/// The conjugate type has to implement [`ConvexMapping`], but a `Conjugable` mapping need +/// not be convex. +pub trait Conjugable, F : Num = f64> : Mapping { + type Conjugate<'a> : ConvexMapping where Self : 'a; + + fn conjugate(&self) -> Self::Conjugate<'_>; +} + +/// Trait for mappings with a Fenchel preconjugate +/// +/// In contrast to [`Conjugable`], the preconjugate need not implement [`ConvexMapping`], +/// but a `Preconjugable` mapping has to be convex. +pub trait Preconjugable : ConvexMapping +where + Domain : Space, + Predual : HasDual +{ + type Preconjugate<'a> : Mapping where Self : 'a; + + fn preconjugate(&self) -> Self::Preconjugate<'_>; +} + +/// Trait for mappings with a proximap map +/// +/// The conjugate type has to implement [`ConvexMapping`], but a `Conjugable` mapping need +/// not be convex. +pub trait Prox : Mapping { + type Prox<'a> : Mapping where Self : 'a; + + /// Returns a proximal mapping with weight τ + fn prox_mapping(&self, τ : Self::Codomain) -> Self::Prox<'_>; + + /// Calculate the proximal mapping with weight τ + fn prox>(&self, τ : Self::Codomain, z : I) -> Domain { + self.prox_mapping(τ).apply(z) + } + + /// Calculate the proximal mapping with weight τ in-place + fn prox_mut<'b>(&self, τ : Self::Codomain, y : &'b mut Domain) + where + &'b mut Domain : InstanceMut, + Domain:: Decomp : DecompositionMut, + for<'a> &'a Domain : Instance, + { + *y = self.prox(τ, &*y); + } +} + + +/// Constraint to the unit ball of the norm described by `E`. +pub struct NormConstraint { + radius : F, + norm : NormMapping, +} + +impl ConvexMapping for NormMapping +where + Domain : Space, + E : NormExponent, + F : Float, + Self : Mapping +{} + + +impl Mapping for NormConstraint +where + Domain : Space + Norm, + F : Float, + E : NormExponent, +{ + type Codomain = F; + + fn apply>(&self, d : I) -> F { + if d.eval(|x| x.norm(self.norm.exponent)) <= self.radius { + F::ZERO + } else { + F::INFINITY + } + } +} + +impl ConvexMapping for NormConstraint +where + Domain : Space, + E : NormExponent, + F : Float, + Self : Mapping +{} + + +impl Conjugable for NormMapping +where + E : HasDualExponent, + F : Float, + Domain : HasDual + Norm + Space, + >::DualSpace : Norm +{ + type Conjugate<'a> = NormConstraint where Self : 'a; + + fn conjugate(&self) -> Self::Conjugate<'_> { + NormConstraint { + radius : F::ONE, + norm : self.exponent.dual_exponent().as_mapping() + } + } +} + +impl Conjugable for Weighted, C> +where + C : Constant, + E : HasDualExponent, + F : Float, + Domain : HasDual + Norm + Space, + >::DualSpace : Norm +{ + type Conjugate<'a> = NormConstraint where Self : 'a; + + fn conjugate(&self) -> Self::Conjugate<'_> { + NormConstraint { + radius : self.weight.value(), + norm : self.base_fn.exponent.dual_exponent().as_mapping() + } + } +} + +impl Prox for NormConstraint +where + Domain : Space + Norm, + E : NormExponent, + F : Float, + NormProjection : Mapping, +{ + type Prox<'a> = NormProjection where Self : 'a; + + #[inline] + fn prox_mapping(&self, _τ : Self::Codomain) -> Self::Prox<'_> { + assert!(self.radius >= F::ZERO); + NormProjection{ radius : self.radius, exponent : self.norm.exponent } + } +} + +/// Projection to the unit ball of the norm described by `E`. +pub struct NormProjection { + radius : F, + exponent : E, +} + +/* +impl Mapping for NormProjection +where + Domain : Space + Euclidean + std::ops::MulAssign, + F : Float, +{ + type Codomain = Domain; + + fn apply>(&self, d : I) -> Domain { + d.own().proj_ball2(self.radius) + } +} +*/ + +impl Mapping for NormProjection +where + Domain : Space + Projection, + F : Float, + E : NormExponent, +{ + type Codomain = Domain; + + fn apply>(&self, d : I) -> Domain { + d.own().proj_ball(self.radius, self.exponent) + } +} + + +/// The zero mapping +pub struct Zero(PhantomData<(Domain, F)>); + +impl Zero { + pub fn new() -> Self { + Zero(PhantomData) + } +} + +impl Mapping for Zero { + type Codomain = F; + + /// Compute the value of `self` at `x`. + fn apply>(&self, _x : I) -> Self::Codomain { + F::ZERO + } +} + +impl ConvexMapping for Zero { } + + +impl, F : Float> Conjugable for Zero { + type Conjugate<'a> = ZeroIndicator where Self : 'a; + + #[inline] + fn conjugate(&self) -> Self::Conjugate<'_> { + ZeroIndicator::new() + } +} + +impl Preconjugable for Zero +where + Domain : Space, + Predual : HasDual +{ + type Preconjugate<'a> = ZeroIndicator where Self : 'a; + + #[inline] + fn preconjugate(&self) -> Self::Preconjugate<'_> { + ZeroIndicator::new() + } +} + +impl Prox for Zero { + type Prox<'a> = IdOp where Self : 'a; + + #[inline] + fn prox_mapping(&self, _τ : Self::Codomain) -> Self::Prox<'_> { + IdOp::new() + } +} + + +/// The zero indicator +pub struct ZeroIndicator(PhantomData<(Domain, F)>); + +impl ZeroIndicator { + pub fn new() -> Self { + ZeroIndicator(PhantomData) + } +} + +impl, F : Float> Mapping for ZeroIndicator { + type Codomain = F; + + /// Compute the value of `self` at `x`. + fn apply>(&self, x : I) -> Self::Codomain { + x.eval(|x̃| if x̃.is_zero() { F::ZERO } else { F::INFINITY }) + } +} + +impl, F : Float> ConvexMapping for ZeroIndicator { } + +impl, F : Float> Conjugable for ZeroIndicator { + type Conjugate<'a> = Zero where Self : 'a; + + #[inline] + fn conjugate(&self) -> Self::Conjugate<'_> { + Zero::new() + } +} + +impl Preconjugable for ZeroIndicator +where + Domain : Normed, + Predual : HasDual +{ + type Preconjugate<'a> = Zero where Self : 'a; + + #[inline] + fn preconjugate(&self) -> Self::Preconjugate<'_> { + Zero::new() + } +} diff -r d14c877e14b7 -r b3c35d16affe src/direct_product.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/direct_product.rs Mon Feb 03 19:22:16 2025 -0500 @@ -0,0 +1,506 @@ +/*! +Direct products of the form $A \times B$. + +TODO: This could be easily much more generic if `derive_more` could derive arithmetic +operations on references. +*/ + +use core::ops::{Mul,MulAssign,Div,DivAssign,Add,AddAssign,Sub,SubAssign,Neg}; +use std::clone::Clone; +use serde::{Serialize, Deserialize}; +use crate::types::{Num, Float}; +use crate::{maybe_lifetime, maybe_ref}; +use crate::euclidean::Euclidean; +use crate::instance::{Instance, InstanceMut, Decomposition, DecompositionMut, MyCow}; +use crate::mapping::Space; +use crate::linops::AXPY; +use crate::loc::Loc; +use crate::norms::{Norm, PairNorm, NormExponent, Normed, HasDual, L2}; + +#[derive(Debug,Clone,Copy,PartialEq,Eq,Serialize,Deserialize)] +pub struct Pair (pub A, pub B); + +impl Pair { + pub fn new(a : A, b : B) -> Pair { Pair(a, b) } +} + +impl From<(A,B)> for Pair { + #[inline] + fn from((a, b) : (A, B)) -> Pair { Pair(a, b) } +} + +impl From> for (A,B) { + #[inline] + fn from(Pair(a, b) : Pair) -> (A,B) { (a, b) } +} + +macro_rules! impl_binop { + (($a : ty, $b : ty), $trait : ident, $fn : ident, $refl:ident, $refr:ident) => { + impl_binop!(@doit: $a, $b, $trait, $fn; + maybe_lifetime!($refl, &'l Pair<$a,$b>), + (maybe_lifetime!($refl, &'l $a), + maybe_lifetime!($refl, &'l $b)); + maybe_lifetime!($refr, &'r Pair), + (maybe_lifetime!($refr, &'r Ai), + maybe_lifetime!($refr, &'r Bi)); + $refl, $refr); + }; + + (@doit: $a:ty, $b:ty, + $trait:ident, $fn:ident; + $self:ty, ($aself:ty, $bself:ty); + $in:ty, ($ain:ty, $bin:ty); + $refl:ident, $refr:ident) => { + impl<'l, 'r, Ai, Bi> $trait<$in> + for $self + where $aself: $trait<$ain>, + $bself: $trait<$bin> { + type Output = Pair<<$aself as $trait<$ain>>::Output, + <$bself as $trait<$bin>>::Output>; + + #[inline] + fn $fn(self, y : $in) -> Self::Output { + Pair(maybe_ref!($refl, self.0).$fn(maybe_ref!($refr, y.0)), + maybe_ref!($refl, self.1).$fn(maybe_ref!($refr, y.1))) + } + } + }; +} + +macro_rules! impl_assignop { + (($a : ty, $b : ty), $trait : ident, $fn : ident, $refr:ident) => { + impl_assignop!(@doit: $a, $b, + $trait, $fn; + maybe_lifetime!($refr, &'r Pair), + (maybe_lifetime!($refr, &'r Ai), + maybe_lifetime!($refr, &'r Bi)); + $refr); + }; + (@doit: $a : ty, $b : ty, + $trait:ident, $fn:ident; + $in:ty, ($ain:ty, $bin:ty); + $refr:ident) => { + impl<'r, Ai, Bi> $trait<$in> + for Pair<$a,$b> + where $a: $trait<$ain>, + $b: $trait<$bin> { + #[inline] + fn $fn(&mut self, y : $in) -> () { + self.0.$fn(maybe_ref!($refr, y.0)); + self.1.$fn(maybe_ref!($refr, y.1)); + } + } + } +} + +macro_rules! impl_scalarop { + (($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident, $refl:ident) => { + impl_scalarop!(@doit: $field, + $trait, $fn; + maybe_lifetime!($refl, &'l Pair<$a,$b>), + (maybe_lifetime!($refl, &'l $a), + maybe_lifetime!($refl, &'l $b)); + $refl); + }; + (@doit: $field : ty, + $trait:ident, $fn:ident; + $self:ty, ($aself:ty, $bself:ty); + $refl:ident) => { + // Scalar as Rhs + impl<'l> $trait<$field> + for $self + where $aself: $trait<$field>, + $bself: $trait<$field> { + type Output = Pair<<$aself as $trait<$field>>::Output, + <$bself as $trait<$field>>::Output>; + #[inline] + fn $fn(self, a : $field) -> Self::Output { + Pair(maybe_ref!($refl, self.0).$fn(a), + maybe_ref!($refl, self.1).$fn(a)) + } + } + } +} + +// Not used due to compiler overflow +#[allow(unused_macros)] +macro_rules! impl_scalarlhs_op { + (($a : ty, $b : ty), $field : ty, $trait:ident, $fn:ident, $refr:ident) => { + impl_scalarlhs_op!(@doit: $trait, $fn, + maybe_lifetime!($refr, &'r Pair<$a,$b>), + (maybe_lifetime!($refr, &'r $a), + maybe_lifetime!($refr, &'r $b)); + $refr, $field); + }; + (@doit: $trait:ident, $fn:ident, + $in:ty, ($ain:ty, $bin:ty); + $refr:ident, $field:ty) => { + impl<'r> $trait<$in> + for $field + where $field : $trait<$ain> + + $trait<$bin> { + type Output = Pair<<$field as $trait<$ain>>::Output, + <$field as $trait<$bin>>::Output>; + #[inline] + fn $fn(self, x : $in) -> Self::Output { + Pair(self.$fn(maybe_ref!($refr, x.0)), + self.$fn(maybe_ref!($refr, x.1))) + } + } + }; +} + +macro_rules! impl_scalar_assignop { + (($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident) => { + impl<'r> $trait<$field> + for Pair<$a, $b> + where $a: $trait<$field>, $b: $trait<$field> { + #[inline] + fn $fn(&mut self, a : $field) -> () { + self.0.$fn(a); + self.1.$fn(a); + } + } + } +} + +macro_rules! impl_unaryop { + (($a : ty, $b : ty), $trait:ident, $fn:ident, $refl:ident) => { + impl_unaryop!(@doit: $trait, $fn; + maybe_lifetime!($refl, &'l Pair<$a,$b>), + (maybe_lifetime!($refl, &'l $a), + maybe_lifetime!($refl, &'l $b)); + $refl); + }; + (@doit: $trait:ident, $fn:ident; + $self:ty, ($aself:ty, $bself:ty); + $refl : ident) => { + impl<'l> $trait + for $self + where $aself: $trait, + $bself: $trait { + type Output = Pair<<$aself as $trait>::Output, + <$bself as $trait>::Output>; + #[inline] + fn $fn(self) -> Self::Output { + Pair(maybe_ref!($refl, self.0).$fn(), + maybe_ref!($refl, self.1).$fn()) + } + } + } +} + +#[macro_export] +macro_rules! impl_pair_vectorspace_ops { + (($a:ty, $b:ty), $field:ty) => { + impl_pair_vectorspace_ops!(@binary, ($a, $b), Add, add); + impl_pair_vectorspace_ops!(@binary, ($a, $b), Sub, sub); + impl_pair_vectorspace_ops!(@assign, ($a, $b), AddAssign, add_assign); + impl_pair_vectorspace_ops!(@assign, ($a, $b), SubAssign, sub_assign); + impl_pair_vectorspace_ops!(@scalar, ($a, $b), $field, Mul, mul); + impl_pair_vectorspace_ops!(@scalar, ($a, $b), $field, Div, div); + // Compiler overflow + // $( + // impl_pair_vectorspace_ops!(@scalar_lhs, ($a, $b), $field, $impl_scalarlhs_op, Mul, mul); + // )* + impl_pair_vectorspace_ops!(@scalar_assign, ($a, $b), $field, MulAssign, mul_assign); + impl_pair_vectorspace_ops!(@scalar_assign, ($a, $b), $field, DivAssign, div_assign); + impl_pair_vectorspace_ops!(@unary, ($a, $b), Neg, neg); + }; + (@binary, ($a : ty, $b : ty), $trait : ident, $fn : ident) => { + impl_binop!(($a, $b), $trait, $fn, ref, ref); + impl_binop!(($a, $b), $trait, $fn, ref, noref); + impl_binop!(($a, $b), $trait, $fn, noref, ref); + impl_binop!(($a, $b), $trait, $fn, noref, noref); + }; + (@assign, ($a : ty, $b : ty), $trait : ident, $fn :ident) => { + impl_assignop!(($a, $b), $trait, $fn, ref); + impl_assignop!(($a, $b), $trait, $fn, noref); + }; + (@scalar, ($a : ty, $b : ty), $field : ty, $trait : ident, $fn :ident) => { + impl_scalarop!(($a, $b), $field, $trait, $fn, ref); + impl_scalarop!(($a, $b), $field, $trait, $fn, noref); + }; + (@scalar_lhs, ($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident) => { + impl_scalarlhs_op!(($a, $b), $field, $trait, $fn, ref); + impl_scalarlhs_op!(($a, $b), $field, $trait, $fn, noref); + }; + (@scalar_assign, ($a : ty, $b : ty), $field : ty, $trait : ident, $fn : ident) => { + impl_scalar_assignop!(($a, $b), $field, $trait, $fn); + }; + (@unary, ($a : ty, $b : ty), $trait : ident, $fn : ident) => { + impl_unaryop!(($a, $b), $trait, $fn, ref); + impl_unaryop!(($a, $b), $trait, $fn, noref); + }; +} + +impl_pair_vectorspace_ops!((f32, f32), f32); +impl_pair_vectorspace_ops!((f64, f64), f64); + +type PairOutput = Pair<>::Output, >::Output>; + +impl Euclidean for Pair +where + A : Euclidean, + B : Euclidean, + F : Float, + PairOutput : Euclidean, + Self : Sized + + Mul> + MulAssign + + Div> + DivAssign + + Add> + + Sub> + + for<'b> Add<&'b Self, Output=PairOutput> + + for<'b> Sub<&'b Self, Output=PairOutput> + + AddAssign + for<'b> AddAssign<&'b Self> + + SubAssign + for<'b> SubAssign<&'b Self> + + Neg> +{ + type Output = PairOutput; + + fn dot>(&self, other : I) -> F { + let Pair(u, v) = other.decompose(); + self.0.dot(u) + self.1.dot(v) + } + + fn norm2_squared(&self) -> F { + self.0.norm2_squared() + self.1.norm2_squared() + } + + fn dist2_squared>(&self, other : I) -> F { + let Pair(u, v) = other.decompose(); + self.0.dist2_squared(u) + self.1.dist2_squared(v) + } +} + +impl AXPY> for Pair +where + U : Space, + V : Space, + A : AXPY, + B : AXPY, + F : Num, + Self : MulAssign, + Pair : MulAssign, + Pair : AXPY>, +{ + + type Owned = Pair; + + fn axpy>>(&mut self, α : F, x : I, β : F) { + let Pair(u, v) = x.decompose(); + self.0.axpy(α, u, β); + self.1.axpy(α, v, β); + } + + fn copy_from>>(&mut self, x : I) { + let Pair(u, v) = x.decompose(); + self.0.copy_from(u); + self.1.copy_from(v); + } + + fn scale_from>>(&mut self, α : F, x : I) { + let Pair(u, v) = x.decompose(); + self.0.scale_from(α, u); + self.1.scale_from(α, v); + } + + /// Return a similar zero as `self`. + fn similar_origin(&self) -> Self::Owned { + Pair(self.0.similar_origin(), self.1.similar_origin()) + } + + /// Set self to zero. + fn set_zero(&mut self) { + self.0.set_zero(); + self.1.set_zero(); + } +} + +/// [`Decomposition`] for working with [`Pair`]s. +#[derive(Copy, Clone, Debug)] +pub struct PairDecomposition(D, Q); + +impl Space for Pair { + type Decomp = PairDecomposition; +} + +impl Decomposition> for PairDecomposition +where + A : Space, + B : Space, + D : Decomposition, + Q : Decomposition, +{ + type Decomposition<'b> = Pair, Q::Decomposition<'b>> where Pair : 'b; + type Reference<'b> = Pair, Q::Reference<'b>> where Pair : 'b; + + #[inline] + fn lift<'b>(Pair(u, v) : Self::Reference<'b>) -> Self::Decomposition<'b> { + Pair(D::lift(u), Q::lift(v)) + } +} + +impl Instance, PairDecomposition> for Pair +where + A : Space, + B : Space, + D : Decomposition, + Q : Decomposition, + U : Instance, + V : Instance, +{ + #[inline] + fn decompose<'b>(self) + -> as Decomposition>>::Decomposition<'b> + where Self : 'b, Pair : 'b + { + Pair(self.0.decompose(), self.1.decompose()) + } + + #[inline] + fn ref_instance(&self) + -> as Decomposition>>::Reference<'_> + { + Pair(self.0.ref_instance(), self.1.ref_instance()) + } + + #[inline] + fn cow<'b>(self) -> MyCow<'b, Pair> where Self : 'b{ + MyCow::Owned(Pair(self.0.own(), self.1.own())) + } + + #[inline] + fn own(self) -> Pair { + Pair(self.0.own(), self.1.own()) + } +} + + +impl<'a, A, B, U, V, D, Q> Instance, PairDecomposition> for &'a Pair +where + A : Space, + B : Space, + D : Decomposition, + Q : Decomposition, + U : Instance, + V : Instance, + &'a U : Instance, + &'a V : Instance, +{ + #[inline] + fn decompose<'b>(self) + -> as Decomposition>>::Decomposition<'b> + where Self : 'b, Pair : 'b + { + Pair(D::lift(self.0.ref_instance()), Q::lift(self.1.ref_instance())) + } + + #[inline] + fn ref_instance(&self) + -> as Decomposition>>::Reference<'_> + { + Pair(self.0.ref_instance(), self.1.ref_instance()) + } + + #[inline] + fn cow<'b>(self) -> MyCow<'b, Pair> where Self : 'b { + MyCow::Owned(self.own()) + } + + #[inline] + fn own(self) -> Pair { + let Pair(ref u, ref v) = self; + Pair(u.own(), v.own()) + } + +} + +impl DecompositionMut> for PairDecomposition +where + A : Space, + B : Space, + D : DecompositionMut, + Q : DecompositionMut, +{ + type ReferenceMut<'b> = Pair, Q::ReferenceMut<'b>> where Pair : 'b; +} + +impl InstanceMut, PairDecomposition> for Pair +where + A : Space, + B : Space, + D : DecompositionMut, + Q : DecompositionMut, + U : InstanceMut, + V : InstanceMut, +{ + #[inline] + fn ref_instance_mut(&mut self) + -> as DecompositionMut>>::ReferenceMut<'_> + { + Pair(self.0.ref_instance_mut(), self.1.ref_instance_mut()) + } +} + +impl<'a, A, B, U, V, D, Q> InstanceMut, PairDecomposition> for &'a mut Pair +where + A : Space, + B : Space, + D : DecompositionMut, + Q : DecompositionMut, + U : InstanceMut, + V : InstanceMut, +{ + #[inline] + fn ref_instance_mut(&mut self) + -> as DecompositionMut>>::ReferenceMut<'_> + { + Pair(self.0.ref_instance_mut(), self.1.ref_instance_mut()) + } +} + + +impl Norm> +for Pair +where + F : Num, + ExpA : NormExponent, + ExpB : NormExponent, + ExpJ : NormExponent, + A : Norm, + B : Norm, + Loc : Norm, +{ + fn norm(&self, PairNorm(expa, expb, expj) : PairNorm) -> F { + Loc([self.0.norm(expa), self.1.norm(expb)]).norm(expj) + } +} + + +impl Normed for Pair +where + A : Normed, + B : Normed, +{ + type NormExp = PairNorm; + + #[inline] + fn norm_exponent(&self) -> Self::NormExp { + PairNorm(self.0.norm_exponent(), self.1.norm_exponent(), L2) + } + + #[inline] + fn is_zero(&self) -> bool { + self.0.is_zero() && self.1.is_zero() + } +} + +impl HasDual for Pair +where + A : HasDual, + B : HasDual, + +{ + type DualSpace = Pair; +} diff -r d14c877e14b7 -r b3c35d16affe src/discrete_gradient.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/discrete_gradient.rs Mon Feb 03 19:22:16 2025 -0500 @@ -0,0 +1,489 @@ +/*! +Simple disrete gradient operators + */ +use numeric_literals::replace_float_literals; +use nalgebra::{ + DVector, Matrix, U1, Storage, StorageMut, Dyn +}; +use crate::types::Float; +use crate::instance::Instance; +use crate::linops::{Mapping, Linear, BoundedLinear, Adjointable, GEMV}; +use crate::norms::{Norm, L2}; + +#[derive(Copy, Clone, Debug)] +/// Forward differences with Neumann boundary conditions +pub struct ForwardNeumann; + +#[derive(Copy, Clone, Debug)] +/// Forward differences with Dirichlet boundary conditions +pub struct ForwardDirichlet; + +#[derive(Copy, Clone, Debug)] +/// Backward differences with Dirichlet boundary conditions +pub struct BackwardDirichlet; + +#[derive(Copy, Clone, Debug)] +/// Backward differences with Neumann boundary conditions +pub struct BackwardNeumann; + +/// Finite differences gradient +pub struct Grad< + F : Float + nalgebra::RealField, + B : Discretisation, + const N : usize +> { + dims : [usize; N], + h : F, // may be negative to implement adjoints! + discretisation : B, +} + + +/// Finite differences divergence +pub struct Div< + F : Float + nalgebra::RealField, + B : Discretisation, + const N : usize +> { + dims : [usize; N], + h : F, // may be negative to implement adjoints! + discretisation : B, +} + +/// Internal: classification of a point in a 1D discretisation +pub enum DiscretisationOrInterior { + /// center, forward + LeftBoundary(usize, usize), + /// center, backward + RightBoundary(usize, usize), + /// center, (backward, forward) + Interior(usize, (usize, usize)), +} + +use DiscretisationOrInterior::*; + +/// Trait for different discretisations +pub trait Discretisation : Copy { + /// Opposite discretisation, appropriate for adjoints with negated cell width. + type Opposite : Discretisation; + + /// Add to appropiate index of `v` (as determined by `b`) the appropriate difference + /// of `x` with cell width `h`. + fn add_diff_mut( + &self, + v : &mut Matrix, + x : &Matrix, + α : F, + b : DiscretisationOrInterior, + ) where + SMut : StorageMut, + S : Storage; + + /// Give the opposite discretisation, appropriate for adjoints with negated `h`. + fn opposite(&self) -> Self::Opposite; + + /// Bound for the corresponding operator norm. + #[replace_float_literals(F::cast_from(literal))] + fn opnorm_bound(&self, h : F) -> F { + // See: Chambolle, “An Algorithm for Total Variation Minimization and Applications”. + // Ok for forward and backward differences. + // + // Fuck nalgebra for polluting everything with its own shit. + num_traits::Float::sqrt(8.0) / h + } +} + +impl Discretisation for ForwardNeumann { + type Opposite = BackwardDirichlet; + + #[inline] + fn add_diff_mut( + &self, + v : &mut Matrix, + x : &Matrix, + α : F, + b : DiscretisationOrInterior, + ) where + SMut : StorageMut, + S : Storage + { + match b { + Interior(c, (_, f)) | LeftBoundary(c, f) => { v[c] += (x[f] - x[c]) * α }, + RightBoundary(_c, _b) => { }, + } + } + + #[inline] + fn opposite(&self) -> Self::Opposite { + BackwardDirichlet + } +} + +impl Discretisation for BackwardNeumann { + type Opposite = ForwardDirichlet; + + #[inline] + fn add_diff_mut( + &self, + v : &mut Matrix, + x : &Matrix, + α : F, + b : DiscretisationOrInterior, + ) where + SMut : StorageMut, + S : Storage + { + match b { + Interior(c, (b, _)) | RightBoundary(c, b) => { v[c] += (x[c] - x[b]) * α }, + LeftBoundary(_c, _f) => { }, + } + } + + #[inline] + fn opposite(&self) -> Self::Opposite { + ForwardDirichlet + } +} + +impl Discretisation for BackwardDirichlet { + type Opposite = ForwardNeumann; + + #[inline] + fn add_diff_mut( + &self, + v : &mut Matrix, + x : &Matrix, + α : F, + b : DiscretisationOrInterior, + ) where + SMut : StorageMut, + S : Storage + { + match b { + Interior(c, (b, _f)) => { v[c] += (x[c] - x[b]) * α }, + LeftBoundary(c, _f) => { v[c] += x[c] * α }, + RightBoundary(c, b) => { v[c] -= x[b] * α }, + } + } + + #[inline] + fn opposite(&self) -> Self::Opposite { + ForwardNeumann + } +} + +impl Discretisation for ForwardDirichlet { + type Opposite = BackwardNeumann; + + #[inline] + fn add_diff_mut( + &self, + v : &mut Matrix, + x : &Matrix, + α : F, + b : DiscretisationOrInterior, + ) where + SMut : StorageMut, + S : Storage + { + match b { + Interior(c, (_b, f)) => { v[c] += (x[f] - x[c]) * α }, + LeftBoundary(c, f) => { v[c] += x[f] * α }, + RightBoundary(c, _b) => { v[c] -= x[c] * α }, + } + } + + #[inline] + fn opposite(&self) -> Self::Opposite { + BackwardNeumann + } +} + +struct Iter<'a, const N : usize> { + /// Dimensions + dims : &'a [usize; N], + /// Dimension along which to calculate differences + d : usize, + /// Stride along coordinate d + d_stride : usize, + /// Cartesian indices + i : [usize; N], + /// Linear index + k : usize, + /// Maximal linear index + len : usize +} + +impl<'a, const N : usize> Iter<'a, N> { + fn new(dims : &'a [usize; N], d : usize) -> Self { + let d_stride = dims[0..d].iter().product::(); + let len = dims.iter().product::(); + Iter{ dims, d, d_stride, i : [0; N], k : 0, len } + } +} + +impl<'a, const N : usize> Iterator for Iter<'a, N> { + type Item = DiscretisationOrInterior; + fn next(&mut self) -> Option { + let res = if self.k >= self.len { + None + } else { + let cartesian_idx = self.i[self.d]; + let cartesian_max = self.dims[self.d]; + let k = self.k; + + if cartesian_idx == 0 { + Some(LeftBoundary(k, k + self.d_stride)) + } else if cartesian_idx + 1 >= cartesian_max { + Some(RightBoundary(k, k - self.d_stride)) + } else { + Some(Interior(k, (k - self.d_stride, k + self.d_stride))) + } + }; + self.k += 1; + for j in 0..N { + if self.i[j] + 1 < self.dims[j] { + self.i[j] += 1; + break + } + self.i[j] = 0 + } + res + } +} + +impl Mapping> +for Grad +where + B : Discretisation, + F : Float + nalgebra::RealField, +{ + type Codomain = DVector; + fn apply>>(&self, i : I) -> DVector { + let mut y = DVector::zeros(N * self.len()); + self.apply_add(&mut y, i); + y + } +} + +#[replace_float_literals(F::cast_from(literal))] +impl GEMV> +for Grad +where + B : Discretisation, + F : Float + nalgebra::RealField, +{ + fn gemv>>( + &self, y : &mut DVector, α : F, i : I, β : F + ) { + if β == 0.0 { + y.as_mut_slice().iter_mut().for_each(|x| *x = 0.0); + } else if β != 1.0 { + //*y *= β; + y.as_mut_slice().iter_mut().for_each(|x| *x *= β); + } + let h = self.h; + let m = self.len(); + i.eval(|x| { + assert_eq!(x.len(), m); + for d in 0..N { + let mut v = y.generic_view_mut((d*m, 0), (Dyn(m), U1)); + for b in Iter::new(&self.dims, d) { + self.discretisation.add_diff_mut(&mut v, x, α/h, b) + } + } + }) + } +} + +impl Mapping> +for Div +where + B : Discretisation, + F : Float + nalgebra::RealField, +{ + type Codomain = DVector; + fn apply>>(&self, i : I) -> DVector { + let mut y = DVector::zeros(self.len()); + self.apply_add(&mut y, i); + y + } +} + +#[replace_float_literals(F::cast_from(literal))] +impl GEMV> +for Div +where + B : Discretisation, + F : Float + nalgebra::RealField, +{ + fn gemv>>( + &self, y : &mut DVector, α : F, i : I, β : F + ) { + if β == 0.0 { + y.as_mut_slice().iter_mut().for_each(|x| *x = 0.0); + } else if β != 1.0 { + //*y *= β; + y.as_mut_slice().iter_mut().for_each(|x| *x *= β); + } + let h = self.h; + let m = self.len(); + i.eval(|x| { + assert_eq!(x.len(), N * m); + for d in 0..N { + let v = x.generic_view((d*m, 0), (Dyn(m), U1)); + for b in Iter::new(&self.dims, d) { + self.discretisation.add_diff_mut(y, &v, α/h, b) + } + } + }) + } +} + +impl Grad +where + B : Discretisation, + F : Float + nalgebra::RealField +{ + /// Creates a new discrete gradient operator for the vector `u`, verifying dimensions. + pub fn new_for(u : &DVector, h : F, dims : [usize; N], discretisation : B) + -> Option + { + if u.len() == dims.iter().product::() { + Some(Grad { dims, h, discretisation } ) + } else { + None + } + } + + fn len(&self) -> usize { + self.dims.iter().product::() + } +} + + +impl Div +where + B : Discretisation, + F : Float + nalgebra::RealField +{ + /// Creates a new discrete gradient operator for the vector `u`, verifying dimensions. + pub fn new_for(u : &DVector, h : F, dims : [usize; N], discretisation : B) + -> Option + { + if u.len() == dims.iter().product::() * N { + Some(Div { dims, h, discretisation } ) + } else { + None + } + } + + fn len(&self) -> usize { + self.dims.iter().product::() + } +} + +impl Linear> +for Grad +where + B : Discretisation, + F : Float + nalgebra::RealField, +{ +} + +impl Linear> +for Div +where + B : Discretisation, + F : Float + nalgebra::RealField, +{ +} + +impl BoundedLinear, L2, L2, F> +for Grad +where + B : Discretisation, + F : Float + nalgebra::RealField, + DVector : Norm, +{ + fn opnorm_bound(&self, _ : L2, _ : L2) -> F { + // Fuck nalgebra. + self.discretisation.opnorm_bound(num_traits::Float::abs(self.h)) + } +} + + +impl BoundedLinear, L2, L2, F> +for Div +where + B : Discretisation, + F : Float + nalgebra::RealField, + DVector : Norm, +{ + fn opnorm_bound(&self, _ : L2, _ : L2) -> F { + // Fuck nalgebra. + self.discretisation.opnorm_bound(num_traits::Float::abs(self.h)) + } +} + +impl +Adjointable, DVector> +for Grad +where + B : Discretisation, + F : Float + nalgebra::RealField, +{ + type AdjointCodomain = DVector; + type Adjoint<'a> = Div where Self : 'a; + + /// Form the adjoint operator of `self`. + fn adjoint(&self) -> Self::Adjoint<'_> { + Div { + dims : self.dims, + h : -self.h, + discretisation : self.discretisation.opposite(), + } + } +} + + +impl +Adjointable, DVector> +for Div +where + B : Discretisation, + F : Float + nalgebra::RealField, +{ + type AdjointCodomain = DVector; + type Adjoint<'a> = Grad where Self : 'a; + + /// Form the adjoint operator of `self`. + fn adjoint(&self) -> Self::Adjoint<'_> { + Grad { + dims : self.dims, + h : -self.h, + discretisation : self.discretisation.opposite(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn grad_adjoint() { + let im = DVector::from( (0..9).map(|t| t as f64).collect::>()); + let v = DVector::from( (0..18).map(|t| t as f64).collect::>()); + + let grad = Grad::new_for(&im, 1.0, [3, 3], ForwardNeumann).unwrap(); + let a = grad.apply(&im).dot(&v); + let b = grad.adjoint().apply(&v).dot(&im); + assert_eq!(a, b); + + let grad = Grad::new_for(&im, 1.0, [3, 3], ForwardDirichlet).unwrap(); + let a = grad.apply(&im).dot(&v); + let b = grad.adjoint().apply(&v).dot(&im); + assert_eq!(a, b); + + } +} diff -r d14c877e14b7 -r b3c35d16affe src/error.rs --- a/src/error.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/error.rs Mon Feb 03 19:22:16 2025 -0500 @@ -2,10 +2,8 @@ Error passing helper types */ -use std::error::Error; - /// A [`Result`] containing `T` or a dynamic error type -pub type DynResult = Result>; +pub type DynResult = Result; /// A [`Result`] containing `()` or a dynamic error type pub type DynError = DynResult<()>; diff -r d14c877e14b7 -r b3c35d16affe src/euclidean.rs --- a/src/euclidean.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/euclidean.rs Mon Feb 03 19:22:16 2025 -0500 @@ -2,42 +2,37 @@ Euclidean spaces. */ +use std::ops::{Mul, MulAssign, Div, DivAssign, Add, Sub, AddAssign, SubAssign, Neg}; use crate::types::*; -use std::ops::{Mul, MulAssign, Div, DivAssign, Add, Sub, AddAssign, SubAssign, Neg}; - -/// Space (type) with a defined dot product. -/// -/// `U` is the space of the multiplier, and `F` the space of scalars. -/// Since `U` ≠ `Self`, this trait can also implement dual products. -pub trait Dot { - fn dot(&self, other : &U) -> F; -} +use crate::instance::Instance; +use crate::norms::{HasDual, Reflexive}; /// Space (type) with Euclidean and vector space structure /// /// The type should implement vector space operations (addition, subtraction, scalar /// multiplication and scalar division) along with their assignment versions, as well -/// as the [`Dot`] product with respect to `Self`. -pub trait Euclidean : Sized + Dot - + Mul>::Output> + MulAssign - + Div>::Output> + DivAssign - + Add>::Output> - + Sub>::Output> - + for<'b> Add<&'b Self, Output=>::Output> - + for<'b> Sub<&'b Self, Output=>::Output> - + AddAssign + for<'b> AddAssign<&'b Self> - + SubAssign + for<'b> SubAssign<&'b Self> - + Neg>::Output> { +/// as an inner product. +pub trait Euclidean : HasDual + Reflexive + + Mul>::Output> + MulAssign + + Div>::Output> + DivAssign + + Add>::Output> + + Sub>::Output> + + for<'b> Add<&'b Self, Output=>::Output> + + for<'b> Sub<&'b Self, Output=>::Output> + + AddAssign + for<'b> AddAssign<&'b Self> + + SubAssign + for<'b> SubAssign<&'b Self> + + Neg>::Output> +{ type Output : Euclidean; - /// Returns origin of same dimensions as `self`. - fn similar_origin(&self) -> >::Output; + // Inner product + fn dot>(&self, other : I) -> F; /// Calculate the square of the 2-norm, $\frac{1}{2}\\|x\\|_2^2$, where `self` is $x$. - #[inline] - fn norm2_squared(&self) -> F { - self.dot(self) - } + /// + /// This is not automatically implemented to avoid imposing + /// `for <'a> &'a Self : Instance` trait bound bloat. + fn norm2_squared(&self) -> F; /// Calculate the square of the 2-norm divided by 2, $\frac{1}{2}\\|x\\|_2^2$, /// where `self` is $x$. @@ -53,11 +48,11 @@ } /// Calculate the 2-distance squared $\\|x-y\\|_2^2$, where `self` is $x$. - fn dist2_squared(&self, y : &Self) -> F; + fn dist2_squared>(&self, y : I) -> F; /// Calculate the 2-distance $\\|x-y\\|_2$, where `self` is $x$. #[inline] - fn dist2(&self, y : &Self) -> F { + fn dist2>(&self, y : I) -> F { self.dist2_squared(y).sqrt() } diff -r d14c877e14b7 -r b3c35d16affe src/fe_model/p2_local_model.rs --- a/src/fe_model/p2_local_model.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/fe_model/p2_local_model.rs Mon Feb 03 19:22:16 2025 -0500 @@ -6,7 +6,8 @@ use crate::loc::Loc; use crate::sets::{Set,NPolygon,SpannedHalfspace}; use crate::linsolve::*; -use crate::euclidean::Dot; +use crate::euclidean::Euclidean; +use crate::instance::Instance; use super::base::{LocalModel,RealLocalModel}; use crate::sets::Cube; use numeric_literals::replace_float_literals; @@ -30,7 +31,8 @@ impl<'a, F : Float> Set> for RealInterval { #[inline] - fn contains(&self, &Loc([x]) : &Loc) -> bool { + fn contains>>(&self, z : I) -> bool { + let &Loc([x]) = z.ref_instance(); let &[Loc([x0]), Loc([x1])] = &self.0; (x0 < x && x < x1) || (x1 < x && x < x0) } @@ -38,11 +40,11 @@ impl<'a, F : Float> Set> for PlanarSimplex { #[inline] - fn contains(&self, x : &Loc) -> bool { + fn contains>>(&self, z : I) -> bool { let &[x0, x1, x2] = &self.0; NPolygon([[x0, x1].spanned_halfspace(), [x1, x2].spanned_halfspace(), - [x2, x0].spanned_halfspace()]).contains(x) + [x2, x0].spanned_halfspace()]).contains(z) } } diff -r d14c877e14b7 -r b3c35d16affe src/instance.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/instance.rs Mon Feb 03 19:22:16 2025 -0500 @@ -0,0 +1,282 @@ +/*! +Helper traits to work with references or owned values of types and their subsets. +*/ + +#[derive(Clone, Copy)] +pub enum EitherDecomp { + Owned(A), + Borrowed(B), +} + +/// A very basic implementation of [`std::borrow::Cow`] without a [`Clone`] trait dependency. +pub type MyCow<'b, X> = EitherDecomp; + +impl<'b, X> std::ops::Deref for MyCow<'b, X> { + type Target = X; + + #[inline] + fn deref(&self) -> &Self::Target { + match self { + EitherDecomp::Owned(x) => &x, + EitherDecomp::Borrowed(x) => x, + } + } +} + +impl<'b, X> MyCow<'b, X> { + #[inline] + pub fn into_owned(self) -> X where X : Clone { + match self { + EitherDecomp::Owned(x) => x, + EitherDecomp::Borrowed(x) => x.clone(), + } + } +} + +/// Trait for abitrary mathematical spaces. +pub trait Space : Instance { + /// Default decomposition for the space + type Decomp : Decomposition; +} + +#[macro_export] +macro_rules! impl_basic_space { + ($($type:ty)*) => { $( + impl $crate::instance::Space for $type { + type Decomp = $crate::instance::BasicDecomposition; + } + )* }; + ($type:ty where $($where:tt)*) => { + impl<$($where)*> $crate::instance::Space for $type { + type Decomp = $crate::instance::BasicDecomposition; + } + }; +} + +impl_basic_space!(u8 u16 u32 u64 u128 usize + i8 i16 i32 i64 i128 isize + f32 f64); + +/// Marker type for decompositions to be used with [`Instance`]. +pub trait Decomposition : Sized { + /// Possibly owned form of the decomposition + type Decomposition<'b> : Instance where X : 'b; + /// Unlikely owned form of the decomposition. + /// Type for a lightweight intermediate conversion that does not own the original variable. + /// Usually this is just a reference, but may also be a lightweight structure that + /// contains references; see the implementation for [`crate::direct_product::Pair`]. + type Reference<'b> : Instance + Copy where X : 'b; + + /// Left the lightweight reference type into a full decomposition type. + fn lift<'b>(r : Self::Reference<'b>) -> Self::Decomposition<'b>; +} + +/// Most common [`Decomposition`] (into `Either`) that allows working with owned +/// values and all sorts of references. +#[derive(Copy, Clone, Debug)] +pub struct BasicDecomposition; + +impl Decomposition for BasicDecomposition { + type Decomposition<'b> = MyCow<'b, X> where X : 'b; + type Reference<'b> = &'b X where X : 'b; + + #[inline] + fn lift<'b>(r : Self::Reference<'b>) -> Self::Decomposition<'b> { + MyCow::Borrowed(r) + } +} + +/// Helper trait for functions to work with either owned values or references to either the +/// “principal type” `X` or types some present a subset of `X`. In the latter sense, this +/// generalises [`std::borrow::ToOwned`], [`std::borrow::Borrow`], and [`std::borrow::Cow`]. +/// +/// This is used, for example, by [`crate::mapping::Mapping::apply`]. +pub trait Instance::Decomp> : Sized where D : Decomposition { + /// Decomposes self according to `decomposer`. + fn decompose<'b>(self) -> D::Decomposition<'b> + where Self : 'b, X : 'b; + + /// Returns a lightweight instance of `self`. + fn ref_instance(&self) -> D::Reference<'_>; + + /// Returns an owned instance of `X`, cloning or converting non-true instances when necessary. + fn own(self) -> X; + + // ************** automatically implemented methods below from here ************** + + /// Returns an owned instance or reference to `X`, converting non-true instances when necessary. + /// + /// Default implementation uses [`Self::own`]. Consumes the input. + fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b { + MyCow::Owned(self.own()) + } + + #[inline] + /// Evaluates `f` on a reference to self. + /// + /// Default implementation uses [`Self::cow`]. Consumes the input. + fn eval<'b, R>(self, f : impl FnOnce(&X) -> R) -> R + where X : 'b, Self : 'b + { + f(&*self.cow()) + } + + #[inline] + /// Evaluates `f` or `g` depending on whether a reference or owned value is available. + /// + /// Default implementation uses [`Self::cow`]. Consumes the input. + fn either<'b, R>( + self, + f : impl FnOnce(X) -> R, + g : impl FnOnce(&X) -> R + ) -> R + where Self : 'b + { + match self.cow() { + EitherDecomp::Owned(x) => f(x), + EitherDecomp::Borrowed(x) => g(x), + } + } +} + + +impl Instance for X { + #[inline] + fn decompose<'b>(self) -> >::Decomposition<'b> + where Self : 'b, X : 'b + { + MyCow::Owned(self) + } + + #[inline] + fn own(self) -> X { + self + } + + #[inline] + fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b { + MyCow::Owned(self) + } + + #[inline] + fn ref_instance(&self) -> >::Reference<'_> { + self + } +} + +impl<'a, X : Space + Clone> Instance for &'a X { + #[inline] + fn decompose<'b>(self) -> >::Decomposition<'b> + where Self : 'b, X : 'b + { + MyCow::Borrowed(self) + } + + #[inline] + fn own(self) -> X { + self.clone() + } + + #[inline] + fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b { + MyCow::Borrowed(self) + } + + #[inline] + fn ref_instance(&self) -> >::Reference<'_> { + *self + } +} + +impl<'a, X : Space + Clone> Instance for &'a mut X { + #[inline] + fn decompose<'b>(self) -> >::Decomposition<'b> + where Self : 'b, X : 'b + { + EitherDecomp::Borrowed(self) + } + + #[inline] + fn own(self) -> X { + self.clone() + } + + #[inline] + fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b, X : Clone { + EitherDecomp::Borrowed(self) + } + + #[inline] + fn ref_instance(&self) -> >::Reference<'_> { + *self + } +} + +impl<'a, X : Space + Clone> Instance for MyCow<'a, X> { + + #[inline] + fn decompose<'b>(self) -> >::Decomposition<'b> + where Self : 'b, X : 'b + { + self + } + + #[inline] + fn own(self) -> X { + match self { + MyCow::Borrowed(a) => a.own(), + MyCow::Owned(b) => b.own() + } + } + + #[inline] + fn cow<'b>(self) -> MyCow<'b, X> where Self : 'b { + match self { + MyCow::Borrowed(a) => a.cow(), + MyCow::Owned(b) => b.cow() + } + } + + #[inline] + fn ref_instance(&self) -> >::Reference<'_> { + match self { + MyCow::Borrowed(a) => a, + MyCow::Owned(b) => &b, + } + } +} + +/// Marker type for mutable decompositions to be used with [`InstanceMut`]. +pub trait DecompositionMut : Sized { + type ReferenceMut<'b> : InstanceMut where X : 'b; +} + + +/// Helper trait for functions to work with mutable references. +pub trait InstanceMut::Decomp> : Sized where D : DecompositionMut { + /// Returns a mutable decomposition of self. + fn ref_instance_mut(&mut self) -> D::ReferenceMut<'_>; +} + +impl DecompositionMut for BasicDecomposition { + type ReferenceMut<'b> = &'b mut X where X : 'b; +} + +/// This impl may seem pointless, but allows throwaway mutable scratch variables +impl<'a, X : Space> InstanceMut for X { + #[inline] + fn ref_instance_mut(&mut self) + -> >::ReferenceMut<'_> + { + self + } +} + +impl<'a, X : Space> InstanceMut for &'a mut X { + #[inline] + fn ref_instance_mut(&mut self) + -> >::ReferenceMut<'_> + { + self + } +} diff -r d14c877e14b7 -r b3c35d16affe src/iterate.rs --- a/src/iterate.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/iterate.rs Mon Feb 03 19:22:16 2025 -0500 @@ -19,6 +19,7 @@ .. Default::default() }; let mut x = 1 as float; +# let mut iter_clone = iter.clone(); iter.iterate(|state|{ // This is our computational step x = x + x.sqrt(); @@ -26,7 +27,17 @@ // return current value when requested return x }) -}) +}); +// or alternatively (avoiding problems with moves) +# iter = iter_clone; +for state in iter.iter() { + // This is our computational step + x = x + x.sqrt(); + state.if_verbose(||{ + // return current value when requested + return x + }) +} ``` There is no colon after `state.if_verbose`, because we need to return its value. If you do something after the step, you need to store the result in a variable and return it from the main computational step afterwards. @@ -52,6 +63,8 @@ use std::marker::PhantomData; use std::time::Duration; use std::error::Error; +use std::cell::RefCell; +use std::rc::Rc; use crate::types::*; use crate::logger::*; @@ -114,7 +127,7 @@ /// /// This is the parameter obtained by the closure passed to [`AlgIterator::iterate`] or /// [`AlgIteratorFactory::iterate`]. -pub trait AlgIteratorState { +pub trait AlgIteratorState : Sized { /// Call `call_objective` if this is a verbose iteration. /// /// Verbosity depends on the [`AlgIterator`] that produced this state. @@ -122,15 +135,18 @@ /// The closure `calc_objective` should return an arbitrary value of type `V`, to be inserted /// into the log, or whatever is deemed by the [`AlgIterator`]. For usage instructions see the /// [module documentation][self]. - fn if_verbose(&self, calc_objective : impl FnMut() -> V) -> Step; + fn if_verbose(self, calc_objective : impl FnOnce() -> V) -> Step; /// Returns the current iteration count. fn iteration(&self) -> usize; + + /// Indicates whether the iterator is quiet + fn is_quiet(&self) -> bool; } /// Result of a step of an [`AlgIterator`] #[derive(Debug, Serialize)] -pub enum Step { +pub enum Step { /// Iteration should be terminated Terminated, /// Iteration should be terminated due to failure @@ -138,14 +154,14 @@ /// No result this iteration (due to verbosity settings) Quiet, /// Result of this iteration (due to verbosity settings) - Result(V), + Result(V, S), } -impl Step { +impl Step { /// Maps the value contained within the `Step`, if any, by the closure `f`. - pub fn map(self, mut f : impl FnMut(V) -> U) -> Step { + pub fn map(self, mut f : impl FnMut(V) -> U) -> Step { match self { - Step::Result(v) => Step::Result(f(v)), + Step::Result(v, s) => Step::Result(f(v), s), Step::Failure(e) => Step::Failure(e), Step::Quiet => Step::Quiet, Step::Terminated => Step::Terminated, @@ -153,6 +169,12 @@ } } +impl Default for Step { + fn default() -> Self { + Step::Quiet + } +} + /// An iterator for algorithms, produced by [`AlgIteratorFactory::prepare`]. /// /// Typically not accessed directly, but transparently produced by an [`AlgIteratorFactory`]. @@ -160,13 +182,26 @@ pub trait AlgIterator : Sized { /// The state type type State : AlgIteratorState; - /// The output type for [`Self::step`]. + /// The output type for [`Self::poststep`] and [`Self::step`]. type Output; - /// The error type for [`Self::step`] and [`Self::iterate`]. - type Err : Error; + /// The input type for [`Self::poststep`]. + type Input; - /// Advance the iterator. - fn step(&mut self) -> Step; + /// Advance the iterator, performing `step_fn` with the state + fn step(&mut self, step_fn : &mut F) -> Step + where F : FnMut(Self::State) -> Step, + E : Error { + self.prestep().map_or(Step::Terminated, + |state| self.poststep(step_fn(state))) + } + + /// Initial stage of advancing the iterator, before the actual step + fn prestep(&mut self) -> Option; + + /// Handle step result + fn poststep(&mut self, result : Step) + -> Step + where E : Error; /// Return current iteration count. fn iteration(&self) -> usize { @@ -176,56 +211,29 @@ /// Return current state. fn state(&self) -> Self::State; - /// Iterate the `AlgIterator` until termination. + /// Iterate the `AlgIterator` until termination, erforming `step_fn` on each step. /// /// Returns either `()` or an error if the step closure terminated in [`Step::Failure´]. #[inline] - fn iterate(&mut self) -> Result<(), Self::Err> { + fn iterate(&mut self, mut step_fn : F) -> Result<(), E> + where F : FnMut(Self::State) -> Step, + E : Error { loop { - match self.step() { + match self.step(&mut step_fn) { Step::Terminated => return Ok(()), Step::Failure(e) => return Err(e), _ => {}, } } } - - /// Converts the `AlgIterator` into a plain [`Iterator`]. - /// - /// [`Step::Quiet`] results are discarded, and [`Step::Failure`] results **panic**. - fn downcast(self) -> AlgIteratorI { - AlgIteratorI(self) - } -} - -/// Conversion of an `AlgIterator` into a plain [`Iterator`]. -/// -/// The conversion discards [`Step::Quiet`] and **panics** on [`Step::Failure`]. -pub struct AlgIteratorI(A); - -impl Iterator for AlgIteratorI -where A : AlgIterator { - type Item = A::Output; - - fn next(&mut self) -> Option { - loop { - match self.0.step() { - Step::Result(v) => return Some(v), - Step::Failure(e) => panic!("{e:?}"), - Step::Terminated => return None, - Step::Quiet => continue, - } - } - } } /// A factory for producing an [`AlgIterator`]. /// /// For usage instructions see the [module documentation][self]. pub trait AlgIteratorFactory : Sized { - type Iter : AlgIterator - where F : FnMut(&Self::State) -> Step, - E : Error; + type Iter : AlgIterator; + /// The state type of the corresponding [`AlgIterator`]. /// A reference to this is passed to the closures passed to methods such as [`Self::iterate`]. type State : AlgIteratorState; @@ -235,12 +243,7 @@ type Output; /// Prepare an [`AlgIterator`], consuming the factory. - /// - /// The function `step_fn` should accept a `state` ([`AlgIteratorState`] parameter, and return - /// a [`Step`]. - fn prepare(self, step_fn : F) -> Self::Iter - where F : FnMut(&Self::State) -> Step, - E : Error; + fn prepare(self) -> Self::Iter; /// Iterate the the closure `step`. /// @@ -252,12 +255,12 @@ /// This method is equivalent to [`Self::prepare`] followed by [`AlgIterator::iterate`]. #[inline] fn iterate_fallible(self, step : F) -> Result<(), E> - where F : FnMut(&Self::State) -> Step, + where F : FnMut(Self::State) -> Step, E : Error { - self.prepare(step).iterate() + self.prepare().iterate(step) } - /// Iterate the the closure `step`. + /// Iterate the closure `step`. /// /// The closure should accept a `state` parameter (satisfying the trait [`AlgIteratorState`]), /// It should return the output of @@ -269,7 +272,7 @@ /// with the error type `E=`[`std::convert::Infallible`]. #[inline] fn iterate(self, step : F) - where F : FnMut(&Self::State) -> Step { + where F : FnMut(Self::State) -> Step { self.iterate_fallible(step).unwrap_or_default() } @@ -285,13 +288,14 @@ /// /// For usage instructions see the [module documentation][self]. #[inline] - fn iterate_data_fallible(self, mut datasource : I, mut step : F) -> Result<(), E> - where F : FnMut(&Self::State, D) -> Step, + fn iterate_data_fallible(self, mut datasource : I, mut step : F) + -> Result<(), E> + where F : FnMut(Self::State, D) -> Step, I : Iterator, E : Error { - self.prepare(move |state| { + self.prepare().iterate(move |state| { datasource.next().map_or(Step::Terminated, |d| step(state, d)) - }).iterate() + }) } /// Iterate the closure `step` with data produced by `datasource`. @@ -306,7 +310,7 @@ /// For usage instructions see the [module documentation][self]. #[inline] fn iterate_data(self, datasource : I, step : F) - where F : FnMut(&Self::State, D) -> Step, + where F : FnMut(Self::State, D) -> Step, I : Iterator { self.iterate_data_fallible(datasource, step).unwrap_or_default() } @@ -395,6 +399,26 @@ /// Is the iterator quiet, i.e., on-verbose? fn is_quiet(&self) -> bool { false } + + /// Returns an an [`std::iter::Iterator`] that can be used in a `for`-loop. + fn iter(self) -> AlgIteratorIterator { + AlgIteratorIterator { + algi : Rc::new(RefCell::new(self.prepare())), + } + } + + /// Returns an an [`std::iter::Iterator`] that can be used in a `for`-loop, + /// also inputting an initial iteration status calculated by `f` if needed. + fn iter_init(self, f : impl FnOnce() -> ::Input) + -> AlgIteratorIterator { + let mut i = self.prepare(); + let st = i.state(); + let step : Step<::Input, Self::State> = st.if_verbose(f); + i.poststep(step); + AlgIteratorIterator { + algi : Rc::new(RefCell::new(i)), + } + } } /// Options for [`BasicAlgIteratorFactory`]. @@ -427,6 +451,11 @@ /// * every 10 iterations from there on until 100 iterations, /// * every 100 iteartinos frmo there on until 1000 iterations, etc. Logarithmic(usize), + /// Same as `Logarithmic`, but $\log_b(n)$ is replaced by $min\{c, \log_b(n)\}$ where $c$ + /// is the given `cap`. For example, with `base=10` and `cap=2`, the first ten iterations + /// will be output, then every tenth iteration, and after 100 iterations, every 100th iteration, + /// without further logarithmic progression. + LogarithmicCap{ base : usize, cap : u32 }, } impl Verbose { @@ -443,6 +472,10 @@ let every = base.pow((iter as float).log(base as float).floor() as u32); iter % every == 0 } + &Verbose::LogarithmicCap{base, cap} => { + let every = base.pow(((iter as float).log(base as float).floor() as u32).min(cap)); + iter % every == 0 + } } } } @@ -467,6 +500,8 @@ verbose : bool, /// Whether results should be calculated. calc : bool, + /// Indicates whether the iteration is quiet + quiet : bool, } /// [`AlgIteratorFactory`] for [`BasicAlgIterator`] @@ -478,11 +513,10 @@ /// The simplest [`AlgIterator`], created by [`BasicAlgIteratorFactory`] #[derive(Clone,Debug)] -pub struct BasicAlgIterator { +pub struct BasicAlgIterator { options : AlgIteratorOptions, iter : usize, - step_fn : F, - _phantoms : PhantomData<(V, E)>, + _phantoms : PhantomData, } impl AlgIteratorOptions { @@ -500,18 +534,13 @@ impl AlgIteratorFactory for AlgIteratorOptions where V : LogRepr { type State = BasicState; - type Iter = BasicAlgIterator - where F : FnMut(&Self::State) -> Step, - E : Error; + type Iter = BasicAlgIterator; type Output = V; - fn prepare(self, step_fn : F) -> Self::Iter - where F : FnMut(&Self::State) -> Step, - E : Error { + fn prepare(self) -> Self::Iter { BasicAlgIterator{ options : self, iter : 0, - step_fn, _phantoms : PhantomData, } } @@ -525,18 +554,13 @@ impl AlgIteratorFactory for BasicAlgIteratorFactory where V : LogRepr { type State = BasicState; - type Iter = BasicAlgIterator - where F : FnMut(&Self::State) -> Step, - E : Error; + type Iter = BasicAlgIterator; type Output = V; - fn prepare(self, step_fn : F) -> Self::Iter - where F : FnMut(&Self::State) -> Step, - E : Error { + fn prepare(self) -> Self::Iter { BasicAlgIterator { options : self.options, iter : 0, - step_fn, _phantoms : PhantomData } } @@ -547,33 +571,33 @@ } } -impl AlgIterator for BasicAlgIterator -where V : LogRepr, - E : Error, - F : FnMut(&BasicState) -> Step { +impl AlgIterator for BasicAlgIterator +where V : LogRepr { type State = BasicState; type Output = V; - type Err = E; + type Input = V; #[inline] - fn step(&mut self) -> Step { + fn prestep(&mut self) -> Option { if self.iter >= self.options.max_iter { - Step::Terminated + None } else { self.iter += 1; - let state = self.state(); - let res = (self.step_fn)(&state); - if let Step::Result(ref val) = res { - if state.verbose && !self.options.quiet { - println!("{}{}/{} {}{}", "".dimmed(), - state.iter, - self.options.max_iter, - val.logrepr(), - "".clear()); - } + Some(self.state()) + } + } + + fn poststep(&mut self, res : Step) -> Step { + if let Step::Result(ref val, ref state) = res { + if state.verbose && !self.options.quiet { + println!("{}{}/{} {}{}", "".dimmed(), + state.iter, + self.options.max_iter, + val.logrepr(), + "".clear()); } - res } + res } #[inline] @@ -585,19 +609,20 @@ fn state(&self) -> BasicState { let iter = self.iter; let verbose = self.options.verbose_iter.is_verbose(iter); - BasicState{ + BasicState { iter : iter, verbose : verbose, calc : verbose, + quiet : self.options.quiet } } } impl AlgIteratorState for BasicState { #[inline] - fn if_verbose(&self, mut calc_objective : impl FnMut() -> V) -> Step { + fn if_verbose(self, calc_objective : impl FnOnce() -> V) -> Step { if self.calc { - Step::Result(calc_objective()) + Step::Result(calc_objective(), self) } else { Step::Quiet } @@ -605,7 +630,12 @@ #[inline] fn iteration(&self) -> usize { - return self.iter; + self.iter + } + + #[inline] + fn is_quiet(&self) -> bool { + self.quiet } } @@ -636,17 +666,13 @@ for StallIteratorFactory where BaseFactory : AlgIteratorFactory, U : SignedNum + PartialOrd { - type Iter = StallIterator> - where F : FnMut(&Self::State) -> Step, - E : Error; + type Iter = StallIterator; type State = BaseFactory::State; type Output = BaseFactory::Output; - fn prepare(self, step_fn : F) -> Self::Iter - where F : FnMut(&Self::State) -> Step, - E : Error { + fn prepare(self) -> Self::Iter { StallIterator { - base_iterator : self.base_options.prepare(step_fn), + base_iterator : self.base_options.prepare(), stall : self.stall, previous_value : None, } @@ -662,18 +688,24 @@ where BaseIterator : AlgIterator, U : SignedNum + PartialOrd { type State = BaseIterator::State; - type Output = BaseIterator::Output; - type Err = BaseIterator::Err; + type Output = U; + type Input = BaseIterator::Input; #[inline] - fn step(&mut self) -> Step { - match self.base_iterator.step() { - Step::Result(nv) => { + fn prestep(&mut self) -> Option { + self.base_iterator.prestep() + } + + #[inline] + fn poststep(&mut self, res : Step) -> Step + where E : Error { + match self.base_iterator.poststep(res) { + Step::Result(nv, state) => { let previous_v = self.previous_value; self.previous_value = Some(nv); match previous_v { Some(pv) if (nv - pv).abs() <= self.stall * pv.abs() => Step::Terminated, - _ => Step::Result(nv), + _ => Step::Result(nv, state), } }, val => val, @@ -711,17 +743,13 @@ for ValueIteratorFactory where BaseFactory : AlgIteratorFactory, U : SignedNum + PartialOrd { - type Iter = ValueIterator> - where F : FnMut(&Self::State) -> Step, - E : Error; + type Iter = ValueIterator; type State = BaseFactory::State; type Output = BaseFactory::Output; - fn prepare(self, step_fn : F) -> Self::Iter - where F : FnMut(&Self::State) -> Step, - E : Error { + fn prepare(self) -> Self::Iter { ValueIterator { - base_iterator : self.base_options.prepare(step_fn), + base_iterator : self.base_options.prepare(), target : self.target } } @@ -736,17 +764,22 @@ where BaseIterator : AlgIterator, U : SignedNum + PartialOrd { type State = BaseIterator::State; - type Output = BaseIterator::Output; - type Err = BaseIterator::Err; + type Output = U; + type Input = BaseIterator::Input; #[inline] - fn step(&mut self) -> Step { - match self.base_iterator.step() { - Step::Result(v) => { + fn prestep(&mut self) -> Option { + self.base_iterator.prestep() + } + + #[inline] + fn poststep(&mut self, res : Step) -> Step where E : Error{ + match self.base_iterator.poststep(res) { + Step::Result(v, state) => { if v <= self.target { Step::Terminated } else { - Step::Result(v) + Step::Result(v, state) } }, val => val, @@ -792,16 +825,12 @@ where BaseFactory : AlgIteratorFactory, BaseFactory::Output : 'log { type State = BaseFactory::State; - type Iter = LoggingIterator<'log, BaseFactory::Output, BaseFactory::Iter> - where F : FnMut(&Self::State) -> Step, - E : Error; + type Iter = LoggingIterator<'log, BaseFactory::Output, BaseFactory::Iter>; type Output = (); - fn prepare(self, step_fn : F) -> Self::Iter - where F : FnMut(&Self::State) -> Step, - E : Error { + fn prepare(self) -> Self::Iter { LoggingIterator { - base_iterator : self.base_options.prepare(step_fn), + base_iterator : self.base_options.prepare(), logger : self.logger, } } @@ -818,12 +847,17 @@ BaseIterator::Output : 'log { type State = BaseIterator::State; type Output = (); - type Err = BaseIterator::Err; + type Input = BaseIterator::Input; #[inline] - fn step(&mut self) -> Step { - match self.base_iterator.step() { - Step::Result(v) => { + fn prestep(&mut self) -> Option { + self.base_iterator.prestep() + } + + #[inline] + fn poststep(&mut self, res : Step) -> Step<(), Self::State, E> where E : Error { + match self.base_iterator.poststep(res) { + Step::Result(v, _) => { self.logger.log(v); Step::Quiet }, @@ -845,7 +879,7 @@ } /// This [`AlgIteratorFactory`] allows output mapping. -/// +/// /// Typically produced with [`AlgIteratorFactory::mapped`]. #[derive(Debug)] pub struct MappingIteratorFactory { @@ -868,16 +902,12 @@ where BaseFactory : AlgIteratorFactory, G : Fn(usize, BaseFactory::Output) -> U { type State = BaseFactory::State; - type Iter = MappingIterator> - where F : FnMut(&Self::State) -> Step, - E : Error; + type Iter = MappingIterator; type Output = U; - fn prepare(self, step_fn : F) -> Self::Iter - where F : FnMut(&Self::State) -> Step, - E : Error { + fn prepare(self) -> Self::Iter { MappingIterator { - base_iterator : self.base_options.prepare(step_fn), + base_iterator : self.base_options.prepare(), map : self.map } } @@ -894,12 +924,17 @@ G : Fn(usize, BaseIterator::Output) -> U { type State = BaseIterator::State; type Output = U; - type Err = BaseIterator::Err; + type Input = BaseIterator::Input; #[inline] - fn step(&mut self) -> Step { - match self.base_iterator.step() { - Step::Result(v) => Step::Result((self.map)(self.iteration(), v)), + fn prestep(&mut self) -> Option { + self.base_iterator.prestep() + } + + #[inline] + fn poststep(&mut self, res : Step) -> Step where E : Error { + match self.base_iterator.poststep(res) { + Step::Result(v, state) => Step::Result((self.map)(self.iteration(), v), state), Step::Quiet => Step::Quiet, Step::Terminated => Step::Terminated, Step::Failure(e) => Step::Failure(e), @@ -935,7 +970,11 @@ /// Data `U` with production time attached #[derive(Copy, Clone, Debug, Serialize)] pub struct Timed { + /// CPU time taken pub cpu_time : Duration, + /// Iteration number + pub iter : usize, + /// User data //#[serde(flatten)] pub data : U } @@ -951,16 +990,12 @@ for TimingIteratorFactory where BaseFactory : AlgIteratorFactory { type State = BaseFactory::State; - type Iter = TimingIterator> - where F : FnMut(&Self::State) -> Step, - E : Error; + type Iter = TimingIterator; type Output = Timed; - fn prepare(self, step_fn : F) -> Self::Iter - where F : FnMut(&Self::State) -> Step, - E : Error { + fn prepare(self) -> Self::Iter { TimingIterator { - base_iterator : self.0.prepare(step_fn), + base_iterator : self.0.prepare(), start_time : ProcessTime::now() } } @@ -976,16 +1011,22 @@ where BaseIterator : AlgIterator { type State = BaseIterator::State; type Output = Timed; - type Err = BaseIterator::Err; + type Input = BaseIterator::Input; + + #[inline] + fn prestep(&mut self) -> Option { + self.base_iterator.prestep() + } #[inline] - fn step(&mut self) -> Step { - match self.base_iterator.step() { - Step::Result(data) => { + fn poststep(&mut self, res : Step) -> Step where E : Error { + match self.base_iterator.poststep(res) { + Step::Result(data, state) => { Step::Result(Timed{ cpu_time : self.start_time.elapsed(), + iter : self.iteration(), data - }) + }, state) }, Step::Quiet => Step::Quiet, Step::Terminated => Step::Terminated, @@ -1005,6 +1046,82 @@ } // +// New for-loop interface +// + +pub struct AlgIteratorIterator { + algi : Rc>, +} + +pub struct AlgIteratorIteration { + state : I::State, + algi : Rc>, +} + +impl std::iter::Iterator for AlgIteratorIterator { + type Item = AlgIteratorIteration; + + fn next(&mut self) -> Option { + let algi = self.algi.clone(); + RefCell::borrow_mut(&self.algi).prestep().map(|state| AlgIteratorIteration { + state, + algi, + }) + } +} + +/// Types of errors that may occur +#[derive(Debug,PartialEq,Eq)] +pub enum IterationError { + /// [`AlgIteratorIteration::if_verbose_check`] is not called in iteration order. + ReportingOrderingError +} + +impl AlgIteratorIteration { + /// Call `call_objective` if this is a verbose iteration. + /// + /// Verbosity depends on the [`AlgIterator`] that produced this state. + /// + /// The closure `calc_objective` should return an arbitrary value of type `V`, to be inserted + /// into the log, or whatever is deemed by the [`AlgIterator`]. For usage instructions see the + /// [module documentation][self]. + /// + /// This function may panic if result reporting is not ordered correctly (an unlikely mistake + /// if using this facility correctly). For a version that propagates errors, see + /// [`Self::if_verbose_check`]. + pub fn if_verbose(self, calc_objective : impl FnOnce() -> I::Input) { + self.if_verbose_check(calc_objective).unwrap() + } + + /// Version of [`Self::if_verbose`] that propagates errors instead of panicking. + pub fn if_verbose_check(self, calc_objective : impl FnOnce() -> I::Input) + -> Result<(), IterationError> { + let mut algi = match RefCell::try_borrow_mut(&self.algi) { + Err(_) => return Err(IterationError::ReportingOrderingError), + Ok(algi) => algi + }; + if self.state.iteration() != algi.iteration() { + Err(IterationError::ReportingOrderingError) + } else { + let res : Step + = self.state.if_verbose(calc_objective); + algi.poststep(res); + Ok(()) + } + } + + /// Returns the current iteration count. + pub fn iteration(&self) -> usize { + self.state.iteration() + } + + /// Indicates whether the iterator is quiet + pub fn is_quiet(&self) -> bool { + self.state.is_quiet() + } +} + +// // Tests // @@ -1050,4 +1167,44 @@ .collect::>()) } } + + #[test] + fn iteration_for_loop() { + let options = AlgIteratorOptions{ + max_iter : 10, + verbose_iter : Verbose::Every(3), + .. Default::default() + }; + + { + let mut start = 1 as int; + for state in options.iter() { + start = start * 2; + state.if_verbose(|| start) + } + assert_eq!(start, (2 as int).pow(10)); + } + + { + let mut start = 1 as int; + let mut log = Logger::new(); + let factory = options.instantiate() + .with_iteration_number() + .into_log(&mut log); + for state in factory.iter() { + start = start * 2; + state.if_verbose(|| start) + } + assert_eq!(start, (2 as int).pow(10)); + assert_eq!(log.data() + .iter() + .map(|LogItem{ data : v, iter : _ }| v.clone()) + .collect::>(), + (1..10).map(|i| (2 as int).pow(i)) + .skip(2) + .step_by(3) + .collect::>()) + } + } + } diff -r d14c877e14b7 -r b3c35d16affe src/lib.rs --- a/src/lib.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/lib.rs Mon Feb 03 19:22:16 2025 -0500 @@ -7,22 +7,16 @@ #![allow(mixed_script_confusables)] #![allow(confusable_idents)] -#![feature(maybe_uninit_uninit_array,maybe_uninit_array_assume_init,maybe_uninit_slice)] -#![feature(try_trait_v2_residual,try_trait_v2)] - -#![feature(array_methods)] - -#![feature(arc_unwrap_or_clone)] - -#![feature(float_minimum_maximum)] - -#![feature(get_mut_unchecked)] - -// They don't work: -//#![feature(negative_impls)] -//#![feature(specialization)] +#![cfg_attr(feature = "nightly", + feature(maybe_uninit_uninit_array,maybe_uninit_array_assume_init,maybe_uninit_slice), + feature(float_minimum_maximum), + feature(get_mut_unchecked), + feature(cow_is_borrowed), +)] pub mod types; +pub mod instance; +pub mod collection; pub mod nanleast; pub mod error; pub mod parallelism; @@ -45,6 +39,10 @@ pub mod fe_model; pub mod bisection_tree; pub mod nalgebra_support; - +pub(crate) mod metaprogramming; +pub mod direct_product; +pub mod convex; +pub mod discrete_gradient; +pub mod operator_arithmetic; pub use types::*; diff -r d14c877e14b7 -r b3c35d16affe src/linops.rs --- a/src/linops.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/linops.rs Mon Feb 03 19:22:16 2025 -0500 @@ -4,58 +4,79 @@ use numeric_literals::replace_float_literals; use std::marker::PhantomData; +use serde::Serialize; use crate::types::*; -use serde::Serialize; -pub use crate::mapping::Apply; +pub use crate::mapping::{Mapping, Space, Composition}; +use crate::direct_product::Pair; +use crate::instance::Instance; +use crate::norms::{NormExponent, PairNorm, L1, L2, Linfinity, Norm}; /// Trait for linear operators on `X`. -pub trait Linear : Apply - + for<'a> Apply<&'a X, Output=Self::Codomain> { - type Codomain; -} +pub trait Linear : Mapping +{ } /// Efficient in-place summation. #[replace_float_literals(F::cast_from(literal))] -pub trait AXPY { +pub trait AXPY : Space + std::ops::MulAssign +where + F : Num, + X : Space, +{ + type Owned : AXPY; + /// Computes `y = βy + αx`, where `y` is `Self`. - fn axpy(&mut self, α : F, x : &X, β : F); + fn axpy>(&mut self, α : F, x : I, β : F); /// Copies `x` to `self`. - fn copy_from(&mut self, x : &X) { + fn copy_from>(&mut self, x : I) { self.axpy(1.0, x, 0.0) } /// Computes `y = αx`, where `y` is `Self`. - fn scale_from(&mut self, α : F, x : &X) { + fn scale_from>(&mut self, α : F, x : I) { self.axpy(α, x, 0.0) } + + /// Return a similar zero as `self`. + fn similar_origin(&self) -> Self::Owned; + + /// Set self to zero. + fn set_zero(&mut self); } /// Efficient in-place application for [`Linear`] operators. #[replace_float_literals(F::cast_from(literal))] -pub trait GEMV>::Codomain> : Linear { +pub trait GEMV>::Codomain> : Linear { /// Computes `y = αAx + βy`, where `A` is `Self`. - fn gemv(&self, y : &mut Y, α : F, x : &X, β : F); + fn gemv>(&self, y : &mut Y, α : F, x : I, β : F); + #[inline] /// Computes `y = Ax`, where `A` is `Self` - fn apply_mut(&self, y : &mut Y, x : &X){ + fn apply_mut>(&self, y : &mut Y, x : I){ self.gemv(y, 1.0, x, 0.0) } + #[inline] /// Computes `y += Ax`, where `A` is `Self` - fn apply_add(&self, y : &mut Y, x : &X){ + fn apply_add>(&self, y : &mut Y, x : I){ self.gemv(y, 1.0, x, 1.0) } } /// Bounded linear operators -pub trait BoundedLinear : Linear { - type FloatType : Float; +pub trait BoundedLinear : Linear +where + F : Num, + X : Space + Norm, + XExp : NormExponent, + CodExp : NormExponent +{ /// A bound on the operator norm $\|A\|$ for the linear operator $A$=`self`. /// This is not expected to be the norm, just any bound on it that can be - /// reasonably implemented. - fn opnorm_bound(&self) -> Self::FloatType; + /// reasonably implemented. The [`NormExponent`] `xexp` indicates the norm + /// in `X`, and `codexp` in the codomain. + fn opnorm_bound(&self, xexp : XExp, codexp : CodExp) -> F; } // Linear operator application into mutable target. The [`AsRef`] bound @@ -69,16 +90,16 @@ }*/ /// Trait for forming the adjoint operator of `Self`. -pub trait Adjointable : Linear { - type AdjointCodomain; +pub trait Adjointable : Linear +where + X : Space, + Yʹ : Space, +{ + type AdjointCodomain : Space; type Adjoint<'a> : Linear where Self : 'a; /// Form the adjoint operator of `self`. fn adjoint(&self) -> Self::Adjoint<'_>; - - /*fn adjoint_apply(&self, y : &Yʹ) -> Self::AdjointCodomain { - self.adjoint().apply(y) - }*/ } /// Trait for forming a preadjoint of an operator. @@ -88,67 +109,576 @@ /// operator. The space `Ypre` is the predual of its codomain, and should be the /// domain of the adjointed operator. `Self::Preadjoint` should be /// [`Adjointable`]`<'a,Ypre,X>`. -pub trait Preadjointable : Linear { - type PreadjointCodomain; - type Preadjoint<'a> : Adjointable where Self : 'a; +/// We do not make additional restrictions on `Self::Preadjoint` (in particular, it +/// does not have to be adjointable) to allow `X` to be a subspace yet the preadjoint +/// have the full space as the codomain, etc. +pub trait Preadjointable : Linear { + type PreadjointCodomain : Space; + type Preadjoint<'a> : Linear< + Ypre, Codomain=Self::PreadjointCodomain + > where Self : 'a; - /// Form the preadjoint operator of `self`. + /// Form the adjoint operator of `self`. fn preadjoint(&self) -> Self::Preadjoint<'_>; } -/// Adjointable operators $A: X → Y$ on between reflexive spaces $X$ and $Y$. -pub trait SimplyAdjointable : Adjointable>::Codomain> {} -impl<'a,X,T> SimplyAdjointable for T where T : Adjointable>::Codomain> {} +/// Adjointable operators $A: X → Y$ between reflexive spaces $X$ and $Y$. +pub trait SimplyAdjointable : Adjointable>::Codomain> {} +impl<'a,X : Space, T> SimplyAdjointable for T +where T : Adjointable>::Codomain> {} /// The identity operator #[derive(Clone,Copy,Debug,Serialize,Eq,PartialEq)] pub struct IdOp (PhantomData); impl IdOp { - fn new() -> IdOp { IdOp(PhantomData) } + pub fn new() -> IdOp { IdOp(PhantomData) } } -impl Apply for IdOp { - type Output = X; +impl Mapping for IdOp { + type Codomain = X; - fn apply(&self, x : X) -> X { - x + fn apply>(&self, x : I) -> X { + x.own() } } -impl<'a, X> Apply<&'a X> for IdOp where X : Clone { - type Output = X; - - fn apply(&self, x : &'a X) -> X { - x.clone() - } -} - -impl Linear for IdOp where X : Clone { - type Codomain = X; -} - +impl Linear for IdOp +{ } #[replace_float_literals(F::cast_from(literal))] -impl GEMV for IdOp where Y : AXPY, X : Clone { +impl GEMV for IdOp +where + Y : AXPY, + X : Clone + Space +{ // Computes `y = αAx + βy`, where `A` is `Self`. - fn gemv(&self, y : &mut Y, α : F, x : &X, β : F) { + fn gemv>(&self, y : &mut Y, α : F, x : I, β : F) { y.axpy(α, x, β) } - fn apply_mut(&self, y : &mut Y, x : &X){ + fn apply_mut>(&self, y : &mut Y, x : I){ y.copy_from(x); } } -impl BoundedLinear for IdOp where X : Clone { - type FloatType = float; - fn opnorm_bound(&self) -> float { 1.0 } +impl BoundedLinear for IdOp +where + X : Space + Clone + Norm, + F : Num, + E : NormExponent +{ + fn opnorm_bound(&self, _xexp : E, _codexp : E) -> F { F::ONE } } -impl Adjointable for IdOp where X : Clone { +impl Adjointable for IdOp { type AdjointCodomain=X; type Adjoint<'a> = IdOp where X : 'a; + fn adjoint(&self) -> Self::Adjoint<'_> { IdOp::new() } } +impl Preadjointable for IdOp { + type PreadjointCodomain=X; + type Preadjoint<'a> = IdOp where X : 'a; + + fn preadjoint(&self) -> Self::Preadjoint<'_> { IdOp::new() } +} + + +/// The zero operator +#[derive(Clone,Copy,Debug,Serialize,Eq,PartialEq)] +pub struct ZeroOp<'a, X, XD, Y, F> { + zero : &'a Y, // TODO: don't pass this in `new`; maybe not even store. + dual_or_predual_zero : XD, + _phantoms : PhantomData<(X, Y, F)>, +} + +// TODO: Need to make Zero in Instance. + +impl<'a, F : Num, X : Space, XD, Y : Space + Clone> ZeroOp<'a, X, XD, Y, F> { + pub fn new(zero : &'a Y, dual_or_predual_zero : XD) -> Self { + ZeroOp{ zero, dual_or_predual_zero, _phantoms : PhantomData } + } +} + +impl<'a, F : Num, X : Space, XD, Y : AXPY + Clone> Mapping for ZeroOp<'a, X, XD, Y, F> { + type Codomain = Y; + + fn apply>(&self, _x : I) -> Y { + self.zero.clone() + } +} + +impl<'a, F : Num, X : Space, XD, Y : AXPY + Clone> Linear for ZeroOp<'a, X, XD, Y, F> +{ } + +#[replace_float_literals(F::cast_from(literal))] +impl<'a, F, X, XD, Y> GEMV for ZeroOp<'a, X, XD, Y, F> +where + F : Num, + Y : AXPY + Clone, + X : Space +{ + // Computes `y = αAx + βy`, where `A` is `Self`. + fn gemv>(&self, y : &mut Y, _α : F, _x : I, β : F) { + *y *= β; + } + + fn apply_mut>(&self, y : &mut Y, _x : I){ + y.set_zero(); + } +} + +impl<'a, F, X, XD, Y, E1, E2> BoundedLinear for ZeroOp<'a, X, XD, Y, F> +where + X : Space + Norm, + Y : AXPY + Clone + Norm, + F : Num, + E1 : NormExponent, + E2 : NormExponent, +{ + fn opnorm_bound(&self, _xexp : E1, _codexp : E2) -> F { F::ZERO } +} + +impl<'a, F : Num, X, XD, Y, Yprime : Space> Adjointable for ZeroOp<'a, X, XD, Y, F> +where + X : Space, + Y : AXPY + Clone + 'static, + XD : AXPY + Clone + 'static, +{ + type AdjointCodomain = XD; + type Adjoint<'b> = ZeroOp<'b, Yprime, (), XD, F> where Self : 'b; + // () means not (pre)adjointable. + + fn adjoint(&self) -> Self::Adjoint<'_> { + ZeroOp::new(&self.dual_or_predual_zero, ()) + } +} + +impl<'a, F, X, XD, Y, Ypre> Preadjointable for ZeroOp<'a, X, XD, Y, F> +where + F : Num, + X : Space, + Y : AXPY + Clone, + Ypre : Space, + XD : AXPY + Clone + 'static, +{ + type PreadjointCodomain = XD; + type Preadjoint<'b> = ZeroOp<'b, Ypre, (), XD, F> where Self : 'b; + // () means not (pre)adjointable. + + fn preadjoint(&self) -> Self::Preadjoint<'_> { + ZeroOp::new(&self.dual_or_predual_zero, ()) + } +} + +impl Linear for Composition +where + X : Space, + T : Linear, + S : Linear +{ } + +impl GEMV for Composition +where + F : Num, + X : Space, + T : Linear, + S : GEMV, +{ + fn gemv>(&self, y : &mut Y, α : F, x : I, β : F) { + self.outer.gemv(y, α, self.inner.apply(x), β) + } + + /// Computes `y = Ax`, where `A` is `Self` + fn apply_mut>(&self, y : &mut Y, x : I){ + self.outer.apply_mut(y, self.inner.apply(x)) + } + + /// Computes `y += Ax`, where `A` is `Self` + fn apply_add>(&self, y : &mut Y, x : I){ + self.outer.apply_add(y, self.inner.apply(x)) + } +} + +impl BoundedLinear for Composition +where + F : Num, + X : Space + Norm, + Z : Space + Norm, + Xexp : NormExponent, + Yexp : NormExponent, + Zexp : NormExponent, + T : BoundedLinear, + S : BoundedLinear, +{ + fn opnorm_bound(&self, xexp : Xexp, yexp : Yexp) -> F { + let zexp = self.intermediate_norm_exponent; + self.outer.opnorm_bound(zexp, yexp) * self.inner.opnorm_bound(xexp, zexp) + } +} + +/// “Row operator” $(S, T)$; $(S, T)(x, y)=Sx + Ty$. +pub struct RowOp(pub S, pub T); + +use std::ops::Add; + +impl Mapping> for RowOp +where + A : Space, + B : Space, + S : Mapping, + T : Mapping, + S::Codomain : Add, + >::Output : Space, + +{ + type Codomain = >::Output; + + fn apply>>(&self, x : I) -> Self::Codomain { + let Pair(a, b) = x.decompose(); + self.0.apply(a) + self.1.apply(b) + } +} + +impl Linear> for RowOp +where + A : Space, + B : Space, + S : Linear, + T : Linear, + S::Codomain : Add, + >::Output : Space, +{ } + + +impl<'b, F, S, T, Y, U, V> GEMV, Y> for RowOp +where + U : Space, + V : Space, + S : GEMV, + T : GEMV, + F : Num, + Self : Linear, Codomain=Y> +{ + fn gemv>>(&self, y : &mut Y, α : F, x : I, β : F) { + let Pair(u, v) = x.decompose(); + self.0.gemv(y, α, u, β); + self.1.gemv(y, α, v, F::ONE); + } + + fn apply_mut>>(&self, y : &mut Y, x : I) { + let Pair(u, v) = x.decompose(); + self.0.apply_mut(y, u); + self.1.apply_add(y, v); + } + + /// Computes `y += Ax`, where `A` is `Self` + fn apply_add>>(&self, y : &mut Y, x : I) { + let Pair(u, v) = x.decompose(); + self.0.apply_add(y, u); + self.1.apply_add(y, v); + } +} + +/// “Column operator” $(S; T)$; $(S; T)x=(Sx, Tx)$. +pub struct ColOp(pub S, pub T); + +impl Mapping for ColOp +where + A : Space, + S : Mapping, + T : Mapping, +{ + type Codomain = Pair; + + fn apply>(&self, a : I) -> Self::Codomain { + Pair(self.0.apply(a.ref_instance()), self.1.apply(a)) + } +} + +impl Linear for ColOp +where + A : Space, + S : Mapping, + T : Mapping, +{ } + +impl GEMV> for ColOp +where + X : Space, + S : GEMV, + T : GEMV, + F : Num, + Self : Linear> +{ + fn gemv>(&self, y : &mut Pair, α : F, x : I, β : F) { + self.0.gemv(&mut y.0, α, x.ref_instance(), β); + self.1.gemv(&mut y.1, α, x, β); + } + + fn apply_mut>(&self, y : &mut Pair, x : I){ + self.0.apply_mut(&mut y.0, x.ref_instance()); + self.1.apply_mut(&mut y.1, x); + } + + /// Computes `y += Ax`, where `A` is `Self` + fn apply_add>(&self, y : &mut Pair, x : I){ + self.0.apply_add(&mut y.0, x.ref_instance()); + self.1.apply_add(&mut y.1, x); + } +} + + +impl Adjointable, Yʹ> for RowOp +where + A : Space, + B : Space, + Yʹ : Space, + S : Adjointable, + T : Adjointable, + Self : Linear>, + // for<'a> ColOp, T::Adjoint<'a>> : Linear< + // Yʹ, + // Codomain=Pair + // >, +{ + type AdjointCodomain = Pair; + type Adjoint<'a> = ColOp, T::Adjoint<'a>> where Self : 'a; + + fn adjoint(&self) -> Self::Adjoint<'_> { + ColOp(self.0.adjoint(), self.1.adjoint()) + } +} + +impl Preadjointable, Yʹ> for RowOp +where + A : Space, + B : Space, + Yʹ : Space, + S : Preadjointable, + T : Preadjointable, + Self : Linear>, + for<'a> ColOp, T::Preadjoint<'a>> : Linear< + Yʹ, Codomain=Pair, + >, +{ + type PreadjointCodomain = Pair; + type Preadjoint<'a> = ColOp, T::Preadjoint<'a>> where Self : 'a; + + fn preadjoint(&self) -> Self::Preadjoint<'_> { + ColOp(self.0.preadjoint(), self.1.preadjoint()) + } +} + + +impl Adjointable> for ColOp +where + A : Space, + Xʹ : Space, + Yʹ : Space, + R : Space + ClosedAdd, + S : Adjointable, + T : Adjointable, + Self : Linear, + // for<'a> RowOp, T::Adjoint<'a>> : Linear< + // Pair, + // Codomain=R, + // >, +{ + type AdjointCodomain = R; + type Adjoint<'a> = RowOp, T::Adjoint<'a>> where Self : 'a; + + fn adjoint(&self) -> Self::Adjoint<'_> { + RowOp(self.0.adjoint(), self.1.adjoint()) + } +} + +impl Preadjointable> for ColOp +where + A : Space, + Xʹ : Space, + Yʹ : Space, + R : Space + ClosedAdd, + S : Preadjointable, + T : Preadjointable, + Self : Linear, + for<'a> RowOp, T::Preadjoint<'a>> : Linear< + Pair, Codomain = R, + >, +{ + type PreadjointCodomain = R; + type Preadjoint<'a> = RowOp, T::Preadjoint<'a>> where Self : 'a; + + fn preadjoint(&self) -> Self::Preadjoint<'_> { + RowOp(self.0.preadjoint(), self.1.preadjoint()) + } +} + +/// Diagonal operator +pub struct DiagOp(pub S, pub T); + +impl Mapping> for DiagOp +where + A : Space, + B : Space, + S : Mapping, + T : Mapping, +{ + type Codomain = Pair; + + fn apply>>(&self, x : I) -> Self::Codomain { + let Pair(a, b) = x.decompose(); + Pair(self.0.apply(a), self.1.apply(b)) + } +} + +impl Linear> for DiagOp +where + A : Space, + B : Space, + S : Linear, + T : Linear, +{ } + +impl GEMV, Pair> for DiagOp +where + A : Space, + B : Space, + U : Space, + V : Space, + S : GEMV, + T : GEMV, + F : Num, + Self : Linear, Codomain=Pair>, +{ + fn gemv>>(&self, y : &mut Pair, α : F, x : I, β : F) { + let Pair(u, v) = x.decompose(); + self.0.gemv(&mut y.0, α, u, β); + self.1.gemv(&mut y.1, α, v, β); + } + + fn apply_mut>>(&self, y : &mut Pair, x : I){ + let Pair(u, v) = x.decompose(); + self.0.apply_mut(&mut y.0, u); + self.1.apply_mut(&mut y.1, v); + } + + /// Computes `y += Ax`, where `A` is `Self` + fn apply_add>>(&self, y : &mut Pair, x : I){ + let Pair(u, v) = x.decompose(); + self.0.apply_add(&mut y.0, u); + self.1.apply_add(&mut y.1, v); + } +} + +impl Adjointable, Pair> for DiagOp +where + A : Space, + B : Space, + Xʹ: Space, + Yʹ : Space, + R : Space, + S : Adjointable, + T : Adjointable, + Self : Linear>, + for<'a> DiagOp, T::Adjoint<'a>> : Linear< + Pair, Codomain=R, + >, +{ + type AdjointCodomain = R; + type Adjoint<'a> = DiagOp, T::Adjoint<'a>> where Self : 'a; + + fn adjoint(&self) -> Self::Adjoint<'_> { + DiagOp(self.0.adjoint(), self.1.adjoint()) + } +} + +impl Preadjointable, Pair> for DiagOp +where + A : Space, + B : Space, + Xʹ: Space, + Yʹ : Space, + R : Space, + S : Preadjointable, + T : Preadjointable, + Self : Linear>, + for<'a> DiagOp, T::Preadjoint<'a>> : Linear< + Pair, Codomain=R, + >, +{ + type PreadjointCodomain = R; + type Preadjoint<'a> = DiagOp, T::Preadjoint<'a>> where Self : 'a; + + fn preadjoint(&self) -> Self::Preadjoint<'_> { + DiagOp(self.0.preadjoint(), self.1.preadjoint()) + } +} + +/// Block operator +pub type BlockOp = ColOp, RowOp>; + + +macro_rules! pairnorm { + ($expj:ty) => { + impl + BoundedLinear, PairNorm, ExpR, F> + for RowOp + where + F : Float, + A : Space + Norm, + B : Space + Norm, + S : BoundedLinear, + T : BoundedLinear, + S::Codomain : Add, + >::Output : Space, + ExpA : NormExponent, + ExpB : NormExponent, + ExpR : NormExponent, + { + fn opnorm_bound( + &self, + PairNorm(expa, expb, _) : PairNorm, + expr : ExpR + ) -> F { + // An application of the triangle inequality bounds the norm by the maximum + // of the individual norms. A simple observation shows this to be exact. + let na = self.0.opnorm_bound(expa, expr); + let nb = self.1.opnorm_bound(expb, expr); + na.max(nb) + } + } + + impl + BoundedLinear, F> + for ColOp + where + F : Float, + A : Space + Norm, + S : BoundedLinear, + T : BoundedLinear, + ExpA : NormExponent, + ExpS : NormExponent, + ExpT : NormExponent, + { + fn opnorm_bound( + &self, + expa : ExpA, + PairNorm(exps, expt, _) : PairNorm + ) -> F { + // This is based on the rule for RowOp and ‖A^*‖ = ‖A‖, hence, + // for A=[S; T], ‖A‖=‖[S^*, T^*]‖ ≤ max{‖S^*‖, ‖T^*‖} = max{‖S‖, ‖T‖} + let ns = self.0.opnorm_bound(expa, exps); + let nt = self.1.opnorm_bound(expa, expt); + ns.max(nt) + } + } + } +} + +pairnorm!(L1); +pairnorm!(L2); +pairnorm!(Linfinity); + diff -r d14c877e14b7 -r b3c35d16affe src/linsolve.rs --- a/src/linsolve.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/linsolve.rs Mon Feb 03 19:22:16 2025 -0500 @@ -3,6 +3,7 @@ */ use crate::types::Float; +#[cfg(feature = "nightly")] use std::mem::MaybeUninit; /// Gaussian elimination for $AX=B$, where $A$ and $B$ are both stored in `ab`, @@ -45,27 +46,44 @@ // Solve UAX=UB for X where UA with U presenting the transformations above an // upper triangular matrix. + // + // If the "nightly" feature is enabled, we will use an uninitialised array for a + // little bit of efficiency. // This use of MaybeUninit assumes F : Copy. Otherwise undefined behaviour may occur. - let mut x : [[MaybeUninit; K]; M] = core::array::from_fn(|_| MaybeUninit::uninit_array::() ); - //unsafe { std::mem::MaybeUninit::uninit().assume_init() }; - for i in (0..M).rev() { - for 𝓁 in 0..K { - let mut tmp = ab[i][M+𝓁]; - for j in (i+1)..M { - tmp -= ab[i][j] * unsafe { *(x[j][𝓁].assume_init_ref()) }; + #[cfg(feature = "nightly")] + { + let mut x : [[MaybeUninit; K]; M] = core::array::from_fn(|_| MaybeUninit::uninit_array::() ); + //unsafe { std::mem::MaybeUninit::uninit().assume_init() }; + for i in (0..M).rev() { + for 𝓁 in 0..K { + let mut tmp = ab[i][M+𝓁]; + for j in (i+1)..M { + tmp -= ab[i][j] * unsafe { *(x[j][𝓁].assume_init_ref()) }; + } + tmp /= ab[i][i]; + x[i][𝓁].write(tmp); } - tmp /= ab[i][i]; - x[i][𝓁].write(tmp); + } + unsafe { + //core::intrinsics::assert_inhabited::<[[F; K]; M]>(); + (&x as *const _ as *const [[F; K]; M]).read() } } - //unsafe { MaybeUninit::array_assume_init(x) }; - let xinit = unsafe { - //core::intrinsics::assert_inhabited::<[[F; K]; M]>(); - (&x as *const _ as *const [[F; K]; M]).read() - }; - - //std::mem::forget(x); - xinit + #[cfg(not(feature = "nightly"))] + { + let mut x : [[F; K]; M] = [[F::ZERO; K]; M]; + for i in (0..M).rev() { + for 𝓁 in 0..K { + let mut tmp = ab[i][M+𝓁]; + for j in (i+1)..M { + tmp -= ab[i][j] * x[j][𝓁]; + } + tmp /= ab[i][i]; + x[i][𝓁] = tmp; + } + } + x + } } /// Gaussian elimination for $Ax=b$, where $A$ and $b$ are both stored in `ab`, diff -r d14c877e14b7 -r b3c35d16affe src/loc.rs --- a/src/loc.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/loc.rs Mon Feb 03 19:22:16 2025 -0500 @@ -5,13 +5,17 @@ use std::ops::{Add,Sub,AddAssign,SubAssign,Mul,Div,MulAssign,DivAssign,Neg,Index,IndexMut}; use std::slice::{Iter,IterMut}; +use std::fmt::{Display, Formatter}; use crate::types::{Float,Num,SignedNum}; use crate::maputil::{FixedLength,FixedLengthMut,map1,map2,map1_mut,map2_mut}; use crate::euclidean::*; use crate::norms::*; -use crate::linops::AXPY; +use crate::linops::{AXPY, Mapping, Linear}; +use crate::instance::{Instance, BasicDecomposition}; +use crate::mapping::Space; use serde::ser::{Serialize, Serializer, SerializeSeq}; + /// A container type for (short) `N`-dimensional vectors of element type `F`. /// /// Supports basic operations of an [`Euclidean`] space, several [`Norm`]s, and @@ -22,6 +26,19 @@ pub [F; N] ); +impl Display for Loc{ + // Required method + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "[")?; + let mut comma = ""; + for e in self.iter() { + write!(f, "{comma}{e}")?; + comma = ", "; + } + write!(f, "]") + } +} + // Need to manually implement as [F; N] serialisation is provided only for some N. impl Serialize for Loc where @@ -135,6 +152,14 @@ } } +impl Loc { + #[inline] + pub fn flatten1d(self) -> F { + let Loc([v]) = self; + v + } +} + impl From> for [F; N] { #[inline] fn from(other : Loc) -> [F; N] { @@ -237,6 +262,20 @@ make_binop!(Add, add, AddAssign, add_assign); make_binop!(Sub, sub, SubAssign, sub_assign); +impl std::iter::Sum for Loc { + fn sum>>(mut iter: I) -> Self { + match iter.next() { + None => Self::ORIGIN, + Some(mut v) => { + for w in iter { + v += w + } + v + } + } + } +} + macro_rules! make_scalarop_rhs { ($trait:ident, $fn:ident, $trait_assign:ident, $fn_assign:ident) => { impl $trait for Loc { @@ -390,24 +429,17 @@ domination!(Linfinity, L2); domination!(L2, L1); -impl Dot,F> for Loc { +impl Euclidean for Loc { + type Output = Self; + /// This implementation is not stabilised as it's meant to be used for very small vectors. /// Use [`nalgebra`] for larger vectors. #[inline] - fn dot(&self, other : &Loc) -> F { + fn dot>(&self, other : I) -> F { self.0.iter() - .zip(other.0.iter()) + .zip(other.ref_instance().0.iter()) .fold(F::ZERO, |m, (&v, &w)| m + v * w) } -} - -impl Euclidean for Loc { - type Output = Self; - - #[inline] - fn similar_origin(&self) -> Self { - Self::ORIGIN - } /// This implementation is not stabilised as it's meant to be used for very small vectors. /// Use [`nalgebra`] for larger vectors. @@ -416,9 +448,9 @@ self.iter().fold(F::ZERO, |m, &v| m + v * v) } - fn dist2_squared(&self, other : &Self) -> F { + fn dist2_squared>(&self, other : I) -> F { self.iter() - .zip(other.iter()) + .zip(other.ref_instance().iter()) .fold(F::ZERO, |m, (&v, &w)| { let d = v - w; m + d * d }) } @@ -433,10 +465,11 @@ } #[inline] - fn dist2(&self, other : &Self) -> F { + fn dist2>(&self, other : I) -> F { // Optimisation for N==1 that avoids squaring and square rooting. + let otherr = other.ref_instance(); if N==1 { - unsafe { *self.0.get_unchecked(0) - *other.0.get_unchecked(0) }.abs() + unsafe { *self.0.get_unchecked(0) - *otherr.0.get_unchecked(0) }.abs() } else { self.dist2_squared(other).sqrt() } @@ -444,9 +477,94 @@ } impl Loc { + /// Origin point pub const ORIGIN : Self = Loc([F::ZERO; N]); } +impl, const N : usize> Loc { + /// Reflects along the given coordinate + pub fn reflect(mut self, i : usize) -> Self { + self[i] = -self[i]; + self + } + + /// Reflects along the given coordinate sequences + pub fn reflect_many>(mut self, idxs : I) -> Self { + for i in idxs { + self[i]=-self[i] + } + self + } +} + +impl> Loc { + /// Reflect x coordinate + pub fn reflect_x(self) -> Self { + let Loc([x, y]) = self; + [-x, y].into() + } + + /// Reflect y coordinate + pub fn reflect_y(self) -> Self { + let Loc([x, y]) = self; + [x, -y].into() + } +} + +impl Loc { + /// Rotate by angle φ + pub fn rotate(self, φ : F) -> Self { + let Loc([x, y]) = self; + let sin_φ = φ.sin(); + let cos_φ = φ.cos(); + [cos_φ * x - sin_φ * y, + sin_φ * x + cos_φ * y].into() + } +} + +impl> Loc { + /// Reflect x coordinate + pub fn reflect_x(self) -> Self { + let Loc([x, y, z]) = self; + [-x, y, z].into() + } + + /// Reflect y coordinate + pub fn reflect_y(self) -> Self { + let Loc([x, y, z]) = self; + [x, -y, z].into() + } + + /// Reflect y coordinate + pub fn reflect_z(self) -> Self { + let Loc([x, y, z]) = self; + [x, y, -z].into() + } +} + +impl Loc { + /// Rotate by angles (yaw, pitch, roll) + pub fn rotate(self, Loc([φ, ψ, θ]) : Self) -> Self { + let Loc([mut x, mut y, mut z]) = self; + let sin_φ = φ.sin(); + let cos_φ = φ.cos(); + [x, y, z] = [cos_φ * x - sin_φ *y, + sin_φ * x + cos_φ * y, + z]; + let sin_ψ = ψ.sin(); + let cos_ψ = ψ.cos(); + [x, y, z] = [cos_ψ * x + sin_ψ * z, + y, + -sin_ψ * x + cos_ψ * z]; + let sin_θ = θ.sin(); + let cos_θ = θ.cos(); + [x, y, z] = [x, + cos_θ * y - sin_θ * z, + sin_θ * y + cos_θ * z]; + [x, y, z].into() + } +} + impl StaticEuclidean for Loc { #[inline] fn origin() -> Self { @@ -454,6 +572,31 @@ } } + +/// The default norm for `Loc` is [`L2`]. +impl Normed for Loc { + type NormExp = L2; + + #[inline] + fn norm_exponent(&self) -> Self::NormExp { + L2 + } + + // #[inline] + // fn similar_origin(&self) -> Self { + // [F::ZERO; N].into() + // } + + #[inline] + fn is_zero(&self) -> bool { + self.norm2_squared() == F::ZERO + } +} + +impl HasDual for Loc { + type DualSpace = Self; +} + impl Norm for Loc { #[inline] fn norm(&self, _ : L2) -> F { self.norm2() } @@ -461,9 +604,20 @@ impl Dist for Loc { #[inline] - fn dist(&self, other : &Self, _ : L2) -> F { self.dist2(other) } + fn dist>(&self, other : I, _ : L2) -> F { self.dist2(other) } } +/* Implemented automatically as Euclidean. +impl Projection for Loc { + #[inline] + fn proj_ball_mut(&mut self, ρ : F, nrm : L2) { + let n = self.norm(nrm); + if n > ρ { + *self *= ρ/n; + } + } +}*/ + impl Norm for Loc { /// This implementation is not stabilised as it's meant to be used for very small vectors. /// Use [`nalgebra`] for larger vectors. @@ -475,9 +629,9 @@ impl Dist for Loc { #[inline] - fn dist(&self, other : &Self, _ : L1) -> F { + fn dist>(&self, other : I, _ : L1) -> F { self.iter() - .zip(other.iter()) + .zip(other.ref_instance().iter()) .fold(F::ZERO, |m, (&v, &w)| m + (v-w).abs() ) } } @@ -500,9 +654,9 @@ impl Dist for Loc { #[inline] - fn dist(&self, other : &Self, _ : Linfinity) -> F { + fn dist>(&self, other : I, _ : Linfinity) -> F { self.iter() - .zip(other.iter()) + .zip(other.ref_instance().iter()) .fold(F::ZERO, |m, (&v, &w)| m.max((v-w).abs())) } } @@ -536,19 +690,48 @@ } } -impl AXPY> for Loc { + +impl Space for Loc { + type Decomp = BasicDecomposition; +} + +impl Mapping> for Loc { + type Codomain = F; + + fn apply>>(&self, x : I) -> Self::Codomain { + x.eval(|x̃| self.dot(x̃)) + } +} + +impl Linear> for Loc { } + +impl AXPY> for Loc { + type Owned = Self; #[inline] - fn axpy(&mut self, α : F, x : &Loc, β : F) { - if β == F::ZERO { - map2_mut(self, x, |yi, xi| { *yi = α * (*xi) }) - } else { - map2_mut(self, x, |yi, xi| { *yi = β * (*yi) + α * (*xi) }) - } + fn axpy>>(&mut self, α : F, x : I, β : F) { + x.eval(|x̃| { + if β == F::ZERO { + map2_mut(self, x̃, |yi, xi| { *yi = α * (*xi) }) + } else { + map2_mut(self, x̃, |yi, xi| { *yi = β * (*yi) + α * (*xi) }) + } + }) } #[inline] - fn copy_from(&mut self, x : &Loc) { - map2_mut(self, x, |yi, xi| *yi = *xi ) + fn copy_from>>(&mut self, x : I) { + x.eval(|x̃| map2_mut(self, x̃, |yi, xi| *yi = *xi )) + } + + #[inline] + fn similar_origin(&self) -> Self::Owned { + Self::ORIGIN + } + + #[inline] + fn set_zero(&mut self) { + *self = Self::ORIGIN; } } + diff -r d14c877e14b7 -r b3c35d16affe src/logger.rs --- a/src/logger.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/logger.rs Mon Feb 03 19:22:16 2025 -0500 @@ -28,6 +28,11 @@ pub fn data(&self) -> &Vec { &self.data } + + /// Map the log with `g`. + pub fn map(self, g : impl FnMut(V) -> W) -> Logger { + Logger { data : self.data.into_iter().map(g).collect() } + } } impl<'a, V : Serialize + 'a> TableDump<'a> for Logger { diff -r d14c877e14b7 -r b3c35d16affe src/mapping.rs --- a/src/mapping.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/mapping.rs Mon Feb 03 19:22:16 2025 -0500 @@ -3,115 +3,248 @@ */ use std::marker::PhantomData; -use crate::types::{Float}; -use serde::Serialize; +use std::borrow::Cow; +use crate::types::{Num, Float, ClosedMul}; use crate::loc::Loc; +pub use crate::instance::{Instance, Decomposition, BasicDecomposition, Space}; +use crate::norms::{Norm, NormExponent}; +use crate::operator_arithmetic::{Weighted, Constant}; -/// Trait for application of `Self` as a mathematical function or operator on `X`. -pub trait Apply { - type Output; +/// A mapping from `Domain` to `Self::Codomain`. +pub trait Mapping { + type Codomain : Space; /// Compute the value of `self` at `x`. - fn apply(&self, x : X) -> Self::Output; -} + fn apply>(&self, x : I) -> Self::Codomain; -/// This blanket implementation is a workaround helper to Rust trait system limitations. -/// -/// It is introduced because the trait system does not allow blanket implementations of both -/// [`Apply`] and [`Apply<&'a X>`]. With this, the latter is implemented automatically for -/// the reference, which can be sufficient to apply the operation in another blanket implementation. -impl<'a, T, X> Apply for &'a T where T : Apply { - type Output = >::Output; + #[inline] + /// Form the composition `self ∘ other` + fn compose>(self, other : T) + -> Composition + where + Self : Sized + { + Composition{ outer : self, inner : other, intermediate_norm_exponent : () } + } + #[inline] - fn apply(&self, x : X) -> Self::Output { - (*self).apply(x) + /// Form the composition `self ∘ other`, assigning a norm to the inermediate space + fn compose_with_norm( + self, other : T, norm : E + ) -> Composition + where + Self : Sized, + X : Space, + T : Mapping, + E : NormExponent, + Domain : Norm, + F : Num + { + Composition{ outer : self, inner : other, intermediate_norm_exponent : norm } + } + + /// Multiply `self` by the scalar `a`. + #[inline] + fn weigh(self, a : C) -> Weighted + where + Self : Sized, + C : Constant, + Self::Codomain : ClosedMul, + { + Weighted { weight : a, base_fn : self } } } -/// A mapping from `Domain` to `Codomain`. -/// -/// This is automatically implemented when the relevant [`Apply`] are implemented. -pub trait Mapping : Apply - + for<'a> Apply<&'a Domain, Output=Self::Codomain> { - type Codomain; -} - -impl Mapping for T -where T : Apply + for<'a> Apply<&'a Domain, Output=Codomain> { - type Codomain = Codomain; -} - - -/// A helper trait alias for referring to [`Mapping`]s from [`Loc`] to `F` a [`Float`]. -pub trait RealMapping : Mapping, Codomain = F> {} +/// Automatically implemented shorthand for referring to [`Mapping`]s from [`Loc`] to `F`. +pub trait RealMapping +: Mapping, Codomain = F> {} impl RealMapping for T where T : Mapping, Codomain = F> {} - -/// Trait for calculation the differential of `Self` as a mathematical function on `X`. -pub trait Differentiate { - type Output; +/// A helper trait alias for referring to [`Mapping`]s from [`Loc`] to [`Loc`]. +pub trait RealVectorField +: Mapping, Codomain = Loc> {} - /// Compute the differential of `self` at `x`. - fn differential(&self, x : X) -> Self::Output; -} - +impl RealVectorField for T +where T : Mapping, Codomain = Loc> {} /// A differentiable mapping from `Domain` to [`Mapping::Codomain`], with differentials /// `Differential`. /// -/// This is automatically implemented when the relevant [`Differentiate`] are implemented. -pub trait DifferentiableMapping -: Mapping - + Differentiate - + for<'a> Differentiate<&'a Domain, Output=Self::Differential>{ - type Differential; +/// This is automatically implemented when [`DifferentiableImpl`] is. +pub trait DifferentiableMapping : Mapping { + type DerivativeDomain : Space; + type Differential<'b> : Mapping where Self : 'b; + + /// Calculate differential at `x` + fn differential>(&self, x : I) -> Self::DerivativeDomain; + + /// Form the differential mapping of `self`. + fn diff(self) -> Self::Differential<'static>; + + /// Form the differential mapping of `self`. + fn diff_ref(&self) -> Self::Differential<'_>; } +/// Automatically implemented shorthand for referring to differentiable [`Mapping`]s from +/// [`Loc`] to `F`. +pub trait DifferentiableRealMapping +: DifferentiableMapping, Codomain = F, DerivativeDomain = Loc> {} -impl DifferentiableMapping for T -where T : Mapping - + Differentiate - + for<'a> Differentiate<&'a Domain, Output=Differential> { - type Differential = Differential; +impl DifferentiableRealMapping for T +where T : DifferentiableMapping, Codomain = F, DerivativeDomain = Loc> {} + +/// Helper trait for implementing [`DifferentiableMapping`] +pub trait DifferentiableImpl : Sized { + type Derivative : Space; + + /// Compute the differential of `self` at `x`, consuming the input. + fn differential_impl>(&self, x : I) -> Self::Derivative; } -/// A sum of [`Mapping`]s. -#[derive(Serialize, Debug, Clone)] -pub struct Sum> { - components : Vec, - _domain : PhantomData, -} +impl DifferentiableMapping for T +where + Domain : Space, + T : Clone + Mapping + DifferentiableImpl +{ + type DerivativeDomain = T::Derivative; + type Differential<'b> = Differential<'b, Domain, Self> where Self : 'b; + + #[inline] + fn differential>(&self, x : I) -> Self::DerivativeDomain { + self.differential_impl(x) + } -impl> Sum { - /// Construct from an iterator. - pub fn new>(iter : I) -> Self { - Sum { components : iter.collect(), _domain : PhantomData } + fn diff(self) -> Differential<'static, Domain, Self> { + Differential{ g : Cow::Owned(self), _space : PhantomData } + } + + fn diff_ref(&self) -> Differential<'_, Domain, Self> { + Differential{ g : Cow::Borrowed(self), _space : PhantomData } } } -impl Apply for Sum -where M : Mapping, - M::Codomain : std::iter::Sum { - type Output = M::Codomain; +/// Container for the differential [`Mapping`] of a [`DifferentiableMapping`]. +pub struct Differential<'a, X, G : Clone> { + g : Cow<'a, G>, + _space : PhantomData +} + +impl<'a, X, G : Clone> Differential<'a, X, G> { + pub fn base_fn(&self) -> &G { + &self.g + } +} + +impl<'a, X, G> Mapping for Differential<'a, X, G> +where + X : Space, + G : Clone + DifferentiableMapping +{ + type Codomain = G::DerivativeDomain; + + #[inline] + fn apply>(&self, x : I) -> Self::Codomain { + (*self.g).differential(x) + } +} - fn apply(&self, x : Domain) -> Self::Output { - self.components.iter().map(|c| c.apply(x)).sum() +/// Container for flattening [`Loc`]`` codomain of a [`Mapping`] to `F`. +pub struct FlattenedCodomain { + g : G, + _phantoms : PhantomData<(X, F)> +} + +impl Mapping for FlattenedCodomain +where + X : Space, + G: Mapping> +{ + type Codomain = F; + + #[inline] + fn apply>(&self, x : I) -> Self::Codomain { + self.g.apply(x).flatten1d() + } +} + +/// An auto-trait for constructing a [`FlattenCodomain`] structure for +/// flattening the codomain of a [`Mapping`] from [`Loc`]`` to `F`. +pub trait FlattenCodomain : Mapping> + Sized { + /// Flatten the codomain from [`Loc`]`` to `F`. + fn flatten_codomain(self) -> FlattenedCodomain { + FlattenedCodomain{ g : self, _phantoms : PhantomData } } } -impl Differentiate for Sum -where M : DifferentiableMapping, - M :: Codomain : std::iter::Sum, - M :: Differential : std::iter::Sum, - Domain : Copy { +impl>> FlattenCodomain for G {} + +/// Container for dimensional slicing [`Loc`]`` codomain of a [`Mapping`] to `F`. +pub struct SlicedCodomain<'a, X, F, G : Clone, const N : usize> { + g : Cow<'a, G>, + slice : usize, + _phantoms : PhantomData<(X, F)> +} - type Output = M::Differential; +impl<'a, X, F, G, const N : usize> Mapping for SlicedCodomain<'a, X, F, G, N> +where + X : Space, + F : Copy + Space, + G : Mapping> + Clone, +{ + type Codomain = F; - fn differential(&self, x : Domain) -> Self::Output { - self.components.iter().map(|c| c.differential(x)).sum() + #[inline] + fn apply>(&self, x : I) -> Self::Codomain { + let tmp : [F; N] = (*self.g).apply(x).into(); + // Safety: `slice_codomain` below checks the range. + unsafe { *tmp.get_unchecked(self.slice) } } } + +/// An auto-trait for constructing a [`FlattenCodomain`] structure for +/// flattening the codomain of a [`Mapping`] from [`Loc`]`` to `F`. +pub trait SliceCodomain + : Mapping> + Clone + Sized +{ + /// Flatten the codomain from [`Loc`]`` to `F`. + fn slice_codomain(self, slice : usize) -> SlicedCodomain<'static, X, F, Self, N> { + assert!(slice < N); + SlicedCodomain{ g : Cow::Owned(self), slice, _phantoms : PhantomData } + } + + /// Flatten the codomain from [`Loc`]`` to `F`. + fn slice_codomain_ref(&self, slice : usize) -> SlicedCodomain<'_, X, F, Self, N> { + assert!(slice < N); + SlicedCodomain{ g : Cow::Borrowed(self), slice, _phantoms : PhantomData } + } +} + +impl> + Clone, const N : usize> +SliceCodomain +for G {} + + +/// The composition S ∘ T. `E` is for storing a `NormExponent` for the intermediate space. +pub struct Composition { + pub outer : S, + pub inner : T, + pub intermediate_norm_exponent : E +} + +impl Mapping for Composition +where + X : Space, + T : Mapping, + S : Mapping +{ + type Codomain = S::Codomain; + + #[inline] + fn apply>(&self, x : I) -> Self::Codomain { + self.outer.apply(self.inner.apply(x)) + } +} diff -r d14c877e14b7 -r b3c35d16affe src/maputil.rs --- a/src/maputil.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/maputil.rs Mon Feb 03 19:22:16 2025 -0500 @@ -2,6 +2,7 @@ Utilities for mapping over various container types. */ +#[cfg(feature = "nightly")] use std::mem::MaybeUninit; use itertools::izip; @@ -322,8 +323,13 @@ /// /// If `iter.next()` panicks, all items already yielded by the iterator are /// dropped. +#[cfg(feature = "nightly")] #[inline] -pub(crate) fn collect_into_array_unchecked, const N: usize>(mut iter: I) -> [T; N] +pub(crate) fn collect_into_array_unchecked< + T, + I : Iterator, + const N: usize +>(mut iter: I) -> [T; N] { if N == 0 { // SAFETY: An empty array is always inhabited and has no validity invariants. @@ -375,6 +381,19 @@ unreachable!("Something went wrong with iterator length") } +#[cfg(not(feature = "nightly"))] +#[inline] +pub(crate) fn collect_into_array_unchecked< + T, + I : Iterator, + const N: usize +>(iter: I) -> [T; N] +{ + match iter.collect::>().try_into() { + Ok(a) => a, + Err(_) => panic!("collect_into_array failure: should not happen"), + } +} #[cfg(test)] mod tests { diff -r d14c877e14b7 -r b3c35d16affe src/metaprogramming.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/metaprogramming.rs Mon Feb 03 19:22:16 2025 -0500 @@ -0,0 +1,41 @@ +/*! +Metaprogramming tools +*/ + +/// Reference `x` if so indicated by the first parameter. +/// Typically to be used from another macro. +/// +/// ```ignore +/// maybe_ref!(ref, V) // ➡ &V +/// maybe_ref!(noref, V) // ➡ V +/// ``` +#[macro_export] +macro_rules! maybe_ref { + (ref, $x:expr) => { &$x }; + (noref, $x:expr) => { $x }; + (ref, $x:ty) => { &$x }; + (noref, $x:ty) => { $x }; +} + +/// Choose `a` if first argument is the literal `ref`, otherwise `b`. +#[macro_export] +macro_rules! ifref { + (noref, $a:expr, $b:expr) => { $b }; + (ref, $a:expr, $b:expr) => { $a }; +} + + +/// Annotate `x` with a lifetime if the first parameter +/// Typically to be used from another macro. +/// +/// ```ignore +/// maybe_ref!(ref, &'a V) // ➡ &'a V +/// maybe_ref!(noref, &'a V) // ➡ V +/// ``` +#[macro_export] +macro_rules! maybe_lifetime { + (ref, $x:ty) => { $x }; + (noref, &$lt:lifetime $x:ty) => { $x }; + (noref, &$x:ty) => { $x }; +} + diff -r d14c877e14b7 -r b3c35d16affe src/nalgebra_support.rs --- a/src/nalgebra_support.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/nalgebra_support.rs Mon Feb 03 19:22:16 2025 -0500 @@ -1,7 +1,7 @@ /*! Integration with nalgebra. -This module mainly implements [`Euclidean`], [`Norm`], [`Dot`], [`Linear`], etc. for [`nalgebra`] +This module mainly implements [`Euclidean`], [`Norm`], [`Linear`], etc. for [`nalgebra`] matrices and vectors. It also provides [`ToNalgebraRealField`] as a vomit-inducingly ugly workaround to nalgebra force-feeding its own versions of the same basic mathematical methods on `f32` and `f64` as @@ -10,10 +10,9 @@ use nalgebra::{ Matrix, Storage, StorageMut, OMatrix, Dim, DefaultAllocator, Scalar, - ClosedMul, ClosedAdd, SimdComplexField, Vector, OVector, RealField, + ClosedAddAssign, ClosedMulAssign, SimdComplexField, Vector, OVector, RealField, LpNorm, UniformNorm }; -use nalgebra::Norm as NalgebraNorm; use nalgebra::base::constraint::{ ShapeConstraint, SameNumberOfRows, SameNumberOfColumns }; @@ -23,102 +22,127 @@ use num_traits::identities::{Zero, One}; use crate::linops::*; use crate::euclidean::*; +use crate::mapping::{Space, BasicDecomposition}; use crate::types::Float; use crate::norms::*; +use crate::instance::Instance; -impl Apply> for Matrix -where SM: Storage, SV: Storage, - N : Dim, M : Dim, K : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator { - type Output = OMatrix; +impl Space for Matrix +where + SM: Storage + Clone, + N : Dim, M : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign, + DefaultAllocator : Allocator, +{ + type Decomp = BasicDecomposition; +} + +impl Mapping> for Matrix +where SM: Storage, SV: Storage + Clone, + N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator { + type Codomain = OMatrix; #[inline] - fn apply(&self, x : Matrix) -> Self::Output { - self.mul(x) + fn apply>>( + &self, x : I + ) -> Self::Codomain { + x.either(|owned| self.mul(owned), |refr| self.mul(refr)) } } -impl<'a, SM,SV,N,M,K,E> Apply<&'a Matrix> for Matrix -where SM: Storage, SV: Storage, - N : Dim, M : Dim, K : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator { - type Output = OMatrix; - - #[inline] - fn apply(&self, x : &'a Matrix) -> Self::Output { - self.mul(x) - } -} impl<'a, SM,SV,N,M,K,E> Linear> for Matrix -where SM: Storage, SV: Storage, - N : Dim, M : Dim, K : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator { - type Codomain = OMatrix; +where SM: Storage, SV: Storage + Clone, + N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + ClosedAddAssign + ClosedMulAssign, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator { } impl GEMV, Matrix> for Matrix -where SM: Storage, SV1: Storage, SV2: StorageMut, - N : Dim, M : Dim, K : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One + Float, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator { +where SM: Storage, SV1: Storage + Clone, SV2: StorageMut, + N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + Float, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator { #[inline] - fn gemv(&self, y : &mut Matrix, α : E, x : &Matrix, β : E) { - Matrix::gemm(y, α, self, x, β) + fn gemv>>( + &self, y : &mut Matrix, α : E, x : I, β : E + ) { + x.eval(|x̃| Matrix::gemm(y, α, self, x̃, β)) } #[inline] - fn apply_mut<'a>(&self, y : &mut Matrix, x : &Matrix) { - self.mul_to(x, y) + fn apply_mut<'a, I : Instance>>(&self, y : &mut Matrix, x : I) { + x.eval(|x̃| self.mul_to(x̃, y)) } } impl AXPY> for Vector -where SM: StorageMut, SV1: Storage, - M : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One + Float, - DefaultAllocator : Allocator { +where SM: StorageMut + Clone, SV1: Storage + Clone, + M : Dim, E : Scalar + Zero + One + Float, + DefaultAllocator : Allocator { + type Owned = OVector; #[inline] - fn axpy(&mut self, α : E, x : &Vector, β : E) { - Matrix::axpy(self, α, x, β) + fn axpy>>(&mut self, α : E, x : I, β : E) { + x.eval(|x̃| Matrix::axpy(self, α, x̃, β)) + } + + #[inline] + fn copy_from>>(&mut self, y : I) { + y.eval(|ỹ| Matrix::copy_from(self, ỹ)) + } + + #[inline] + fn set_zero(&mut self) { + self.iter_mut().for_each(|e| *e = E::ZERO); } #[inline] - fn copy_from(&mut self, y : &Vector) { - Matrix::copy_from(self, y) + fn similar_origin(&self) -> Self::Owned { + OVector::zeros_generic(M::from_usize(self.len()), Const) } } +/* Implemented automatically as Euclidean. +impl Projection for Vector +where SM: StorageMut + Clone, + M : Dim, E : Scalar + Zero + One + Float + RealField, + DefaultAllocator : Allocator { + #[inline] + fn proj_ball_mut(&mut self, ρ : E, _ : L2) { + let n = self.norm(L2); + if n > ρ { + self.iter_mut().for_each(|v| *v *= ρ/n) + } + } +}*/ + impl Projection for Vector -where SM: StorageMut, - M : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One + Float + RealField, - DefaultAllocator : Allocator { +where SM: StorageMut + Clone, + M : Dim, E : Scalar + Zero + One + Float + RealField, + DefaultAllocator : Allocator { #[inline] fn proj_ball_mut(&mut self, ρ : E, _ : Linfinity) { self.iter_mut().for_each(|v| *v = num_traits::clamp(*v, -ρ, ρ)) } } -impl<'own,SV1,SV2,SM,N,M,K,E> Adjointable,Matrix> +impl<'own,SV1,SV2,SM,N,M,K,E> Adjointable, Matrix> for Matrix -where SM: Storage, SV1: Storage, SV2: Storage, - N : Dim, M : Dim, K : Dim, E : Scalar + ClosedMul + ClosedAdd + Zero + One + SimdComplexField, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator, - DefaultAllocator : Allocator { +where SM: Storage, SV1: Storage + Clone, SV2: Storage + Clone, + N : Dim, M : Dim, K : Dim, E : Scalar + Zero + One + SimdComplexField, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator, + DefaultAllocator : Allocator { type AdjointCodomain = OMatrix; type Adjoint<'a> = OMatrix where SM : 'a; @@ -128,20 +152,6 @@ } } -impl Dot,E> -for Vector -where M : Dim, - E : Float + Scalar + ClosedMul + ClosedAdd + Zero + One, - S : Storage, - Si : Storage, - DefaultAllocator : Allocator { - - #[inline] - fn dot(&self, other : &Vector) -> E { - Vector::::dot(self, other) - } -} - /// This function is [`nalgebra::EuclideanNorm::metric_distance`] without the `sqrt`. #[inline] fn metric_distance_squared( @@ -170,15 +180,15 @@ impl Euclidean for Vector where M : Dim, - S : StorageMut, - E : Float + Scalar + ClosedMul + ClosedAdd + Zero + One + RealField, - DefaultAllocator : Allocator { + S : StorageMut + Clone, + E : Float + Scalar + Zero + One + RealField, + DefaultAllocator : Allocator { type Output = OVector; - + #[inline] - fn similar_origin(&self) -> OVector { - OVector::zeros_generic(M::from_usize(self.len()), Const) + fn dot>(&self, other : I) -> E { + Vector::::dot(self, other.ref_instance()) } #[inline] @@ -187,17 +197,17 @@ } #[inline] - fn dist2_squared(&self, other : &Self) -> E { - metric_distance_squared(self, other) + fn dist2_squared>(&self, other : I) -> E { + metric_distance_squared(self, other.ref_instance()) } } impl StaticEuclidean for Vector where M : DimName, - S : StorageMut, - E : Float + Scalar + ClosedMul + ClosedAdd + Zero + One + RealField, - DefaultAllocator : Allocator { + S : StorageMut + Clone, + E : Float + Scalar + Zero + One + RealField, + DefaultAllocator : Allocator { #[inline] fn origin() -> OVector { @@ -205,78 +215,109 @@ } } +/// The default norm for `Vector` is [`L2`]. +impl Normed +for Vector +where M : Dim, + S : Storage + Clone, + E : Float + Scalar + Zero + One + RealField, + DefaultAllocator : Allocator { + + type NormExp = L2; + + #[inline] + fn norm_exponent(&self) -> Self::NormExp { + L2 + } + + #[inline] + fn is_zero(&self) -> bool { + Vector::::norm_squared(self) == E::ZERO + } +} + +impl HasDual +for Vector +where M : Dim, + S : Storage + Clone, + E : Float + Scalar + Zero + One + RealField, + DefaultAllocator : Allocator { + // TODO: Doesn't work with different storage formats. + type DualSpace = Self; +} + impl Norm for Vector where M : Dim, - S : StorageMut, - E : Float + Scalar + ClosedMul + ClosedAdd + Zero + One + RealField, - DefaultAllocator : Allocator { + S : Storage, + E : Float + Scalar + Zero + One + RealField, + DefaultAllocator : Allocator { #[inline] fn norm(&self, _ : L1) -> E { - LpNorm(1).norm(self) + nalgebra::Norm::norm(&LpNorm(1), self) } } impl Dist for Vector where M : Dim, - S : StorageMut, - E : Float + Scalar + ClosedMul + ClosedAdd + Zero + One + RealField, - DefaultAllocator : Allocator { + S : Storage + Clone, + E : Float + Scalar + Zero + One + RealField, + DefaultAllocator : Allocator { #[inline] - fn dist(&self, other : &Self, _ : L1) -> E { - LpNorm(1).metric_distance(self, other) + fn dist>(&self, other : I, _ : L1) -> E { + nalgebra::Norm::metric_distance(&LpNorm(1), self, other.ref_instance()) } } impl Norm for Vector where M : Dim, - S : StorageMut, - E : Float + Scalar + ClosedMul + ClosedAdd + Zero + One + RealField, - DefaultAllocator : Allocator { + S : Storage, + E : Float + Scalar + Zero + One + RealField, + DefaultAllocator : Allocator { #[inline] fn norm(&self, _ : L2) -> E { - LpNorm(2).norm(self) + nalgebra::Norm::norm(&LpNorm(2), self) } } impl Dist for Vector where M : Dim, - S : StorageMut, - E : Float + Scalar + ClosedMul + ClosedAdd + Zero + One + RealField, - DefaultAllocator : Allocator { + S : Storage + Clone, + E : Float + Scalar + Zero + One + RealField, + DefaultAllocator : Allocator { #[inline] - fn dist(&self, other : &Self, _ : L2) -> E { - LpNorm(2).metric_distance(self, other) + fn dist>(&self, other : I, _ : L2) -> E { + nalgebra::Norm::metric_distance(&LpNorm(2), self, other.ref_instance()) } } impl Norm for Vector where M : Dim, - S : StorageMut, - E : Float + Scalar + ClosedMul + ClosedAdd + Zero + One + RealField, - DefaultAllocator : Allocator { + S : Storage, + E : Float + Scalar + Zero + One + RealField, + DefaultAllocator : Allocator { #[inline] fn norm(&self, _ : Linfinity) -> E { - UniformNorm.norm(self) + nalgebra::Norm::norm(&UniformNorm, self) } } impl Dist for Vector where M : Dim, - S : StorageMut, - E : Float + Scalar + ClosedMul + ClosedAdd + Zero + One + RealField, - DefaultAllocator : Allocator { + S : Storage + Clone, + E : Float + Scalar + Zero + One + RealField, + DefaultAllocator : Allocator { #[inline] - fn dist(&self, other : &Self, _ : Linfinity) -> E { - UniformNorm.metric_distance(self, other) + fn dist>(&self, other : I, _ : Linfinity) -> E { + nalgebra::Norm::metric_distance(&UniformNorm, self, other.ref_instance()) } } diff -r d14c877e14b7 -r b3c35d16affe src/norms.rs --- a/src/norms.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/norms.rs Mon Feb 03 19:22:16 2025 -0500 @@ -3,18 +3,31 @@ */ use serde::Serialize; +use std::marker::PhantomData; use crate::types::*; use crate::euclidean::*; +use crate::mapping::{Mapping, Space, Instance}; // // Abstract norms // +#[derive(Copy,Clone,Debug)] +/// Helper structure to convert a [`NormExponent`] into a [`Mapping`] +pub struct NormMapping{ + pub(crate) exponent : E, + _phantoms : PhantomData +} + /// An exponent for norms. /// -// Just a collection of desirabl attributes for a marker type -pub trait NormExponent : Copy + Send + Sync + 'static {} - +// Just a collection of desirable attributes for a marker type +pub trait NormExponent : Copy + Send + Sync + 'static { + /// Return the norm as a mappin + fn as_mapping(self) -> NormMapping { + NormMapping{ exponent : self, _phantoms : PhantomData } + } +} /// Exponent type for the 1-[`Norm`]. #[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)] @@ -37,6 +50,15 @@ pub struct L21; impl NormExponent for L21 {} +/// Norms for pairs (a, b). ‖(a,b)‖ = ‖(‖a‖_A, ‖b‖_B)‖_J +/// For use with [`crate::direct_product::Pair`] +#[derive(Copy,Debug,Clone,Serialize,Eq,PartialEq)] +pub struct PairNorm(pub A, pub B, pub J); + +impl NormExponent for PairNorm +where A : NormExponent, B : NormExponent, J : NormExponent {} + + /// A Huber/Moreau–Yosida smoothed [`L1`] norm. (Not a norm itself.) /// /// The parameter γ of this type is the smoothing factor. Zero means no smoothing, and higher @@ -82,9 +104,9 @@ } /// Trait for distances with respect to a norm. -pub trait Dist : Norm { +pub trait Dist : Norm + Space { /// Calculate the distance - fn dist(&self, other : &Self, _p : Exponent) -> F; + fn dist>(&self, other : I, _p : Exponent) -> F; } /// Trait for Euclidean projections to the `Exponent`-[`Norm`]-ball. @@ -97,7 +119,7 @@ /// /// println!("{:?}, {:?}", x.proj_ball(1.0, L2), x.proj_ball(0.5, Linfinity)); /// ``` -pub trait Projection : Norm + Euclidean +pub trait Projection : Norm + Sized where F : Float { /// Projection of `self` to the `q`-norm-ball of radius ρ. fn proj_ball(mut self, ρ : F, q : Exponent) -> Self { @@ -106,7 +128,7 @@ } /// In-place projection of `self` to the `q`-norm-ball of radius ρ. - fn proj_ball_mut(&mut self, ρ : F, _q : Exponent); + fn proj_ball_mut(&mut self, ρ : F, q : Exponent); } /*impl> Norm for E { @@ -149,8 +171,149 @@ } impl> Dist> for E { - fn dist(&self, other : &Self, huber : HuberL1) -> F { + fn dist>(&self, other : I, huber : HuberL1) -> F { huber.apply(self.dist2_squared(other)) } } +// impl> Norm for Vec { +// fn norm(&self, _l21 : L21) -> F { +// self.iter().map(|e| e.norm(L2)).sum() +// } +// } + +// impl> Dist for Vec { +// fn dist>(&self, other : I, _l21 : L21) -> F { +// self.iter().zip(other.iter()).map(|(e, g)| e.dist(g, L2)).sum() +// } +// } + +impl Mapping for NormMapping +where + F : Float, + E : NormExponent, + Domain : Space + Norm, +{ + type Codomain = F; + + #[inline] + fn apply>(&self, x : I) -> F { + x.eval(|r| r.norm(self.exponent)) + } +} + +pub trait Normed : Space + Norm { + type NormExp : NormExponent; + + fn norm_exponent(&self) -> Self::NormExp; + + #[inline] + fn norm_(&self) -> F { + self.norm(self.norm_exponent()) + } + + // fn similar_origin(&self) -> Self; + + fn is_zero(&self) -> bool; +} + +pub trait HasDual : Normed { + type DualSpace : Normed; +} + +/// Automatically implemented trait for reflexive spaces +pub trait Reflexive : HasDual +where + Self::DualSpace : HasDual +{ } + +impl> Reflexive for X +where + X::DualSpace : HasDual +{ } + +pub trait HasDualExponent : NormExponent { + type DualExp : NormExponent; + + fn dual_exponent(&self) -> Self::DualExp; +} + +impl HasDualExponent for L2 { + type DualExp = L2; + + #[inline] + fn dual_exponent(&self) -> Self::DualExp { + L2 + } +} + +impl HasDualExponent for L1 { + type DualExp = Linfinity; + + #[inline] + fn dual_exponent(&self) -> Self::DualExp { + Linfinity + } +} + + +impl HasDualExponent for Linfinity { + type DualExp = L1; + + #[inline] + fn dual_exponent(&self) -> Self::DualExp { + L1 + } +} + +#[macro_export] +macro_rules! impl_weighted_norm { + ($exponent : ty) => { + impl Norm> for D + where + F : Float, + D : Norm, + C : Constant, + { + fn norm(&self, e : Weighted<$exponent, C>) -> F { + let v = e.weight.value(); + assert!(v > F::ZERO); + v * self.norm(e.base_fn) + } + } + + impl NormExponent for Weighted<$exponent, C> {} + + impl HasDualExponent for Weighted<$exponent, C> + where $exponent : HasDualExponent { + type DualExp = Weighted<<$exponent as HasDualExponent>::DualExp, C::Type>; + + fn dual_exponent(&self) -> Self::DualExp { + Weighted { + weight : C::Type::ONE / self.weight.value(), + base_fn : self.base_fn.dual_exponent() + } + } + } + + impl Projection> for T + where + T : Projection, + F : Float, + C : Constant, + { + fn proj_ball(self, ρ : F, q : Weighted<$exponent , C>) -> Self { + self.proj_ball(ρ / q.weight.value(), q.base_fn) + } + + fn proj_ball_mut(&mut self, ρ : F, q : Weighted<$exponent , C>) { + self.proj_ball_mut(ρ / q.weight.value(), q.base_fn) + } + } + } +} + +//impl_weighted_norm!(L1); +//impl_weighted_norm!(L2); +//impl_weighted_norm!(Linfinity); + diff -r d14c877e14b7 -r b3c35d16affe src/operator_arithmetic.rs --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/operator_arithmetic.rs Mon Feb 03 19:22:16 2025 -0500 @@ -0,0 +1,117 @@ +/*! +Arithmetic of [`Mapping`]s. + */ + +use serde::Serialize; +use crate::types::*; +use crate::instance::{Space, Instance}; +use crate::mapping::{Mapping, DifferentiableImpl, DifferentiableMapping}; + +/// A trait for encoding constant [`Float`] values +pub trait Constant : Copy + Sync + Send + 'static + std::fmt::Debug + Into { + /// The type of the value + type Type : Float; + /// Returns the value of the constant + fn value(&self) -> Self::Type; +} + +impl Constant for F { + type Type = F; + #[inline] + fn value(&self) -> F { *self } +} + +/// Weighting of a [`Mapping`] by scalar multiplication. +#[derive(Copy,Clone,Debug,Serialize)] +pub struct Weighted { + /// The weight + pub weight : C, + /// The base [`Mapping`] being weighted. + pub base_fn : T, +} + +impl Weighted +where + C : Constant, +{ + /// Construct from an iterator. + pub fn new(weight : C, base_fn : T) -> Self { + Weighted{ weight, base_fn } + } +} + +impl<'a, T, V, D, F, C> Mapping for Weighted +where + F : Float, + D : Space, + T : Mapping, + V : Space + ClosedMul, + C : Constant +{ + type Codomain = V; + + #[inline] + fn apply>(&self, x : I) -> Self::Codomain { + self.base_fn.apply(x) * self.weight.value() + } +} + +impl<'a, T, V, D, F, C> DifferentiableImpl for Weighted +where + F : Float, + D : Space, + T : DifferentiableMapping, + V : Space + std::ops::Mul, + C : Constant +{ + type Derivative = V; + + #[inline] + fn differential_impl>(&self, x : I) -> Self::Derivative { + self.base_fn.differential(x) * self.weight.value() + } +} + +/// A sum of [`Mapping`]s. +#[derive(Serialize, Debug, Clone)] +pub struct MappingSum(Vec); + +impl< M> MappingSum { + /// Construct from an iterator. + pub fn new>(iter : I) -> Self { + MappingSum(iter.into_iter().collect()) + } + + /// Iterate over the component functions of the sum + pub fn iter(&self) -> std::slice::Iter<'_, M> { + self.0.iter() + } +} + +impl Mapping for MappingSum +where + Domain : Space + Clone, + M : Mapping, + M::Codomain : std::iter::Sum + Clone +{ + type Codomain = M::Codomain; + + fn apply>(&self, x : I) -> Self::Codomain { + let xr = x.ref_instance(); + self.0.iter().map(|c| c.apply(xr)).sum() + } +} + +impl DifferentiableImpl for MappingSum< M> +where + Domain : Space + Clone, + M : DifferentiableMapping, + M :: DerivativeDomain : std::iter::Sum +{ + type Derivative = M::DerivativeDomain; + + fn differential_impl>(&self, x : I) -> Self::Derivative { + let xr = x.ref_instance(); + self.0.iter().map(|c| c.differential(xr)).sum() + } +} diff -r d14c877e14b7 -r b3c35d16affe src/parallelism.rs --- a/src/parallelism.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/parallelism.rs Mon Feb 03 19:22:16 2025 -0500 @@ -16,9 +16,9 @@ Ordering::{Release, Relaxed}, }; -#[cfg(use_custom_thread_pool)] +#[cfg(feature = "use_custom_thread_pool")] type Pool = ThreadPool; -#[cfg(not(use_custom_thread_pool))] +#[cfg(not(feature = "use_custom_thread_pool"))] type Pool = GlobalPool; const ONE : NonZeroUsize = unsafe { NonZeroUsize::new_unchecked(1) }; @@ -27,12 +27,12 @@ static mut POOL : Option = None; static INIT: Once = Once::new(); -#[cfg(not(use_custom_thread_pool))] +#[cfg(not(feature = "use_custom_thread_pool"))] mod global_pool { /// This is a nicer way to use the global pool of [`rayon`]. pub struct GlobalPool; - #[cfg(not(use_custom_thread_pool))] + #[cfg(not(feature = "use_custom_thread_pool"))] impl GlobalPool { #[inline] pub fn scope<'scope, OP, R>(&self, op: OP) -> R @@ -44,7 +44,7 @@ } } -#[cfg(not(use_custom_thread_pool))] +#[cfg(not(feature = "use_custom_thread_pool"))] pub use global_pool::GlobalPool; /// Set the number of threads. @@ -57,10 +57,10 @@ let n = n.get(); set_task_overbudgeting((n + 1) / 2); POOL = if n > 1 { - #[cfg(use_custom_thread_pool)] { + #[cfg(feature = "use_custom_thread_pool")] { Some(ThreadPoolBuilder::new().num_threads(n).build().unwrap()) } - #[cfg(not(use_custom_thread_pool))] { + #[cfg(not(feature = "use_custom_thread_pool"))] { ThreadPoolBuilder::new().num_threads(n).build_global().unwrap(); Some(GlobalPool) } @@ -75,6 +75,7 @@ /// The initial value is 1. Calling [`set_num_threads`] sets this to $m = (n + 1) / 2$, where /// $n$ is the number of threads. pub fn set_task_overbudgeting(m : usize) { + #[allow(static_mut_refs)] unsafe { TASK_OVERBUDGETING.store(m, Relaxed) } } @@ -97,6 +98,7 @@ /// If the number of configured threads is less than 2, this is None. /// The pool has [`num_threads`]` - 1` threads. pub fn thread_pool() -> Option<&'static Pool> { + #[allow(static_mut_refs)] unsafe { POOL.as_ref() } } @@ -146,6 +148,7 @@ /// is `None`, the [global setting][set_task_overbudgeting] is used.§ pub fn init(overbudget : Option) -> Self { let n = num_threads().get(); + #[allow(static_mut_refs)] let m = overbudget.unwrap_or_else(|| unsafe { TASK_OVERBUDGETING.load(Relaxed) }); if n <= 1 { Self::SingleThreaded diff -r d14c877e14b7 -r b3c35d16affe src/sets.rs --- a/src/sets.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/sets.rs Mon Feb 03 19:22:16 2025 -0500 @@ -3,19 +3,19 @@ */ use std::ops::{RangeFull,RangeFrom,Range,RangeInclusive,RangeTo,RangeToInclusive}; -use std::marker::PhantomData; use crate::types::*; use crate::loc::Loc; -use crate::euclidean::Dot; +use crate::euclidean::Euclidean; +use crate::instance::{Space, Instance}; use serde::Serialize; -mod cube; +pub mod cube; pub use cube::Cube; /// Trait for arbitrary sets. The parameter `U` is the element type. -pub trait Set where U : ?Sized { +pub trait Set where U : Space { /// Check for element containment - fn contains(&self, item : &U) -> bool; + fn contains>(&self, item : I) -> bool; } /// Additional ordering (besides [`PartialOrd`]) of a subfamily of sets: @@ -31,22 +31,27 @@ impl Set> for Cube where U : Num + PartialOrd + Sized { - fn contains(&self, item : &Loc) -> bool { - self.0.iter().zip(item.iter()).all(|(s, x)| s.contains(x)) + fn contains>>(&self, item : I) -> bool { + self.0.iter().zip(item.ref_instance().iter()).all(|(s, x)| s.contains(x)) } } -impl Set for RangeFull { - fn contains(&self, _item : &U) -> bool { true } +impl Set for RangeFull { + fn contains>(&self, _item : I) -> bool { true } } macro_rules! impl_ranges { ($($range:ident),*) => { $( - impl Set - for $range - where Idx : PartialOrd, U : PartialOrd + ?Sized, Idx : PartialOrd { + impl Set for $range + where + Idx : PartialOrd, + U : PartialOrd + Space, + Idx : PartialOrd + { #[inline] - fn contains(&self, item : &U) -> bool { $range::contains(&self, item) } + fn contains>(&self, item : I) -> bool { + item.eval(|x| $range::contains(&self, x)) + } } )* } } @@ -57,44 +62,40 @@ /// /// The halfspace is $H = \\{ t v + a \mid a^⊤ v = 0 \\}$, where $v$ is the orthogonal /// vector and $t$ the offset. -/// -/// `U` is the element type, `F` the floating point number type, and `A` the type of the -/// orthogonal (dual) vectors. They need implement [`Dot`]. #[derive(Clone,Copy,Debug,Serialize,Eq,PartialEq)] -pub struct Halfspace where A : Dot, F : Float { +pub struct Halfspace where A : Euclidean, F : Float { pub orthogonal : A, pub offset : F, - _phantom : PhantomData, } -impl Halfspace where A : Dot, F : Float { +impl Halfspace where A : Euclidean, F : Float { #[inline] pub fn new(orthogonal : A, offset : F) -> Self { - Halfspace{ orthogonal : orthogonal, offset : offset, _phantom : PhantomData } + Halfspace{ orthogonal : orthogonal, offset : offset } } } /// Trait for generating a halfspace spanned by another set `Self` of elements of type `U`. -pub trait SpannedHalfspace where F : Float { +pub trait SpannedHalfspace where F : Float { /// Type of the orthogonal vector describing the halfspace. - type A : Dot; + type A : Euclidean; /// Returns the halfspace spanned by this set. - fn spanned_halfspace(&self) -> Halfspace; + fn spanned_halfspace(&self) -> Halfspace; } // TODO: Gram-Schmidt for higher N. -impl SpannedHalfspace> for [Loc; 2] { +impl SpannedHalfspace for [Loc; 2] { type A = Loc; - fn spanned_halfspace(&self) -> Halfspace> { + fn spanned_halfspace(&self) -> Halfspace { let (x0, x1) = (self[0], self[1]); Halfspace::new(x1-x0, x0[0]) } } // TODO: Gram-Schmidt for higher N. -impl SpannedHalfspace> for [Loc; 2] { +impl SpannedHalfspace for [Loc; 2] { type A = Loc; - fn spanned_halfspace(&self) -> Halfspace> { + fn spanned_halfspace(&self) -> Halfspace { let (x0, x1) = (&self[0], &self[1]); let d = x1 - x0; let orthog = loc![d[1], -d[0]]; // We don't normalise for efficiency @@ -103,19 +104,29 @@ } } -impl Set for Halfspace where A : Dot, F : Float { +impl Set for Halfspace +where + A : Euclidean, + F : Float, +{ #[inline] - fn contains(&self, item : &U) -> bool { + fn contains>(&self, item : I) -> bool { self.orthogonal.dot(item) >= self.offset } } /// Polygons defined by `N` `Halfspace`s. #[derive(Clone,Copy,Debug,Eq,PartialEq)] -pub struct NPolygon(pub [Halfspace; N]) where A : Dot, F : Float; +pub struct NPolygon(pub [Halfspace; N]) +where A : Euclidean, F : Float; -impl Set for NPolygon where A : Dot, F : Float { - fn contains(&self, item : &U) -> bool { - self.0.iter().all(|halfspace| halfspace.contains(item)) +impl Set for NPolygon +where + A : Euclidean, + F : Float, +{ + fn contains>(&self, item : I) -> bool { + let r = item.ref_instance(); + self.0.iter().all(|halfspace| halfspace.contains(r)) } } diff -r d14c877e14b7 -r b3c35d16affe src/sets/cube.rs --- a/src/sets/cube.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/sets/cube.rs Mon Feb 03 19:22:16 2025 -0500 @@ -333,7 +333,9 @@ impl_common!(u8 u16 u32 u64 u128 usize i8 i16 i32 i64 i128 isize, min, max); + // Any NaN yields NaN +#[cfg(feature = "nightly")] impl_common!(f32 f64, minimum, maximum); impl std::ops::Index for Cube { diff -r d14c877e14b7 -r b3c35d16affe src/types.rs --- a/src/types.rs Tue Feb 20 12:33:16 2024 -0500 +++ b/src/types.rs Mon Feb 03 19:22:16 2025 -0500 @@ -13,6 +13,14 @@ pub use num_traits::Float as NumTraitsFloat; // needed to re-export functions. pub use num_traits::cast::AsPrimitive; +pub use simba::scalar::{ + ClosedAdd, ClosedAddAssign, + ClosedSub, ClosedSubAssign, + ClosedMul, ClosedMulAssign, + ClosedDiv, ClosedDivAssign, + ClosedNeg +}; + /// Typical integer type #[allow(non_camel_case_types)] pub type int = i64; @@ -57,7 +65,8 @@ + CastFrom + CastFrom + CastFrom + CastFrom + CastFrom + CastFrom + CastFrom + CastFrom - + CastFrom + CastFrom { + + CastFrom + CastFrom + + crate::instance::Space { const ZERO : Self; const ONE : Self; @@ -84,6 +93,7 @@ const SQRT_2 : Self; const INFINITY : Self; const NEG_INFINITY : Self; + const NAN : Self; const FRAC_2_SQRT_PI : Self; } @@ -133,6 +143,7 @@ const SQRT_2 : Self = std::f64::consts::SQRT_2; const INFINITY : Self = std::f64::INFINITY; const NEG_INFINITY : Self = std::f64::NEG_INFINITY; + const NAN : Self = std::f64::NAN; const FRAC_2_SQRT_PI : Self = std::f64::consts::FRAC_2_SQRT_PI; } @@ -150,6 +161,7 @@ const SQRT_2 : Self = std::f32::consts::SQRT_2; const INFINITY : Self = std::f32::INFINITY; const NEG_INFINITY : Self = std::f32::NEG_INFINITY; + const NAN : Self = std::f32::NAN; const FRAC_2_SQRT_PI : Self = std::f32::consts::FRAC_2_SQRT_PI; }